*: parsing a spec to a Hierarchy struct

Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
Vincent Batts 2016-03-11 15:53:20 -05:00
parent 16b15e1c29
commit fe68ca2065
4 changed files with 152 additions and 73 deletions

65
hierarchy.go Normal file
View file

@ -0,0 +1,65 @@
package mtree
import (
"fmt"
"io"
"sort"
"strings"
)
// DirectoryHierarchy is the mapped structure for an mtree directory hierarchy
// spec
type DirectoryHierarchy struct {
Entries []Entry
}
// WriteTo simplifies the output of the resulting hierarchy spec
func (dh DirectoryHierarchy) WriteTo(w io.Writer) (n int64, err error) {
sort.Sort(byPos(dh.Entries))
var sum int64
for _, e := range dh.Entries {
i, err := io.WriteString(w, e.String()+"\n")
if err != nil {
return sum, err
}
sum += int64(i)
}
return sum, nil
}
type byPos []Entry
func (bp byPos) Len() int { return len(bp) }
func (bp byPos) Less(i, j int) bool { return bp[i].Pos < bp[j].Pos }
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
type Entry struct {
Pos int // order in the spec
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 {
if e.Raw != "" {
return e.Raw
}
// TODO(vbatts) if type is RelativeType and a keyword of not type=dir
return fmt.Sprintf("%s %s", e.Name, strings.Join(e.Keywords, " "))
}
// EntryType are the formats of lines in an mtree spec file
type EntryType int
// The types of lines to be found in an mtree spec file
const (
SignatureType EntryType = iota // first line of the file, like `#mtree v2.0`
BlankType // blank lines are ignored
CommentType // Lines beginning with `#` are ignored
SpecialType // line that has `/` prefix issue a "special" command (currently only /set and /unset)
RelativeType // if the first white-space delimited word does not have a '/' in it. Options/keywords are applied.
DotDotType // .. - A relative path step. keywords/options are ignored
FullType // if the first word on the line has a `/` after the first character, it interpretted as a file pathname with options
)

View file

@ -1,58 +0,0 @@
package mtree
import "io"
// Positioner responds with the newline delimited position
type Positioner interface {
Pos() int
}
// DirectoryHierarchy is the mapped structure for an mtree directory hierarchy
// spec
type DirectoryHierarchy struct {
Comments []Comment
Entries []Entry
}
// WriteTo simplifies the output of the resulting hierarchy spec
func (dh DirectoryHierarchy) WriteTo(w io.Writer) (n int64, err error) {
return 0, nil
}
// Comment stores arbitrary metadata for the spec. Sometimes "user", "machine",
// "tree", and "date". But most of the time, it includes the relative path of
// the directory being stepped into. Or a "signature" like `#mtree v2.0`,
type Comment struct {
Position int
Str string
// TODO(vbatts) include a comment line parser
}
// Pos returns the line of this comment
func (c Comment) Pos() int {
return c.Position
}
// Entry is each component of content in the mtree spec file
type Entry struct {
Position int // order in the spec
Name string // file or directory name
Keywords []string // TODO(vbatts) maybe a keyword typed set of values?
str string // raw string. needed?
Type EntryType
}
// Pos returns the line of this comment
func (e Entry) Pos() int {
return e.Position
}
type EntryType int
const (
SpecialType int = iota // line that has `/` prefix issue a "special" command (currently only /set and /unset)
FileType // indented line
DirectoryType // ^ matched line, that is not /set
PathStepType // .. - keywords/options are ignored
FullType // if the first word on the line has a `/` after the first character, it interpretted as a file pathname with options
)

View file

@ -1,11 +1,8 @@
package mtree package mtree
import ( import (
"bufio"
"fmt" "fmt"
"log"
"os" "os"
"strings"
"testing" "testing"
) )
@ -18,24 +15,22 @@ func TestParser(t *testing.T) {
func() { func() {
fh, err := os.Open(file) fh, err := os.Open(file)
if err != nil { if err != nil {
log.Println(err) t.Error(err)
return return
} }
defer fh.Close() defer fh.Close()
s := bufio.NewScanner(fh) dh, err := ParseSpec(fh)
for s.Scan() { if err != nil {
str := s.Text() t.Error(err)
switch {
case strings.HasPrefix(str, "#"):
continue
default:
} }
fmt.Printf("%q\n", str) fmt.Printf("%q", dh)
}
if err := s.Err(); err != nil { _, err = dh.WriteTo(os.Stdout)
log.Println("ERROR:", err) if err != nil {
t.Error(err)
} }
}() }()
} }
} }

77
parse.go Normal file
View file

@ -0,0 +1,77 @@
package mtree
import (
"bufio"
"io"
"strings"
)
func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
s := bufio.NewScanner(r)
i := int(0)
dh := DirectoryHierarchy{}
for s.Scan() {
str := s.Text()
e := Entry{Pos: i}
switch {
case strings.HasPrefix(str, "#"):
e.Raw = str
if strings.HasPrefix(str, "#mtree") {
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]
e.Keywords = f[1:]
case len(strings.Fields(str)) > 0 && strings.Fields(str)[0] == "..":
e.Type = DotDotType
e.Raw = str
// 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
}
}
// parse the options
f := strings.Fields(str)
if strings.Contains(str, "/") {
e.Type = FullType
} else {
e.Type = RelativeType
}
e.Name = f[0]
e.Keywords = f[1:]
default:
// TODO(vbatts) log a warning?
continue
}
dh.Entries = append(dh.Entries, e)
i++
}
return &dh, s.Err()
}