From 17e8573c8f240191f267206784c80e5c9131302f Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 16 Nov 2017 15:30:54 -0500 Subject: frontend: more --- frontend.go | 182 +++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 143 insertions(+), 39 deletions(-) diff --git a/frontend.go b/frontend.go index 405ead4..f8f335d 100644 --- a/frontend.go +++ b/frontend.go @@ -2,7 +2,9 @@ package libfastimport import ( "bufio" + "fmt" "io" + "strconv" "strings" "git.lukeshu.com/go/libfastimport/textproto" @@ -14,81 +16,183 @@ func (e UnsupportedCommand) Error() string { return "Unsupported command: " + string(e) } -type cmderror struct { - Cmd - error -} - // A Frontend is something that produces a fast-import stream; the // Frontend object provides methods for reading from it. type Frontend struct { fir *textproto.FIReader cbw *textproto.CatBlobWriter w *bufio.Writer - c chan cmderror + + cmd chan Cmd + err error } func NewFrontend(fastImport io.Reader, catBlob io.Writer) *Frontend { - ret := Frontend{} + ret := &Frontend{} ret.fir = textproto.NewFIReader(fastImport) if catBlob != nil { ret.w = bufio.NewWriter(catBlob) ret.cbw = textproto.NewCatBlobWriter(ret.w) } - return &ret + ret.cmd = make(chan Cmd) + go func() { + ret.err = ret.parse() + close(ret.cmd) + }() + return ret } func (f *Frontend) nextLine() (line string, err error) { -retry: - line, err = f.fir.ReadLine() - if err != nil { - return - } - switch { - case strings.HasPrefix(line, "#"): - f.c <- cmderror{CmdComment{Comment: line[1:]}, nil} - goto retry - case strings.HasPrefix(line, "cat-blob "): - f.c <- parse_cat_blob(line) - goto retry - case strings.HasPrefix(line, "get-mark "): - f.c <- parse_get_mark(line) - goto retry - default: - return + for { + line, err = f.fir.ReadLine() + if err != nil { + return + } + switch { + case strings.HasPrefix(line, "#"): + f.cmd <- CmdComment{Comment: line[1:]} + case strings.HasPrefix(line, "cat-blob "): + // 'cat-blob' SP LF + dataref := strings.TrimSuffix(strings.TrimPrefix(line, "cat-blob "), "\n") + f.cmd <- CmdCatBlob{DataRef: dataref} + case strings.HasPrefix(line, "get-mark :"): + // 'get-mark' SP ':' LF + strIdnum := strings.TrimSuffix(strings.TrimPrefix(line, "get-mark :"), "\n") + var nIdnum int + nIdnum, err = strconv.Atoi(strIdnum) + if err != nil { + line = "" + err = fmt.Errorf("get-mark: %v", err) + return + } + f.cmd <- CmdGetMark{Mark: nIdnum} + default: + return + } } } -func (f *Frontend) parse() { - for { - line, err := f.nextLine() +func parse_data(line string) (data string, err error) { + nl := strings.IndexByte(line, '\n') + if nl < 0 { + return "", fmt.Errorf("data: expected newline: %v", data) + } + head := line[:nl] + rest := line[nl+1:] + if !strings.HasPrefix(head, "data ") { + return "", fmt.Errorf("data: could not parse: %v", data) + } + if strings.HasPrefix(head, "data <<") { + // Delimited format + delim := strings.TrimPrefix(head, "data <<") + suffix := "\n" + delim + "\n" + if !strings.HasSuffix(rest, suffix) { + return "", fmt.Errorf("data: did not find suffix: %v", suffix) + } + data = strings.TrimSuffix(rest, suffix) + } else { + // Exact byte count format + strN := strings.TrimSuffix(head, "data ") + intN, err := strconv.Atoi(strN) if err != nil { - f.c <- cmderror{nil, err} - return + return "", err + } + if intN != len(rest) { + panic("FIReader should not have let this happen") } + data = rest + } + return +} + +func (f *Frontend) parse() error { + line, err := f.nextLine() + if err != nil { + return err + } + for { switch { - case strings.HasPrefix(line, "blob "): + case line == "blob\n": + // 'blob' LF + // mark? + // data + c := CmdBlob{} + line, err = f.nextLine() + if err != nil { + return err + } + if strings.HasPrefix(line, "mark :") { + str := strings.TrimSuffix(strings.TrimPrefix(line, "mark :"), "\n") + c.Mark, err = strconv.Atoi(str) + if err != nil { + return err + } + line, err = f.nextLine() + if err != nil { + return err + } + } + if !strings.HasPrefix(line, "data ") { + return fmt.Errorf("Unexpected command in blob: %q", line) + } + c.Data, err = parse_data(line) + if err != nil { + return err + } + f.cmd <- c + case line == "checkpoint\n": + f.cmd <- CmdCheckpoint{} + case line == "done\n": + f.cmd <- CmdDone{} case strings.HasPrefix(line, "commit "): - case strings.HasPrefix(line, "checkpoint\n"): - case strings.HasPrefix(line, "done\n"): + // 'commit' SP LF + // mark? + // ('author' (SP )? SP LT GT SP LF)? + // 'committer' (SP )? SP LT GT SP LF + // data + // ('from' SP LF)? + // ('merge' SP LF)* + // (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)* + // TODO case strings.HasPrefix(line, "feature "): + // 'feature' SP ('=' )? LF + // TODO case strings.HasPrefix(line, "ls "): + // 'ls' SP SP LF + // TODO case strings.HasPrefix(line, "option "): + // 'option' SP