diff options
-rw-r--r-- | frontend.go | 182 |
1 files 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 <dataref> LF + dataref := strings.TrimSuffix(strings.TrimPrefix(line, "cat-blob "), "\n") + f.cmd <- CmdCatBlob{DataRef: dataref} + case strings.HasPrefix(line, "get-mark :"): + // 'get-mark' SP ':' <idnum> 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 <ref> LF + // mark? + // ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? + // 'committer' (SP <name>)? SP LT <email> GT SP <when> LF + // data + // ('from' SP <commit-ish> LF)? + // ('merge' SP <commit-ish> LF)* + // (filemodify | filedelete | filecopy | filerename | filedeleteall | notemodify)* + // TODO case strings.HasPrefix(line, "feature "): + // 'feature' SP <feature> ('=' <argument>)? LF + // TODO case strings.HasPrefix(line, "ls "): + // 'ls' SP <dataref> SP <path> LF + // TODO case strings.HasPrefix(line, "option "): + // 'option' SP <option> LF + // TODO case strings.HasPrefix(line, "progress "): + // 'progress' SP <any> LF + str := strings.TrimSuffix(strings.TrimPrefix(line, "progress "), "\n") + f.cmd <- CmdProgress{Str: str} case strings.HasPrefix(line, "reset "): + // 'reset' SP <ref> LF + // ('from' SP <commit-ish> LF)? + // TODO case strings.HasPrefix(line, "tag "): + // 'tag' SP <name> LF + // 'from' SP <commit-ish> LF + // 'tagger' (SP <name>)? SP LT <email> GT SP <when> LF + // data + // TODO default: - f.c <- cmderror{nil, UnsupportedCommand(line)} - return + return UnsupportedCommand(line) + } + line, err = f.nextLine() + if err != nil { + return err } } } func (f *Frontend) ReadCmd() (Cmd, error) { - cmderror := <-f.c - cmd := cmderror.Cmd - err := cmderror.error - return cmderror.Cmd, cmderror.error + cmd, ok := <-f.cmd + if ok { + return cmd, nil + } + return nil, f.err } func (f *Frontend) RespondGetMark(sha1 string) error { |