gly 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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}