*: entry linking and keyword filter

Setting up sibling and parent relationships for entries, so they can be
easier to walk.

Also, making "keyword=value" easier to parse. This helps filtering.

Both of these ready us for checking/validating a hierarchy.

Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
Vincent Batts 2016-03-18 13:38:32 -04:00
parent ef9ba54f10
commit 3b6cb6e117
4 changed files with 91 additions and 20 deletions

View file

@ -35,11 +35,14 @@ func (bp byPos) Swap(i, j int) { bp[i], bp[j] = bp[j], bp[i] }
// Entry is each component of content in the mtree spec file // Entry is each component of content in the mtree spec file
type Entry struct { type Entry struct {
Pos int // order in the spec Parent, Child *Entry // up, down
Raw string // file or directory name Prev, Next *Entry // left, right
Name string // file or directory name Set *Entry // current `/set` for additional keywords
Keywords []string // TODO(vbatts) maybe a keyword typed set of values? Pos int // order in the spec
Type EntryType Raw string // file or directory name
Name string // file or directory name
Keywords []string // TODO(vbatts) maybe a keyword typed set of values?
Type EntryType
} }
func (e Entry) String() string { func (e Entry) String() string {

View file

@ -9,6 +9,7 @@ import (
"hash" "hash"
"io" "io"
"os" "os"
"strings"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
@ -18,6 +19,53 @@ import (
// be included for the file entry. Otherwise, empty string. // be included for the file entry. Otherwise, empty string.
type KeywordFunc func(path string, info os.FileInfo) (string, error) type KeywordFunc func(path string, info os.FileInfo) (string, error)
// KeyVal is a "keyword=value"
type KeyVal string
// Keyword is the mapping to the available keywords
func (kv KeyVal) Keyword() string {
if !strings.Contains(string(kv), "=") {
return ""
}
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
if !strings.Contains(chunks, ".") {
return chunks
}
return strings.SplitN(chunks, ".", 2)[0]
}
// KeywordSuffix is really only used for xattr, as the keyword is a prefix to
// the xattr "namespace.key"
func (kv KeyVal) KeywordSuffix() string {
if !strings.Contains(string(kv), "=") {
return ""
}
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
if !strings.Contains(chunks, ".") {
return ""
}
return strings.SplitN(chunks, ".", 2)[1]
}
// Value is the data/value portion of "keyword=value"
func (kv KeyVal) Value() string {
if !strings.Contains(string(kv), "=") {
return ""
}
return strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[1]
}
// keywordSelector takes an array of "keyword=value" and filters out that only the set of words
func keywordSelector(keyval, words []string) []string {
retList := []string{}
for _, kv := range keyval {
if inSlice(KeyVal(kv).Keyword(), words) {
retList = append(retList, kv)
}
}
return retList
}
var ( var (
// DefaultKeywords has the several default keyword producers (uid, gid, // DefaultKeywords has the several default keyword producers (uid, gid,
// mode, nlink, type, size, mtime) // mode, nlink, type, size, mtime)
@ -31,6 +79,7 @@ var (
"nlink", "nlink",
"time", "time",
} }
// SetKeywords is the default set of keywords calculated for a `/set` SpecialType
SetKeywords = []string{ SetKeywords = []string{
"uid", "uid",
"gid", "gid",

46
walk.go
View file

@ -12,12 +12,14 @@ import (
type ExcludeFunc func(path string, info os.FileInfo) bool type ExcludeFunc func(path string, info os.FileInfo) bool
type dhCreator struct { type dhCreator struct {
DH *DirectoryHierarchy DH *DirectoryHierarchy
curSet []string curSet *Entry
depth int curDir *Entry
curDirNum int curEnt *Entry
} }
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 // To be able to do a "walk" that produces an outcome with `/set ...` would
// need a more linear walk, which this can not ensure. // need a more linear walk, which this can not ensure.
@ -36,13 +38,13 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie
// handle the /set SpecialType // handle the /set SpecialType
if info.IsDir() { if info.IsDir() {
if len(creator.curSet) == 0 { if creator.curSet == nil {
// set the initial /set keywords // set the initial /set keywords
e := Entry{ e := Entry{
Name: "/set", Name: "/set",
Type: SpecialType, Type: SpecialType,
Pos: len(creator.DH.Entries), Pos: len(creator.DH.Entries),
Keywords: []string{"type=file", "nlink=1", "flags=none", "mode=0664"}, Keywords: keywordSelector(defaultSetKeywords, keywords),
} }
for _, keyword := range SetKeywords { for _, keyword := range SetKeywords {
if str, err := KeywordFuncs[keyword](path, info); err == nil && str != "" { if str, err := KeywordFuncs[keyword](path, info); err == nil && str != "" {
@ -51,9 +53,9 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie
return err return err
} }
} }
creator.curSet = e.Keywords creator.curSet = &e
creator.DH.Entries = append(creator.DH.Entries, e) creator.DH.Entries = append(creator.DH.Entries, e)
} else if len(creator.curSet) > 0 { } else if creator.curSet != nil {
// check the attributes of the /set keywords and re-set if changed // check the attributes of the /set keywords and re-set if changed
klist := []string{} klist := []string{}
for _, keyword := range SetKeywords { for _, keyword := range SetKeywords {
@ -66,19 +68,20 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie
needNewSet := false needNewSet := false
for _, k := range klist { for _, k := range klist {
if !inSlice(k, creator.curSet) { if !inSlice(k, creator.curSet.Keywords) {
needNewSet = true needNewSet = true
} }
} }
if needNewSet { if needNewSet {
creator.curSet = append([]string{"type=file", "nlink=1", "flags=none", "mode=0664"}, klist...) e := Entry{
creator.DH.Entries = append(creator.DH.Entries, Entry{
Name: "/set", Name: "/set",
Type: SpecialType, Type: SpecialType,
Pos: len(creator.DH.Entries), Pos: len(creator.DH.Entries),
Keywords: creator.curSet, Keywords: append(defaultSetKeywords, klist...),
}) }
creator.curSet = &e
creator.DH.Entries = append(creator.DH.Entries, e)
} else { } else {
creator.DH.Entries = append(creator.DH.Entries, Entry{ creator.DH.Entries = append(creator.DH.Entries, Entry{
Type: BlankType, Type: BlankType,
@ -91,16 +94,30 @@ func Walk(root string, exlcudes []ExcludeFunc, keywords []string) (*DirectoryHie
e := Entry{ e := Entry{
Name: filepath.Base(path), Name: filepath.Base(path),
Pos: len(creator.DH.Entries), Pos: len(creator.DH.Entries),
Set: creator.curSet,
} }
for _, keyword := range keywords { for _, keyword := range keywords {
if str, err := KeywordFuncs[keyword](path, info); err == nil && str != "" { if str, err := KeywordFuncs[keyword](path, info); err == nil && str != "" {
if !inSlice(str, creator.curSet) { if !inSlice(str, creator.curSet.Keywords) {
e.Keywords = append(e.Keywords, str) e.Keywords = append(e.Keywords, str)
} }
} else if err != nil { } else if err != nil {
return err return err
} }
} }
if info.IsDir() {
if creator.curDir != nil {
creator.curDir.Next = &e
}
e.Prev = creator.curDir
creator.curDir = &e
} else {
if creator.curEnt != nil {
creator.curEnt.Next = &e
}
e.Prev = creator.curEnt
creator.curEnt = &e
}
creator.DH.Entries = append(creator.DH.Entries, e) creator.DH.Entries = append(creator.DH.Entries, e)
return nil return nil
}) })
@ -198,5 +215,4 @@ func readOrderedDirNames(dirname string) ([]string, error) {
sort.Strings(names) sort.Strings(names)
sort.Strings(dirnames) sort.Strings(dirnames)
return append(names, dirnames...), nil return append(names, dirnames...), nil
} }

View file

@ -2,6 +2,7 @@ package mtree
import ( import (
"io/ioutil" "io/ioutil"
"log"
"testing" "testing"
) )
@ -11,6 +12,8 @@ func TestWalk(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
log.Fatalf("%#v", dh)
fh, err := ioutil.TempFile("", "walk.") fh, err := ioutil.TempFile("", "walk.")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)