package parser import ( "bufio" "bytes" "errors" "fmt" "io" ) type Command struct { Name string Args string } func (c *Command) Reset() { c.Name = "" c.Args = "" } func Parse(reader io.Reader) ([]Command, error) { var commands []Command var command, modelCommand Command scanner := bufio.NewScanner(reader) scanner.Split(scanModelfile) for scanner.Scan() { line := scanner.Bytes() fields := bytes.SplitN(line, []byte(" "), 2) if len(fields) == 0 { continue } switch string(bytes.ToUpper(fields[0])) { case "FROM": command.Name = "model" command.Args = string(fields[1]) // copy command for validation modelCommand = command case "LICENSE", "TEMPLATE", "SYSTEM": command.Name = string(bytes.ToLower(fields[0])) command.Args = string(fields[1]) case "PARAMETER": fields = bytes.SplitN(fields[1], []byte(" "), 2) command.Name = string(fields[0]) command.Args = string(fields[1]) default: continue } commands = append(commands, command) command.Reset() } if modelCommand.Args == "" { return nil, fmt.Errorf("no FROM line for the model was specified") } return commands, scanner.Err() } func scanModelfile(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF || len(data) == 0 { return 0, nil, nil } newline := bytes.IndexByte(data, '\n') if start := bytes.Index(data, []byte(`"""`)); start >= 0 && start < newline { end := bytes.Index(data[start+3:], []byte(`"""`)) if end < 0 { return 0, nil, errors.New(`unterminated multiline string: """`) } n := start + 3 + end + 3 return n, bytes.Replace(data[:n], []byte(`"""`), []byte(""), 2), nil } if start := bytes.Index(data, []byte(`'''`)); start >= 0 && start < newline { end := bytes.Index(data[start+3:], []byte(`'''`)) if end < 0 { return 0, nil, errors.New("unterminated multiline string: '''") } n := start + 3 + end + 3 return n, bytes.Replace(data[:n], []byte("'''"), []byte(""), 2), nil } return bufio.ScanLines(data, atEOF) }