func (p *parser) parseSong() (*songtools.Song, error) { song := &songtools.Song{} token, text, err := p.scanner.next() if err != nil { return nil, err } var section *songtools.Section var line *songtools.Line numNewLines := 0 for token != eofToken { switch token { case commentToken: comment := &songtools.Comment{ Text: text, Hidden: true, } if section != nil { section.Nodes = append(section.Nodes, comment) } else { song.Nodes = append(song.Nodes, comment) } case directiveToken: d, err := parseDirective(text) if err != nil { return nil, err } switch d.Name { case startOfChorusDirectiveName: section = &songtools.Section{ Kind: songtools.SectionKind("Chorus"), } song.Nodes = append(song.Nodes, section) case endOfChorusDirectiveName: section = nil case startOfBridgeDirectiveName: section = &songtools.Section{ Kind: songtools.SectionKind("Bridge"), } song.Nodes = append(song.Nodes, section) case endOfBridgeDirectiveName: section = nil case commentDirectiveName: if section == nil { la := 1 for { // we are going to look forward past all the comments until we find a non-comment nextToken, _, nextErr := p.scanner.la(la) if nextErr != nil { break } if nextToken == directiveToken { innerD, err := parseDirective(text) if err != nil { break } if innerD.Name != commentDirectiveName { break } la += 2 } else if nextToken == chordToken || nextToken == textToken { section = &songtools.Section{ Kind: songtools.SectionKind(d.Value), } song.Nodes = append(song.Nodes, section) break } else { break } } } else { comment := &songtools.Comment{ Text: d.Value, Hidden: false, } if section == nil { song.Nodes = append(song.Nodes, comment) } else { section.Nodes = append(section.Nodes, comment) } } case titleDirectiveName: song.Title = d.Value case subtitleDirectiveName: song.Subtitles = append(song.Subtitles, d.Value) case authorDirectiveName: song.Authors = append(song.Authors, d.Value) case keyDirectiveName: song.Key = songtools.Key(d.Value) default: // choruses and bridges have end tags, which means we can just wait until those show up // and not have to guess at the end of a section. if section != nil && numNewLines == 2 && section.Kind != "Chorus" && section.Kind != "Bridge" { section = nil } if section != nil { section.Nodes = append(section.Nodes, d) } else { song.Nodes = append(song.Nodes, d) } line = nil numNewLines = 0 } case chordToken: if section == nil { section = &songtools.Section{} song.Nodes = append(song.Nodes, section) } if line == nil { line = &songtools.Line{} section.Nodes = append(section.Nodes, line) } chord, ok := songtools.ParseChord(text) if !ok { return nil, fmt.Errorf("The text '%v' is not a chord.", text) } line.Chords = append(line.Chords, chord) line.ChordPositions = append(line.ChordPositions, len(line.Text)) case textToken: if section == nil { section = &songtools.Section{} song.Nodes = append(song.Nodes, section) } if line == nil { line = &songtools.Line{} section.Nodes = append(section.Nodes, line) } line.Text += text numNewLines = 0 case newLineToken: line = nil numNewLines++ if section != nil && numNewLines == 2 && section.Kind != "Chorus" && section.Kind != "Bridge" { section = nil numNewLines = 0 } break } token, text, err = p.scanner.next() if err != nil { return nil, err } } return song, nil }
func (cmd *options) execute(args []string) error { if len(args) > 1 { return fmt.Errorf("too many positional arguments") } var err error in := os.Stdin file := "" if len(args) == 1 { file = args[0] in, err = os.Open(file) if err != nil { return fmt.Errorf("unable to open %q: %v", file, err) } } inBytes, err := ioutil.ReadAll(in) if err != nil { return fmt.Errorf("unable to read: %v", err) } input := bytes.NewBuffer(inBytes) readFormat, err := findReadFormat(cmd.CurrentFormat, file, input) if err != nil { return fmt.Errorf("unable to find input format for %q: %v", file, err) } writeFormat := readFormat if cmd.ToFormat != "" { var ok bool if writeFormat, ok = format.ByName(cmd.ToFormat); !ok { return fmt.Errorf("unable to find output format %q", cmd.ToFormat) } } if writeFormat.Writer == nil { return fmt.Errorf("the input format %q is unable to be used for writing", readFormat.Name) } song, err := readFormat.Reader.Read(input) if err != nil { return fmt.Errorf("unable to parse %q: %v", file, err) } setSongTitleIfNecessary(file, song) if cmd.ToKey != "" { fromKey := songtools.Key(cmd.CurrentKey) if fromKey == "" && song.Key == "" { return fmt.Errorf("unable to get current key") } else if fromKey == "" { fromKey = song.Key } toKey := songtools.Key(cmd.ToKey) noteNames, interval, err := songtools.NoteNamesAndIntervalFromKeyToKey(fromKey, toKey) if err != nil { return fmt.Errorf("unable to get note names and interval: %v", err) } song, err = songtools.TransposeSong(song, interval, noteNames) if err != nil { return fmt.Errorf("unable to transpose from %q to %q: %v", fromKey, toKey, err) } song.Key = toKey } out := os.Stdout defer out.Close() if cmd.Out == "<unset>" { name := song.Title if name == "" && file == "" { return fmt.Errorf("'out' was specified, but the song does not have a title and the input was not a file") } else if name == "" { _, file := filepath.Split(file) ext := filepath.Ext(file) name = strings.TrimSuffix(file, ext) } if len(writeFormat.Extensions) > 0 { name += writeFormat.Extensions[0] } cmd.Out = name } if cmd.Out != "" { out, err = os.OpenFile(cmd.Out, os.O_CREATE, 0666) if err != nil { return fmt.Errorf("unable to open %q: %v", cmd.Out, err) } } return writeFormat.Writer.Write(out, song) }
func (p *parser) parseSong() (*songtools.Song, error) { song := &songtools.Song{} token, text, err := p.scanner.next() if err != nil { return nil, err } var section *songtools.Section var line *songtools.Line numNewLines := 0 for token != eofToken { switch token { case commentToken: comment := &songtools.Comment{ Text: text, Hidden: false, } if section != nil { section.Nodes = append(section.Nodes, comment) } else { song.Nodes = append(song.Nodes, comment) } case directiveToken: d, err := parseDirective(text) if err != nil { return nil, err } if numNewLines == 2 { section = nil } if section != nil { section.Nodes = append(section.Nodes, d) } else { switch d.Name { case titleDirectiveName: song.Title = d.Value case subtitleDirectiveName: song.Subtitles = append(song.Subtitles, d.Value) case authorDirectiveName: song.Authors = append(song.Authors, d.Value) case keyDirectiveName: song.Key = songtools.Key(d.Value) default: song.Nodes = append(song.Nodes, d) } } line = nil numNewLines = 0 case sectionToken: section = &songtools.Section{ Kind: songtools.SectionKind(text), } song.Nodes = append(song.Nodes, section) line = nil numNewLines = 0 case textToken: if section != nil && numNewLines == 0 { // we have text immediately following a section without a newline directive := &songtools.Comment{ Text: text, Hidden: false, } section.Nodes = append(section.Nodes, directive) break } if section == nil { section = &songtools.Section{} song.Nodes = append(song.Nodes, section) } chords, positions, isChordLine := parseTextForChords(text) if !isChordLine { if line != nil && line.Text == "" { line.Text = text } else { line = &songtools.Line{ Text: text, } section.Nodes = append(section.Nodes, line) } line = nil } else { line = &songtools.Line{ Chords: chords, ChordPositions: positions, } section.Nodes = append(section.Nodes, line) } numNewLines = 0 case newLineToken: numNewLines++ if numNewLines > 2 { section = nil line = nil numNewLines = 0 } break } token, text, err = p.scanner.next() if err != nil { return nil, err } } return song, nil }