*: parsing a spec to a Hierarchy struct
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
parent
16b15e1c29
commit
fe68ca2065
4 changed files with 152 additions and 73 deletions
65
hierarchy.go
Normal file
65
hierarchy.go
Normal 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
|
||||||
|
)
|
58
mtree.go
58
mtree.go
|
@ -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
|
|
||||||
)
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
if err := s.Err(); err != nil {
|
fmt.Printf("%q", dh)
|
||||||
log.Println("ERROR:", err)
|
|
||||||
|
_, err = dh.WriteTo(os.Stdout)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
77
parse.go
Normal file
77
parse.go
Normal 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()
|
||||||
|
}
|
Loading…
Reference in a new issue