Class: HeadMusic::Notation::ABC::BookParser

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/abc/book_parser.rb

Overview

Interprets an ABC tune book — one or more tunes separated by blank lines, each beginning with an X: field — as an array of compositions.

Parsing is all-or-nothing: any invalid tune raises, so callers never receive a partial array. Each tune is delegated to Parser with its book-relative start line, so errors point into the book.

Defined Under Namespace

Classes: Segment

Instance Method Summary collapse

Constructor Details

#initialize(book_string) ⇒ BookParser

Returns a new instance of BookParser.



10
11
12
# File 'lib/head_music/notation/abc/book_parser.rb', line 10

def initialize(book_string)
  @book_string = book_string.to_s
end

Instance Method Details

#build_segment(lines, start_line) ⇒ Object (private)



63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/head_music/notation/abc/book_parser.rb', line 63

def build_segment(lines, start_line)
  first_content_index = lines.find_index { |line| !line.strip.start_with?("%") }
  return nil unless first_content_index

  unless lines[first_content_index].strip.start_with?("X:")
    raise HeadMusic::Notation::ABC::ParseError.new(
      "Expected a tune to begin with an X: field",
      line_number: start_line + first_content_index,
      snippet: lines[first_content_index].strip[0, HeadMusic::Notation::ABC::BodyLexer::SNIPPET_LENGTH]
    )
  end
  Segment.new(text: lines.join, start_line: start_line)
end

#compositionsObject



14
15
16
# File 'lib/head_music/notation/abc/book_parser.rb', line 14

def compositions
  @compositions ||= parse_segments
end

#ensure_input_presentObject (private)



29
30
31
32
33
# File 'lib/head_music/notation/abc/book_parser.rb', line 29

def ensure_input_present
  return unless @book_string.strip.empty?

  raise HeadMusic::Notation::ABC::ParseError, "ABC input is blank"
end

#ensure_tunes_found(segments) ⇒ Object (private)



35
36
37
38
39
# File 'lib/head_music/notation/abc/book_parser.rb', line 35

def ensure_tunes_found(segments)
  return if segments.any?

  raise HeadMusic::Notation::ABC::ParseError, "No tunes found in ABC input"
end

#parse_segmentsObject (private)



20
21
22
23
24
25
26
27
# File 'lib/head_music/notation/abc/book_parser.rb', line 20

def parse_segments
  ensure_input_present
  segments = split_into_segments
  ensure_tunes_found(segments)
  segments.map do |segment|
    HeadMusic::Notation::ABC::Parser.new(segment.text, start_line: segment.start_line).composition
  end
end

#split_into_segmentsObject (private)

Groups lines into blank-line-separated paragraphs, dropping paragraphs that contain only comments.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/head_music/notation/abc/book_parser.rb', line 43

def split_into_segments
  segments = []
  current_lines = nil
  current_start = nil
  @book_string.lines.each_with_index do |line, index|
    if line.strip.empty?
      segments << build_segment(current_lines, current_start) if current_lines
      current_lines = nil
      next
    end
    if current_lines.nil?
      current_lines = []
      current_start = index + 1
    end
    current_lines << line
  end
  segments << build_segment(current_lines, current_start) if current_lines
  segments.compact
end