2016-03-11 20:53:20 +00:00
|
|
|
package mtree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"io"
|
2016-04-06 16:48:35 +00:00
|
|
|
"path/filepath"
|
2016-03-11 20:53:20 +00:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2016-03-16 19:59:34 +00:00
|
|
|
// ParseSpec reads a stream of an mtree specification, and returns the DirectoryHierarchy
|
2016-03-11 20:53:20 +00:00
|
|
|
func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
|
|
|
|
s := bufio.NewScanner(r)
|
|
|
|
i := int(0)
|
2016-03-18 20:30:54 +00:00
|
|
|
creator := dhCreator{
|
|
|
|
DH: &DirectoryHierarchy{},
|
|
|
|
}
|
2016-03-11 20:53:20 +00:00
|
|
|
for s.Scan() {
|
|
|
|
str := s.Text()
|
2016-07-15 15:48:40 +00:00
|
|
|
trimmedStr := strings.TrimLeftFunc(str, func(c rune) bool {
|
|
|
|
return c == ' ' || c == '\t'
|
|
|
|
})
|
2016-03-11 20:53:20 +00:00
|
|
|
e := Entry{Pos: i}
|
|
|
|
switch {
|
2016-07-15 15:48:40 +00:00
|
|
|
case strings.HasPrefix(trimmedStr, "#"):
|
2016-03-11 20:53:20 +00:00
|
|
|
e.Raw = str
|
2016-07-15 15:48:40 +00:00
|
|
|
if strings.HasPrefix(trimmedStr, "#mtree") {
|
2016-03-11 20:53:20 +00:00
|
|
|
e.Type = SignatureType
|
|
|
|
} else {
|
|
|
|
e.Type = CommentType
|
|
|
|
// from here, the comment could be "# key: value" metadata
|
|
|
|
// or a relative path hint
|
|
|
|
}
|
|
|
|
case str == "":
|
|
|
|
e.Type = BlankType
|
|
|
|
// nothing else to do here
|
|
|
|
case strings.HasPrefix(str, "/"):
|
|
|
|
e.Type = SpecialType
|
|
|
|
// collapse any escaped newlines
|
|
|
|
for {
|
|
|
|
if strings.HasSuffix(str, `\`) {
|
|
|
|
str = str[:len(str)-1]
|
|
|
|
s.Scan()
|
|
|
|
str += s.Text()
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// parse the options
|
|
|
|
f := strings.Fields(str)
|
|
|
|
e.Name = f[0]
|
2016-11-18 00:47:31 +00:00
|
|
|
e.Keywords = StringToKeyVals(f[1:])
|
2016-03-18 20:30:54 +00:00
|
|
|
if e.Name == "/set" {
|
|
|
|
creator.curSet = &e
|
|
|
|
} else if e.Name == "/unset" {
|
|
|
|
creator.curSet = nil
|
|
|
|
}
|
2016-03-11 20:53:20 +00:00
|
|
|
case len(strings.Fields(str)) > 0 && strings.Fields(str)[0] == "..":
|
|
|
|
e.Type = DotDotType
|
|
|
|
e.Raw = str
|
2016-04-05 20:20:04 +00:00
|
|
|
if creator.curDir != nil {
|
|
|
|
creator.curDir = creator.curDir.Parent
|
|
|
|
}
|
2016-03-11 20:53:20 +00:00
|
|
|
// nothing else to do here
|
|
|
|
case len(strings.Fields(str)) > 0:
|
|
|
|
// collapse any escaped newlines
|
|
|
|
for {
|
|
|
|
if strings.HasSuffix(str, `\`) {
|
|
|
|
str = str[:len(str)-1]
|
|
|
|
s.Scan()
|
|
|
|
str += s.Text()
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2023-06-04 10:00:33 +00:00
|
|
|
|
2016-03-11 20:53:20 +00:00
|
|
|
// parse the options
|
|
|
|
f := strings.Fields(str)
|
2023-06-04 10:02:40 +00:00
|
|
|
e.Name = f[0]
|
2023-06-04 10:00:33 +00:00
|
|
|
e.Keywords = StringToKeyVals(f[1:])
|
|
|
|
// TODO: gather keywords if using tar stream
|
|
|
|
var isDir bool
|
|
|
|
for _, kv := range e.Keywords {
|
|
|
|
if kv.Keyword() == "type" {
|
|
|
|
isDir = kv.Value() == "dir"
|
|
|
|
}
|
|
|
|
}
|
2016-04-06 16:48:35 +00:00
|
|
|
if strings.Contains(e.Name, "/") {
|
2016-03-11 20:53:20 +00:00
|
|
|
e.Type = FullType
|
|
|
|
} else {
|
|
|
|
e.Type = RelativeType
|
2023-06-04 10:00:33 +00:00
|
|
|
e.Parent = creator.curDir
|
|
|
|
if isDir {
|
|
|
|
creator.curDir = &e
|
2016-03-18 20:30:54 +00:00
|
|
|
}
|
|
|
|
}
|
2023-06-04 10:00:33 +00:00
|
|
|
if !isDir {
|
|
|
|
creator.curEnt = &e
|
|
|
|
}
|
2016-07-27 12:03:43 +00:00
|
|
|
e.Set = creator.curSet
|
2023-06-04 10:02:40 +00:00
|
|
|
// we need to clean the filepath at the end because '/'s can be
|
|
|
|
// stripped, which would cause FullTypes to be treated as
|
|
|
|
// RelativeTypes above
|
|
|
|
e.Name = filepath.Clean(e.Name)
|
2016-03-11 20:53:20 +00:00
|
|
|
default:
|
|
|
|
// TODO(vbatts) log a warning?
|
|
|
|
continue
|
|
|
|
}
|
2016-03-18 20:30:54 +00:00
|
|
|
creator.DH.Entries = append(creator.DH.Entries, e)
|
2016-03-11 20:53:20 +00:00
|
|
|
i++
|
|
|
|
}
|
2016-03-18 20:30:54 +00:00
|
|
|
return creator.DH, s.Err()
|
2016-03-11 20:53:20 +00:00
|
|
|
}
|