gly 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gly.rb +4 -1
  3. data/lib/gly/cli.rb +71 -8
  4. data/lib/gly/document.rb +20 -8
  5. data/lib/gly/document_gabc_convertor.rb +10 -2
  6. data/lib/gly/exceptions.rb +16 -0
  7. data/lib/gly/exceptions.rb~ +3 -0
  8. data/lib/gly/gabc_convertor.rb +29 -11
  9. data/lib/gly/gly_convertor.rb +52 -0
  10. data/lib/gly/gly_convertor.rb~ +11 -0
  11. data/lib/gly/glyfy_cli.rb~ +92 -0
  12. data/lib/gly/headers.rb +3 -1
  13. data/lib/gly/lister.rb +41 -6
  14. data/lib/gly/lyrics.rb +7 -7
  15. data/lib/gly/markup.rb +15 -0
  16. data/lib/gly/markup.rb~ +11 -0
  17. data/lib/gly/parser.rb +65 -23
  18. data/lib/gly/preview_builder.rb +13 -4
  19. data/lib/gly/preview_generator.rb +44 -23
  20. data/lib/gly/{parsed_score.rb → score.rb} +1 -1
  21. data/lib/gly/templates/lualatex_document.tex +11 -11
  22. data/tests/examples.rb +27 -7
  23. data/tests/examples/gly/expected/block_lyrics.gabc +1 -1
  24. data/tests/examples/gly/expected/block_lyrics.gabc~ +2 -0
  25. data/tests/examples/gly/expected/block_music.gabc +2 -0
  26. data/tests/examples/gly/expected/block_music.gabc~ +4 -0
  27. data/tests/examples/gly/expected/blocks.gabc +3 -0
  28. data/tests/examples/gly/expected/differentia_final.gabc +2 -0
  29. data/tests/examples/gly/expected/empty_lyric_syllable.gabc +2 -0
  30. data/tests/examples/gly/expected/empty_lyric_syllable.gabc~ +1 -0
  31. data/tests/examples/gly/expected/explicit_lyrics.gabc +1 -1
  32. data/tests/examples/gly/expected/header_colon.gabc +3 -0
  33. data/tests/examples/gly/expected/header_colon.gabc~ +3 -0
  34. data/tests/examples/gly/expected/header_empty.gabc +2 -0
  35. data/tests/examples/gly/expected/header_empty.gabc~ +2 -0
  36. data/tests/examples/gly/expected/nabc.gabc +3 -0
  37. data/tests/examples/gly/expected/no_clef.gabc +2 -0
  38. data/tests/examples/gly/expected/no_clef.gabc~ +1 -0
  39. data/tests/examples/gly/expected/real1.gabc~ +15 -0
  40. data/tests/examples/gly/expected/unsingables.gabc +2 -0
  41. data/tests/examples/gly/expected/unsingables.gabc~ +2 -0
  42. data/tests/examples/gly/given/block_lyrics.gly~ +3 -0
  43. data/tests/examples/gly/given/block_music.gly +4 -0
  44. data/tests/examples/gly/given/blocks.gly +6 -0
  45. data/tests/examples/gly/given/differentia_final.gly +5 -0
  46. data/tests/examples/gly/given/differentia_final.gly~ +2 -0
  47. data/tests/examples/gly/given/empty_lyric_syllable.gly +2 -0
  48. data/tests/examples/gly/given/empty_lyric_syllable.gly~ +2 -0
  49. data/tests/examples/gly/given/header_colon.gly +3 -0
  50. data/tests/examples/gly/given/header_colon.gly~ +4 -0
  51. data/tests/examples/gly/given/header_empty.gly +1 -0
  52. data/tests/examples/gly/given/header_empty.gly~ +2 -0
  53. data/tests/examples/gly/given/nabc.gly +3 -0
  54. data/tests/examples/gly/given/nabc.gly~ +2 -0
  55. data/tests/examples/gly/given/no_clef.gly +2 -0
  56. data/tests/examples/gly/given/no_clef.gly~ +1 -0
  57. data/tests/examples/gly/given/real1.gly~ +18 -0
  58. data/tests/examples/gly/given/unsingables.gly +2 -0
  59. data/tests/examples/gly/given/unsingables.gly~ +2 -0
  60. data/tests/examples/glyfy/expected/header.gly +3 -0
  61. data/tests/examples/glyfy/expected/header.gly~ +2 -0
  62. data/tests/examples/glyfy/expected/simple.gly +3 -0
  63. data/tests/examples/glyfy/expected/simple.gly~ +2 -0
  64. data/tests/examples/glyfy/given/header.gabc +3 -0
  65. data/tests/examples/glyfy/given/simple.gabc +2 -0
  66. data/tests/examples/parser/markup_after_header.gly +4 -0
  67. data/tests/examples/parser/markups.gly +10 -0
  68. data/tests/examples/parser/markups.gly~ +5 -0
  69. data/tests/examples/parser/score_markup_order.gly +19 -0
  70. data/tests/examples/parser/score_markup_order.gly~ +13 -0
  71. data/tests/parser.rb +51 -0
  72. data/tests/parser.rb~ +12 -0
  73. data/tests/run.rb +1 -0
  74. data/tests/test_helper.rb +19 -0
  75. metadata +59 -10
  76. data/bin/glyfy +0 -4
  77. data/tests/examples.rb~ +0 -9
  78. data/tests/programmed_examples.rb~ +0 -14
  79. data/tests/run.rb~ +0 -3
  80. data/tests/string_helpers_test.rb~ +0 -19
@@ -32,6 +32,7 @@ initial-style
32
32
  centering-scheme
33
33
  user-notes
34
34
  annotation
35
+ nabc-lines
35
36
  )
36
37
 
37
38
  def initialize
@@ -47,7 +48,8 @@ annotation
47
48
  @pairs << [key, value]
48
49
  end
49
50
 
50
- def_delegator :@headers, :[]
51
+ def_delegators :@headers, :[], :size, :keys, :values,
52
+ :has_key?, :includes?, :include?
51
53
  def_delegator :@pairs, :empty?
52
54
 
53
55
  # some header fields may appear more than once;
@@ -1,8 +1,9 @@
1
1
  module Gly
2
2
  class Lister
3
- def initialize(files)
3
+ def initialize(files, format=nil)
4
4
  @files = files
5
5
  @error = false
6
+ @format = find_formatter(format || :grep)
6
7
  end
7
8
 
8
9
  def list(io, errio)
@@ -15,12 +16,9 @@ module Gly
15
16
  next
16
17
  end
17
18
 
18
- io.puts
19
- io.puts "== #{f}"
20
-
19
+ io.puts @format.file(f)
21
20
  document.scores.each do |s|
22
- l = s.lyrics.readable
23
- io.puts l unless l.empty?
21
+ io.puts @format.score(s)
24
22
  end
25
23
  end
26
24
  end
@@ -28,5 +26,42 @@ module Gly
28
26
  def error?
29
27
  @error
30
28
  end
29
+
30
+ private
31
+
32
+ def find_formatter(name)
33
+ formatter_name = name.to_s.capitalize + 'Format'
34
+ begin
35
+ klass = self.class.const_get(formatter_name)
36
+ rescue NameError
37
+ raise Gly::Exception.new("Invalid list format '#{name}'")
38
+ end
39
+
40
+ klass.new
41
+ end
42
+
43
+ # simple, sort of beautiful, easy to read for a human
44
+ class OverviewFormat
45
+ def file(f)
46
+ "\n== #{f}"
47
+ end
48
+
49
+ def score(s)
50
+ l = s.lyrics.readable
51
+ l unless l.empty?
52
+ end
53
+ end
54
+
55
+ # less beauty, more repetition, for easier grepping
56
+ class GrepFormat
57
+ def file(f)
58
+ @file = f
59
+ nil
60
+ end
61
+
62
+ def score(s)
63
+ "#{@file}##{s.headers['id']} #{s.lyrics.readable}"
64
+ end
65
+ end
31
66
  end
32
67
  end
@@ -11,13 +11,13 @@ module Gly
11
11
  def each_syllable
12
12
  return enum_for(:each_syllable) unless block_given?
13
13
 
14
- @words.each do |w|
15
- w.each_syllable.each_with_index do |s,si|
16
- if si == 0
17
- yield ' ' + s
18
- else
19
- yield s
20
- end
14
+ @words.each_with_index do |w,wi|
15
+ w.each_syllable.each do |s|
16
+ yield s
17
+ end
18
+
19
+ if (wi + 1) < @words.size
20
+ yield ' '
21
21
  end
22
22
  end
23
23
  end
@@ -0,0 +1,15 @@
1
+ module Gly
2
+ class Markup
3
+ def initialize(text='')
4
+ @text = text
5
+ end
6
+
7
+ def text
8
+ @text.strip
9
+ end
10
+
11
+ def <<(line)
12
+ @text += line
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Gly
2
+ class Markup
3
+ def initialize(text='')
4
+ @text = text
5
+ end
6
+
7
+ def <<(line)
8
+ @text += "\n" + line
9
+ end
10
+ end
11
+ end
@@ -5,17 +5,12 @@ module Gly
5
5
  class Parser
6
6
  def initialize(syllable_separator=nil)
7
7
  @syllable_separator = syllable_separator || '--'
8
+ @current_block = :score
8
9
  end
9
10
 
10
11
  def parse(source)
11
12
  if source.is_a? String
12
- if File.file? source
13
- parse_fname source
14
- elsif source == '-'
15
- parse_io STDIN
16
- else
17
- parse_str source
18
- end
13
+ parse_fname source
19
14
  else
20
15
  parse_io source
21
16
  end
@@ -33,7 +28,7 @@ module Gly
33
28
 
34
29
  def parse_io(io)
35
30
  @doc = Document.new
36
- @score = ParsedScore.new
31
+ @score = Score.new
37
32
 
38
33
  if io.respond_to? :path
39
34
  @doc.path = io.path
@@ -42,24 +37,40 @@ module Gly
42
37
  io.each do |line|
43
38
  line = strip_comments(line)
44
39
 
45
- if empty? line
40
+ if empty?(line) && @current_block != :markup
46
41
  next
42
+ # keywords specifying line or block type
47
43
  elsif new_score? line
48
44
  push_score
49
- @score = ParsedScore.new
45
+ @score = Score.new
46
+ @current_block = :score
50
47
  elsif header_start? line
51
48
  push_score
52
49
  @score = @doc.header
53
- elsif header_line? line
54
- parse_header line
50
+ @current_block = :header
51
+ elsif markup_start? line
52
+ push_score
53
+ @doc.content << Markup.new
54
+ @current_block = :markup
55
+ elsif block_start? line
56
+ @current_block = line.match(/\w+/)[0].to_sym
55
57
  elsif explicit_lyrics? line
56
58
  parse_lyrics line
57
59
  elsif explicit_music? line
58
60
  parse_music line
61
+ elsif explicit_markup? line
62
+ push_score
63
+ parse_markup line
64
+ # line in a typed block
65
+ elsif @current_block != :score
66
+ parse_default line
67
+ # content type autodetection
68
+ elsif header_line? line
69
+ parse_header line
59
70
  elsif lyrics_line? line
60
71
  parse_lyrics line
61
72
  else
62
- parse_music line
73
+ parse_default line
63
74
  end
64
75
  end
65
76
 
@@ -75,11 +86,19 @@ module Gly
75
86
  end
76
87
 
77
88
  def new_score?(str)
78
- str =~ /\A\s*\\score/
89
+ str =~ /\A\s*\\(score)\s*\Z/
79
90
  end
80
91
 
81
92
  def header_start?(str)
82
- str =~ /\A\s*\\header/
93
+ str =~ /\A\s*\\(header)\s*\Z/
94
+ end
95
+
96
+ def markup_start?(str)
97
+ str =~ /\A\s*\\(markup)\s*\Z/
98
+ end
99
+
100
+ def block_start?(str)
101
+ str =~ /\A\s*\\(lyrics|music)\s*\Z/
83
102
  end
84
103
 
85
104
  def strip_comments(str)
@@ -87,7 +106,7 @@ module Gly
87
106
  end
88
107
 
89
108
  def header_line?(str)
90
- in_header_block? || @score.lyrics.empty? && @score.music.empty? && str =~ /\w+:\s*./
109
+ @current_block == :score && @score.lyrics.empty? && @score.music.empty? && str =~ /\A[\w_-]+:/
91
110
  end
92
111
 
93
112
  EXPLICIT_LYRICS_RE = /\A\\l(yrics)?\s+/
@@ -102,12 +121,14 @@ module Gly
102
121
  str =~ EXPLICIT_MUSIC_RE
103
122
  end
104
123
 
105
- def lyrics_line?(str)
106
- !contains_square_brackets?(str) && (str.include?(@syllable_separator) || contains_unmusical_letters?(str))
124
+ EXPLICIT_MARKUP_RE = /\A\\markup\s*/
125
+
126
+ def explicit_markup?(str)
127
+ str =~ EXPLICIT_MARKUP_RE
107
128
  end
108
129
 
109
- def in_header_block?
110
- @score.is_a? Headers
130
+ def lyrics_line?(str)
131
+ !contains_square_brackets?(str) && (str.include?(@syllable_separator) || contains_unmusical_letters?(str))
111
132
  end
112
133
 
113
134
  def contains_unmusical_letters?(str)
@@ -120,7 +141,7 @@ module Gly
120
141
  end
121
142
 
122
143
  def parse_header(str)
123
- hid, hvalue = str.split(':').collect(&:strip)
144
+ hid, hvalue = str.split(':', 2).collect(&:strip)
124
145
  @score.headers[hid] = hvalue
125
146
  end
126
147
 
@@ -144,10 +165,31 @@ module Gly
144
165
  end
145
166
  end
146
167
 
168
+ def parse_markup(line)
169
+ if line =~ EXPLICIT_MARKUP_RE
170
+ @doc << Markup.new(line.sub(EXPLICIT_MARKUP_RE, ''))
171
+ else
172
+ @doc.content.last << line
173
+ end
174
+ end
175
+
176
+ def parse_default(line)
177
+ if @current_block == :score
178
+ return parse_music line
179
+ end
180
+
181
+ send "parse_#{@current_block}", line
182
+ end
183
+
147
184
  def push_score
148
- if @score.is_a?(ParsedScore) && !@score.empty?
149
- @doc << @score
185
+ if @score.is_a?(Score) && !@score.empty?
186
+ begin
187
+ @doc << @score
188
+ rescue ArgumentError => ex
189
+ raise ParseError.wrap ex
190
+ end
150
191
  end
192
+ @score = nil
151
193
  end
152
194
  end
153
195
  end
@@ -15,10 +15,11 @@ module Gly
15
15
 
16
16
  def build
17
17
  @gabcs.each do |g|
18
- exec 'gregorio', g
18
+ outfile = g.sub /(\.gabc)?$/i, '.gtex'
19
+ benevolent_exec('gregorio', '-o', outfile, g)
19
20
  end
20
21
 
21
- exec 'lualatex', @main_tex
22
+ exec 'lualatex', '--interaction=nonstopmode', @main_tex
22
23
  end
23
24
 
24
25
  private
@@ -28,11 +29,19 @@ module Gly
28
29
  unless ok
29
30
  case $?.exitstatus
30
31
  when 127
31
- STDERR.puts "'#{progname}' is required for this gly command to work, but it was not found. Please, ensure that '#{progname}' is installed in one of the directories listed in your PATH and try again."
32
+ raise Gly::Exception.new "'#{progname}' is required for this gly command to work, but it was not found. Please, ensure that '#{progname}' is installed in one of the directories listed in your PATH and try again."
32
33
  else
33
- STDERR.puts "'#{progname}' exited with exit code #{$?.to_i}. Let's continue and see what happens ..."
34
+ raise Gly::Exception.new "'#{progname}' exited with exit code #{$?.to_i}."
34
35
  end
35
36
  end
36
37
  end
38
+
39
+ def benevolent_exec(progname, *args)
40
+ begin
41
+ exec progname, *args
42
+ rescue Gly::Exception => err
43
+ STDERR.puts err.message + " Let's continue and see what happens ..."
44
+ end
45
+ end
37
46
  end
38
47
  end
@@ -27,37 +27,25 @@ module Gly
27
27
  fw.puts header_table document.header
28
28
  end
29
29
 
30
- convertor.each_score_with_gabcname do |score, gabc_fname|
31
- @builder.add_gabc gabc_fname
32
-
33
- if @options[:full_headers]
34
- fw.puts header_table score.headers
35
- end
36
-
37
- gtex_fname = gabc_fname.sub /\.gabc/i, ''
38
- piece_title = %w(book manuscript arranger author).collect do |m| score.headers[m]
39
- end.delete_if(&:nil?).join ', '
40
- fw.puts "\\commentary{\\footnotesize{#{piece_title}}}\n" unless piece_title.empty?
41
-
42
- annotations = score.headers.each_value('annotation')
43
- begin
44
- fw.puts "\\setfirstannotation{#{annotations.next}}"
45
- fw.puts "\\setsecondannotation{#{annotations.next}}"
46
- rescue StopIteration
47
- # ok, no more annotations
30
+ scores_with_names = convertor.each_score_with_gabcname
31
+
32
+ document.content.each do |c|
33
+ if c.is_a? Markup
34
+ fw.puts render_markup(c)
35
+ else
36
+ score, gabc_fname, gtex_fname = scores_with_names.next
37
+ fw.puts render_score(score, gabc_fname, gtex_fname)
48
38
  end
49
-
50
- fw.puts "\\includescore{#{gtex_fname}}\n\\vspace{1cm}"
51
39
  end
52
40
 
53
41
  if @options['no_document']
54
42
  tex = doc_body.string
55
43
  else
56
44
  replacements = {
57
- glyvars: header_variables(document.header),
58
- body: doc_body.string
45
+ 'glyvars' => header_variables(document.header),
46
+ 'body' => doc_body.string
59
47
  }
60
- tex = @template % replacements
48
+ tex = @template.gsub(/\{\{(\w+)\}\}/) {|m| replacements[m[2..-3]] }
61
49
  end
62
50
 
63
51
  with_preview_io(document.path) do |fw|
@@ -74,6 +62,39 @@ module Gly
74
62
 
75
63
  private
76
64
 
65
+ def render_markup(markup)
66
+ markup.text
67
+ end
68
+
69
+ def render_score(score, gabc_fname, gtex_fname)
70
+ r = StringIO.new
71
+
72
+ @builder.add_gabc gabc_fname
73
+
74
+ if @options[:full_headers]
75
+ r.puts header_table score.headers
76
+ end
77
+
78
+ piece_title = %w(id book manuscript arranger author).collect do |m|
79
+ val = score.headers[m]
80
+ val = "\\texttt{\\##{val}}" if val && m == 'id'
81
+ val
82
+ end.delete_if(&:nil?).join ', '
83
+ r.puts "\\commentary{\\footnotesize{#{piece_title}}}\n" unless piece_title.empty?
84
+
85
+ annotations = score.headers.each_value('annotation')
86
+ begin
87
+ r.puts "\\setfirstannotation{#{annotations.next}}"
88
+ r.puts "\\setsecondannotation{#{annotations.next}}"
89
+ rescue StopIteration
90
+ # ok, no more annotations
91
+ end
92
+
93
+ r.puts "\\includescore{#{gtex_fname}}\n\\vspace{1cm}"
94
+
95
+ r.string
96
+ end
97
+
77
98
  def with_preview_io(src_name)
78
99
  if @preview_dest
79
100
  yield @preview_dest
@@ -1,6 +1,6 @@
1
1
  module Gly
2
2
  # result of a gly score parsing
3
- class ParsedScore
3
+ class Score
4
4
  def initialize
5
5
  @headers = Headers.new
6
6
  @lyrics = Lyrics.new
@@ -1,4 +1,4 @@
1
- %% LuaLaTeX
1
+ % LuaLaTeX
2
2
 
3
3
  \documentclass[a4paper, 12pt]{article}
4
4
  \usepackage[latin]{babel}
@@ -8,19 +8,19 @@
8
8
  \usepackage[hidelinks]{hyperref}
9
9
  \usepackage{etoolbox}
10
10
 
11
- %% for gregorio
11
+ % for gregorio
12
12
  \usepackage{luatextra}
13
13
  \usepackage{graphicx}
14
14
  \usepackage{gregoriotex}
15
15
 
16
- %{glyvars}
16
+ {{glyvars}}
17
17
 
18
- %% header fields like title, transcription-date etc.
19
- %% are translated by 'gly preview' to latex commands like
20
- %% \glyTitle \glyTranscriptionDate etc.
21
- %% (All of your document header fields are handled this way.)
22
- %% These commands can be used for various purposes,
23
- %% but we will use them only to assemble document title:
18
+ % header fields like title, transcription-date etc.
19
+ % are translated by 'gly preview' to latex commands like
20
+ % \glyTitle \glyTranscriptionDate etc.
21
+ % (All of your document header fields are handled this way.)
22
+ % These commands can be used for various purposes,
23
+ % but we will use them only to assemble document title:
24
24
  \newcommand{\glyPreviewTitle}{
25
25
  \begin{center}
26
26
  \ifdef{\glyTitle}{{\huge \glyTitle}}{}
@@ -43,9 +43,9 @@
43
43
 
44
44
  \glyPreviewTitle
45
45
 
46
- %{body}
46
+ {{body}}
47
47
 
48
- %% tagline
48
+ % tagline
49
49
  \vfill
50
50
  \glyPreviewTagline
51
51
  \end{document}