diff --git a/check.go b/check.go index 7df89fa..f5ed7c5 100644 --- a/check.go +++ b/check.go @@ -8,6 +8,7 @@ import ( ) type Result struct { + // XXX perhaps this is a list of the failed files and keywords? } var ErrNotAllClear = fmt.Errorf("some keyword check failed validation") @@ -24,6 +25,7 @@ func Check(root string, dh *DirectoryHierarchy) (*Result, error) { } sort.Sort(byPos(creator.DH.Entries)) + var failed bool for _, e := range creator.DH.Entries { switch e.Type { case SpecialType: @@ -32,20 +34,39 @@ func Check(root string, dh *DirectoryHierarchy) (*Result, error) { } else if e.Name == "/unset" { creator.curSet = nil } - case DotDotType: - // TODO step - case RelativeType: - // TODO determine path, and check keywords - // or maybe to Chdir when type=dir? - case FullType: - info, err := os.Lstat(filepath.Join(root, e.Name)) + case RelativeType, FullType: + info, err := os.Lstat(filepath.Join(root, e.Path())) if err != nil { return nil, err } - // TODO check against keywords present - _ = info + + var kvs KeyVals + if creator.curSet != nil { + kvs = MergeSet(creator.curSet.Keywords, e.Keywords) + } else { + kvs = NewKeyVals(e.Keywords) + } + + for _, kv := range kvs { + keywordFunc, ok := KeywordFuncs[kv.Keyword()] + if !ok { + return nil, fmt.Errorf("Unknown keyword %q for file %q", kv.Keyword(), e.Path()) + } + curKeyVal, err := keywordFunc(filepath.Join(root, e.Path()), info) + if err != nil { + return nil, err + } + if string(kv) != curKeyVal { + failed = true + fmt.Printf("%q: keyword %q: expected %s; got %s", e.Path(), kv.Keyword(), kv.Value(), KeyVal(curKeyVal).Value()) + } + } } } + if failed { + return nil, ErrNotAllClear + } + return nil, nil } diff --git a/check_test.go b/check_test.go new file mode 100644 index 0000000..e0dccec --- /dev/null +++ b/check_test.go @@ -0,0 +1,20 @@ +package mtree + +import ( + "log" + "testing" +) + +func TestCheck(t *testing.T) { + dh, err := Walk(".", nil, append(DefaultKeywords, "sha1")) + if err != nil { + t.Fatal(err) + } + + res, err := Check(".", dh) + if err != nil { + t.Fatal(err) + } + //log.Fatalf("%#v", dh) + log.Fatalf("%#v", res) +} diff --git a/hierarchy.go b/hierarchy.go index 659cbad..daeab74 100644 --- a/hierarchy.go +++ b/hierarchy.go @@ -47,7 +47,7 @@ type Entry struct { } func (e Entry) Path() string { - if e.Parent == nil { + if e.Parent == nil || e.Type == FullType { return e.Name } return filepath.Join(e.Parent.Path(), e.Name) diff --git a/keywords.go b/keywords.go index 0be12d2..bffe169 100644 --- a/keywords.go +++ b/keywords.go @@ -66,6 +66,52 @@ func keywordSelector(keyval, words []string) []string { return retList } +// NewKeyVals constructs a list of KeyVal from the list of strings, like "keyword=value" +func NewKeyVals(keyvals []string) KeyVals { + kvs := make(KeyVals, len(keyvals)) + for i := range keyvals { + kvs[i] = KeyVal(keyvals[i]) + } + return kvs +} + +// KeyVals is a list of KeyVal +type KeyVals []KeyVal + +// Has the "keyword" present in the list of KeyVal, and returns the +// corresponding KeyVal, else an empty string. +func (kvs KeyVals) Has(keyword string) KeyVal { + for i := range kvs { + if kvs[i].Keyword() == keyword { + return kvs[i] + } + } + return emptyKV +} + +var emptyKV = KeyVal("") + +// MergeSet takes the current setKeyVals, and then applies the entryKeyVals +// such that the entry's values win. The union is returned. +func MergeSet(setKeyVals, entryKeyVals []string) KeyVals { + retList := NewKeyVals(append([]string{}, setKeyVals...)) + eKVs := NewKeyVals(entryKeyVals) + seenKeywords := []string{} + for i := range retList { + word := retList[i].Keyword() + if ekv := eKVs.Has(word); ekv != emptyKV { + retList[i] = ekv + } + seenKeywords = append(seenKeywords, word) + } + for i := range eKVs { + if !inSlice(eKVs[i].Keyword(), seenKeywords) { + retList = append(retList, eKVs[i]) + } + } + return retList +} + var ( // DefaultKeywords has the several default keyword producers (uid, gid, // mode, nlink, type, size, mtime) diff --git a/walk.go b/walk.go index 139311e..2011a6a 100644 --- a/walk.go +++ b/walk.go @@ -20,9 +20,9 @@ type dhCreator struct { var defaultSetKeywords = []string{"type=file", "nlink=1", "flags=none", "mode=0664"} -// -// To be able to do a "walk" that produces an outcome with `/set ...` would -// need a more linear walk, which this can not ensure. +// Walk from root directory and assemble the DirectoryHierarchy. excludes +// provided are used to skip paths. keywords are the set to collect from the +// walked paths. The recommended default list is DefaultKeywords. func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHierarchy, error) { creator := dhCreator{DH: &DirectoryHierarchy{}} // TODO insert signature and metadata comments first (user, machine, tree, date) @@ -107,6 +107,7 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie e := Entry{ Name: filepath.Base(path), Pos: len(creator.DH.Entries), + Type: RelativeType, Set: creator.curSet, Parent: creator.curDir, }