11# frozen_string_literal: true
22
3- require ' chord_pro/version'
4- require ' chord_pro/song'
5- require ' chord_pro/section'
6- require ' chord_pro/line'
7- require ' chord_pro/part'
8- require ' chord_pro/measure'
3+ require " chord_pro/version"
4+ require " chord_pro/song"
5+ require " chord_pro/section"
6+ require " chord_pro/line"
7+ require " chord_pro/part"
8+ require " chord_pro/measure"
99
1010module ChordPro
1111 SECTION_REGEX = /\{ (?:chorus|soc|sov|start_of_verse|start_of_chorus|sob|start_of_bridge|start_of_tab|sot|start_of_grid|sog):?\n ?(.*)\} /m
@@ -18,121 +18,122 @@ module ChordPro
1818 COMMENT_REGEX = /\{ (?:c|comment|comment_italic|ci|comment_box|cb):([^$]*)\} /
1919 SANITIZE_REGEX = /\{ end_of_chorus|eoc|end_of_verse|eov|end_of_tab|eot|end_of_tab|eog|end_of_grid|colb\} /
2020
21- def self . parse ( lines )
22- song = Song . new
23- current_section = nil
24-
25- lines . split ( "\n " ) . each do |text |
26- if text . start_with? ( '{meta:' )
27- process_custom_attribute ( song , text )
28- elsif section_start? ( text )
29- current_section = process_section ( song , text )
30- elsif !comment_starts? ( text ) && attribute_start? ( text )
31- process_attribute ( song , text )
32- elsif text . match ( SANITIZE_REGEX )
33- # ignore
34- else
35- process_lyrics_and_chords ( song , current_section , text )
21+ class << self
22+ def parse ( lines )
23+ song = Song . new
24+ current_section = nil
25+
26+ lines . split ( "\n " ) . each do |text |
27+ if text . start_with? ( "{meta:" )
28+ process_custom_attribute ( song , text )
29+ elsif section_start? ( text )
30+ current_section = process_section ( song , text )
31+ elsif !comment_starts? ( text ) && attribute_start? ( text )
32+ process_attribute ( song , text )
33+ elsif text . match ( SANITIZE_REGEX )
34+ # ignore
35+ else
36+ process_lyrics_and_chords ( song , current_section , text )
37+ end
3638 end
39+
40+ song
3741 end
3842
39- song
40- end
43+ def process_section ( song , text )
44+ matches = SECTION_REGEX . match ( text )
45+ name = ( !matches [ 1 ] . empty? ) ? matches [ 1 ] . strip : section_name_by_directive ( text )
4146
42- def self . process_section ( song , text )
43- matches = SECTION_REGEX . match ( text )
44- name = !matches [ 1 ] . empty? ? matches [ 1 ] . strip : section_name_by_directive ( text )
47+ current_section = Section . new ( name : name )
48+ song . sections << current_section
4549
46- current_section = Section . new ( name : name )
47- song . sections << current_section
50+ current_section
51+ end
4852
49- current_section
50- end
53+ def process_attribute ( song , text )
54+ matches = ATTRIBUTE_REGEX . match ( text )
55+ key = matches [ 1 ]
56+ value = matches [ 2 ] . strip
57+
58+ if song . respond_to? ( :"#{ key } =" )
59+ song . send ( :"#{ key } =" , value )
60+ else
61+ puts "WARNING: Unknown attribute '#{ key } '"
62+ end
63+ end
5164
52- def self . process_attribute ( song , text )
53- matches = ATTRIBUTE_REGEX . match ( text )
54- key = matches [ 1 ]
55- value = matches [ 2 ] . strip
65+ def process_custom_attribute ( song , text )
66+ matches = CUSTOM_ATTRIBUTE_REGEX . match ( text )
67+ key = matches [ 1 ]
68+ value = matches [ 2 ] . strip
5669
57- if song . respond_to? ( "#{ key } =" . to_sym )
58- song . send ( "#{ key } =" , value )
59- else
60- puts "WARNING: Unknown attribute '#{ key } '"
70+ song . set_custom ( key , value )
6171 end
62- end
6372
64- def self . process_custom_attribute ( song , text )
65- matches = CUSTOM_ATTRIBUTE_REGEX . match ( text )
66- key = matches [ 1 ]
67- value = matches [ 2 ] . strip
73+ def process_lyrics_and_chords ( song , current_section , text )
74+ return if text == ""
6875
69- song . set_custom ( key , value )
70- end
76+ if current_section . nil?
77+ current_section = Section . new ( name : "" )
78+ song . sections << current_section
79+ end
7180
72- def self . process_lyrics_and_chords ( song , current_section , text )
73- return if text == ''
81+ line = Line . new
7482
75- if current_section . nil?
76- current_section = Section . new ( name : '' )
77- song . sections << current_section
78- end
83+ if text . start_with? ( "|-" )
84+ line . tablature = text
85+ elsif text . start_with? ( "| " )
86+ captures = text . scan ( MEASURES_REGEX ) . flatten
7987
80- line = Line . new
88+ measures = [ ]
8189
82- if text . start_with? ( '|-' )
83- line . tablature = text
84- elsif text . start_with? ( '| ' )
85- captures = text . scan ( MEASURES_REGEX ) . flatten
90+ captures . each do |capture |
91+ chords = capture . scan ( CHORDS_REGEX ) . flatten
92+ measure = Measure . new
93+ measure . chords = chords
94+ measures << measure
95+ end
8696
87- measures = [ ]
97+ line . measures = measures
98+ elsif comment_starts? ( text )
99+ matches = COMMENT_REGEX . match ( text )
100+ comment = matches [ 1 ] . strip
101+ line . comment = comment
102+ else
103+ captures = text . scan ( CHORDS_AND_LYRICS_REGEX ) . flatten
88104
89- captures . each do |capture |
90- chords = capture . scan ( CHORDS_REGEX ) . flatten
91- measure = Measure . new
92- measure . chords = chords
93- measures << measure
94- end
105+ captures . each_slice ( 2 ) do |pair |
106+ part = Part . new
107+ chord = pair [ 0 ] &.strip || ""
108+ part . chord = chord . delete ( "[" ) . delete ( "]" )
109+ part . lyric = pair [ 1 ] || ""
95110
96- line . measures = measures
97- elsif comment_starts? ( text )
98- matches = COMMENT_REGEX . match ( text )
99- comment = matches [ 1 ] . strip
100- line . comment = comment
101- else
102- captures = text . scan ( CHORDS_AND_LYRICS_REGEX ) . flatten
103-
104- captures . each_slice ( 2 ) do |pair |
105- part = Part . new
106- chord = pair [ 0 ] &.strip || ''
107- part . chord = chord . delete ( '[' ) . delete ( ']' )
108- part . lyric = pair [ 1 ] || ''
109-
110- line . parts << part unless ( part . chord == '' ) && ( part . lyric == '' )
111+ line . parts << part unless ( part . chord == "" ) && ( part . lyric == "" )
112+ end
111113 end
112- end
113114
114- current_section . lines << line unless line . empty?
115- end
115+ current_section . lines << line unless line . empty?
116+ end
116117
117- private
118+ private
118119
119- def self . attribute_start? ( text )
120- text . match ( /\{ (.+):(.*)\} / )
121- end
120+ def attribute_start? ( text )
121+ text . match ( /\{ (.+):(.*)\} / )
122+ end
122123
123- def self . section_start? ( text )
124- text . match ( SECTION_REGEX )
125- end
124+ def section_start? ( text )
125+ text . match ( SECTION_REGEX )
126+ end
126127
127- def self . comment_starts? ( text )
128- text . match ( /\{ (?:c|comment|comment_italic|ci|comment_box|cb):/ )
129- end
128+ def comment_starts? ( text )
129+ text . match ( /\{ (?:c|comment|comment_italic|ci|comment_box|cb):/ )
130+ end
130131
131- def self . section_name_by_directive ( text )
132- return 'Chorus' if text . match ( /soc|start_of_chorus|chorus/ )
133- return 'Verse' if text . match ( /sov|start_of_verse/ )
134- return 'Tab' if text . match ( /sot|start_of_tab/ )
135- return 'Grid' if text . match ( /sot|start_of_grid/ )
132+ def section_name_by_directive ( text )
133+ return "Chorus" if /soc|start_of_chorus|chorus/ . match? ( text )
134+ return "Verse" if /sov|start_of_verse/ . match? ( text )
135+ return "Tab" if /sot|start_of_tab/ . match? ( text )
136+ "Grid" if /sot|start_of_grid/ . match? ( text )
137+ end
136138 end
137-
138139end
0 commit comments