lydown 0.7.2 → 0.9.0

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -14
  3. data/bin/lydown +1 -122
  4. data/lib/lydown.rb +3 -3
  5. data/lib/lydown/cli.rb +4 -0
  6. data/lib/lydown/cli/commands.rb +98 -0
  7. data/lib/lydown/cli/compiler.rb +11 -14
  8. data/lib/lydown/cli/diff.rb +3 -3
  9. data/lib/lydown/cli/output.rb +18 -0
  10. data/lib/lydown/cli/proofing.rb +8 -6
  11. data/lib/lydown/cli/support.rb +34 -0
  12. data/lib/lydown/cli/translation.rb +73 -0
  13. data/lib/lydown/core_ext.rb +1 -1
  14. data/lib/lydown/lilypond.rb +49 -11
  15. data/lib/lydown/parsing.rb +37 -6
  16. data/lib/lydown/parsing/nodes.rb +57 -56
  17. data/lib/lydown/rendering.rb +0 -9
  18. data/lib/lydown/rendering/base.rb +2 -2
  19. data/lib/lydown/rendering/command.rb +2 -2
  20. data/lib/lydown/rendering/comments.rb +1 -1
  21. data/lib/lydown/rendering/figures.rb +14 -14
  22. data/lib/lydown/rendering/lib.ly +22 -0
  23. data/lib/lydown/rendering/lyrics.rb +2 -2
  24. data/lib/lydown/rendering/movement.rb +2 -2
  25. data/lib/lydown/rendering/music.rb +46 -46
  26. data/lib/lydown/rendering/notes.rb +149 -70
  27. data/lib/lydown/rendering/settings.rb +21 -16
  28. data/lib/lydown/rendering/skipping.rb +1 -1
  29. data/lib/lydown/rendering/source_ref.rb +4 -4
  30. data/lib/lydown/rendering/staff.rb +8 -4
  31. data/lib/lydown/rendering/voices.rb +9 -9
  32. data/lib/lydown/templates.rb +9 -0
  33. data/lib/lydown/templates/lilypond_doc.erb +1 -1
  34. data/lib/lydown/templates/movement.erb +9 -1
  35. data/lib/lydown/templates/part.erb +12 -3
  36. data/lib/lydown/translation.rb +17 -0
  37. data/lib/lydown/translation/ripple.rb +30 -0
  38. data/lib/lydown/translation/ripple/nodes.rb +191 -0
  39. data/lib/lydown/translation/ripple/ripple.treetop +100 -0
  40. data/lib/lydown/version.rb +1 -1
  41. data/lib/lydown/work.rb +144 -198
  42. data/lib/lydown/work_context.rb +152 -0
  43. metadata +11 -2
@@ -0,0 +1,34 @@
1
+ module Lydown::CLI::Support
2
+ def self.copy_options(options)
3
+ opts = {}.deep!
4
+
5
+ options.each {|k, v| opts[k.to_sym] = v}
6
+ opts
7
+ end
8
+
9
+ def self.detect_filename(options)
10
+ source = ''
11
+ if options[:path] == '-'
12
+ # read source from stdin
13
+ options[:source] = STDIN.read
14
+
15
+ # the output defaults to a file named lydown expect if the format is ly.
16
+ # In that case the output will be sent to STDOUT.
17
+ options[:output_filename] ||= 'lydown' unless options[:format] == 'ly'
18
+ else
19
+ options[:source_filename] = options[:path]
20
+ if (options[:path] !~ /\.ld$/) and File.file?(options[:path] + ".ld")
21
+ options[:path] += ".ld"
22
+ end
23
+
24
+ unless options[:output_filename]
25
+ if options[:path] == '.'
26
+ options[:output_filename] = File.basename(FileUtils.pwd)
27
+ else
28
+ options[:output_filename] = (options[:path] =~ /^(.+)\.ld$/) ?
29
+ $1 : options[:path]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,73 @@
1
+ require 'yaml'
2
+
3
+ module Lydown::CLI::Translation
4
+ class << self
5
+ def process(opts)
6
+ if File.directory?(opts[:path])
7
+ process_directory(opts[:path])
8
+ elsif File.file?(opts[:path])
9
+ process_file(opts[:path])
10
+ elsif File.file?(opts[:path] + '.rpl')
11
+ process_file(opts[:path] + '.rpl')
12
+ end
13
+ end
14
+
15
+ def process_directory(path)
16
+ macros = load_macros(path)
17
+ Dir["#{path}/*"].entries.each do |entry|
18
+ if File.file?(entry) && (entry =~ /\.rpl$/)
19
+ process_file(entry)
20
+ elsif File.directory?(entry)
21
+ process_directory(entry)
22
+ end
23
+ end
24
+ end
25
+
26
+ def process_file(path)
27
+ ext = File.extname(path)
28
+ base_path = ext == '.rpl' ? path.gsub(/#{ext}$/, '') : path
29
+ output_path = base_path + '.ld'
30
+ lyrics_path = base_path + '.lyr'
31
+ figures_path = base_path + '.fig'
32
+
33
+ $stderr.puts "Translating #{path}..."
34
+
35
+ code = {
36
+ path: path,
37
+ ripple: IO.read(path),
38
+ lyrics: File.file?(lyrics_path) && IO.read(lyrics_path),
39
+ figures: File.file?(figures_path) && IO.read(figures_path),
40
+ macros: macros_for_path(path)
41
+ }
42
+
43
+ ld_code = Lydown::Translation.process(code)
44
+
45
+ File.open(output_path, 'w+') {|f| f.write(ld_code)}
46
+ end
47
+
48
+ WORK_FILENAME = '_work.yml'
49
+ MOVEMENT_FILENAME = '_movement.yml'
50
+
51
+ PATH_MACROS = {}
52
+
53
+ def macros_for_path(path)
54
+ PATH_MACROS[path] ||= load_macros(path)
55
+ end
56
+
57
+ def load_macros(path)
58
+ base_dir = File.directory?(path) ? path : File.dirname(path)
59
+ parent_dir = File.expand_path(File.join(base_dir, '..'))
60
+
61
+ yml =
62
+ (YAML.load_file(File.join(parent_dir, WORK_FILENAME)) rescue nil) ||
63
+ (YAML.load_file(File.join(base_dir, WORK_FILENAME)) rescue nil) ||
64
+ {}
65
+ macros = yml['macros'] || {}
66
+
67
+ yml =
68
+ (YAML.load_file(File.join(base_dir, MOVEMENT_FILENAME)) rescue nil) ||
69
+ {}
70
+ macros.merge(yml['macros'] || {})
71
+ end
72
+ end
73
+ end
@@ -62,7 +62,7 @@ class Hash
62
62
  if @deep && k.is_a?(String) && k =~ /\//
63
63
  lookup(k)
64
64
  elsif @deep && k.is_a?(Symbol)
65
- old_get(k.to_s)
65
+ old_get(k) || old_get(k.to_s)
66
66
  else
67
67
  old_get(k)
68
68
  end
@@ -1,6 +1,9 @@
1
1
  require 'lydown/errors'
2
+ require 'lydown/cli/output'
3
+
2
4
  require 'tempfile'
3
5
  require 'fileutils'
6
+ require 'open3'
4
7
 
5
8
  module Lydown
6
9
  module Lilypond
@@ -28,8 +31,9 @@ module Lydown
28
31
  copy_pages(tmp_target, target, ext)
29
32
  end
30
33
  rescue => e
31
- puts e.message
32
- p e.backtrace
34
+ $stderr.puts e.message
35
+ $stderr.puts e.backtrace.join("\n")
36
+ raise e
33
37
  end
34
38
 
35
39
  def copy_pages(source, target, ext)
@@ -46,25 +50,59 @@ module Lydown
46
50
  end
47
51
 
48
52
  def invoke(source, opts = {})
53
+ format = opts[:format]
54
+ format = nil if format == 'midi'
55
+
49
56
  # Run lilypond, pipe source into its STDIN, and capture its STDERR
50
- cmd = 'lilypond -lERROR '
57
+ cmd = 'lilypond '
51
58
  cmd << "-o #{opts[:output_filename]} "
52
59
  cmd << "-dno-point-and-click "
53
- cmd << "--#{opts[:format]} " if opts[:format]
54
- cmd << '-s - 2>&1'
60
+ cmd << "--#{opts[:format]} " if format
61
+ cmd << ' - '
55
62
 
56
63
  err_info = ''
57
- IO.popen(cmd, 'r+') do |f|
58
- f.puts source
59
- f.close_write
60
- err_info = f.read
61
- f.close
64
+ success = false
65
+ Open3.popen2e(cmd) do |input, output, wait_thr|
66
+ input.puts source
67
+ input.close_write
68
+ err_info = read_lilypond_progress(output, opts)
69
+ output.close
70
+ success = wait_thr.value == 0
62
71
  end
63
- unless $?.success?
72
+ unless success
64
73
  err_info = err_info.lines[0, 3].join
65
74
  raise LydownError, "Lilypond compilation failed:\n#{err_info}"
66
75
  end
67
76
  end
77
+
78
+ LILYPOND_STATUS_LINES = %w{
79
+ Processing
80
+ Parsing
81
+ Interpreting
82
+ Preprocessing
83
+ Finding
84
+ Fitting
85
+ Drawing
86
+ Layout
87
+ Converting
88
+ Success:
89
+ }
90
+ STATUS_TOTAL = LILYPOND_STATUS_LINES.size
91
+
92
+ def read_lilypond_progress(f, opts)
93
+ info = ''
94
+ Lydown::CLI::show_progress('Compile', STATUS_TOTAL) do |bar|
95
+ while !f.eof?
96
+ line = f.gets
97
+ info += line
98
+ if line =~ /^([^\s]+)/
99
+ idx = LILYPOND_STATUS_LINES.index($1)
100
+ bar.progress = idx + 1 if idx
101
+ end
102
+ end
103
+ end
104
+ info
105
+ end
68
106
  end
69
107
  end
70
108
  end
@@ -4,26 +4,57 @@ require 'treetop'
4
4
  require 'lydown/parsing/nodes'
5
5
  require 'lydown/parsing/lydown.treetop'
6
6
 
7
- # Treetop.load 'lydown/parsing/lydown'
8
-
9
7
  class LydownParser
10
8
  def self.parse(source, opts = {})
11
- parser = self.new
9
+ if opts[:no_progress]
10
+ do_parse(source, opts)
11
+ else
12
+ Lydown::CLI.show_progress('Parse', source.length * 2) do |bar|
13
+ stream = do_parse(source, opts)
14
+ bar.finish
15
+ stream
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.do_parse(source, opts)
21
+ parser, ast = self.new
12
22
  ast = parser.parse(source)
23
+
13
24
  unless ast
14
25
  error_msg = format_parser_error(source, parser, opts)
15
- STDERR.puts error_msg
26
+ $stderr.puts error_msg
16
27
  raise LydownError, error_msg
17
28
  else
18
29
  stream = []
30
+ ast.to_stream(stream, opts.merge(progress_base: source.length))
19
31
  # insert source ref event into stream if we have a filename ref
20
- stream << {type: :source_ref}.merge(opts) if opts[:filename]
32
+ if opts[:filename] && !stream.empty?
33
+ stream.unshift({type: :source_ref}.merge(opts))
34
+ end
35
+ stream
36
+ end
37
+ end
38
+
39
+ def self.parse_macro_group(source, opts)
40
+ parser, ast = self.new, ast = nil
41
+ old_bar, $progress_bar = $progress_bar, nil
42
+ ast = parser.parse(source)
43
+ unless ast
44
+ error_msg = format_parser_error(source, parser, opts)
45
+ $stderr.puts error_msg
46
+ raise LydownError, error_msg
47
+ else
48
+ stream = []
21
49
  ast.to_stream(stream, opts)
50
+ stream
22
51
  end
52
+ ensure
53
+ $progress_bar = old_bar
23
54
  end
24
55
 
25
56
  def self.format_parser_error(source, parser, opts)
26
- msg = opts[:source_filename] ? "#{opts[:source_filename]}: " : ""
57
+ msg = opts[:filename] ? "#{opts[:filename]}: " : ""
27
58
  if opts[:nice_error]
28
59
  msg << "Unexpected character at line #{parser.failure_line} column #{parser.failure_column}:\n"
29
60
  else
@@ -1,5 +1,5 @@
1
1
  module Lydown::Parsing
2
- module Root
2
+ module RootMethods
3
3
  def _to_stream(element, stream, opts)
4
4
  if element.elements
5
5
  element.elements.each do |e|
@@ -17,9 +17,9 @@ module Lydown::Parsing
17
17
  end
18
18
 
19
19
  def event_hash(stream, opts, hash = {})
20
- if source = opts[:source]
20
+ if opts[:proof_mode] && (source = opts[:source])
21
21
  last = stream.last
22
- if last && last[:type] == :source_ref && last[:line]
22
+ if last && (last[:type] == :source_ref) && last[:line]
23
23
  line, column = last[:line], last[:column]
24
24
  else
25
25
  line, column = source.find_line_and_column(interval.first)
@@ -34,18 +34,34 @@ module Lydown::Parsing
34
34
  else
35
35
  hash
36
36
  end
37
+ ensure
38
+ if $progress_bar
39
+ $progress_bar.progress = (opts[:progress_base] || 0) + interval.end
40
+ end
41
+ end
42
+
43
+ def add_event(stream, opts, type)
44
+ stream << event_hash(stream, opts, {type: type})
45
+ end
46
+ end
47
+
48
+ class Root < Treetop::Runtime::SyntaxNode
49
+ include RootMethods
50
+ def initialize(*args)
51
+ super
52
+ if $progress_bar
53
+ $progress_bar.progress = interval.end
54
+ end
37
55
  end
38
56
  end
39
57
 
40
- module CommentContent
58
+ class CommentContent < Root
41
59
  def to_stream(stream, opts)
42
60
  stream << {type: :comment, content: text_value.strip}
43
61
  end
44
62
  end
45
63
 
46
- module Setting
47
- include Root
48
-
64
+ class Setting < Root
49
65
  def to_stream(stream, opts)
50
66
  level = (text_value =~ /^([\s]+)/) ? ($1.length / 2) : 0
51
67
  @setting = event_hash(stream, opts, {
@@ -76,9 +92,7 @@ module Lydown::Parsing
76
92
  end
77
93
  end
78
94
 
79
- module DurationValue
80
- include Root
81
-
95
+ class DurationValue < Root
82
96
  def to_stream(stream, opts)
83
97
  stream << event_hash(stream, opts, {
84
98
  type: :duration, value: text_value
@@ -86,9 +100,7 @@ module Lydown::Parsing
86
100
  end
87
101
  end
88
102
 
89
- module TupletValue
90
- include Root
91
-
103
+ class TupletValue < Root
92
104
  def to_stream(stream, opts)
93
105
  if text_value =~ /^(\d+)%((\d+)\/(\d+))?$/
94
106
  value, fraction, group_length = $1, $2, $4
@@ -107,9 +119,7 @@ module Lydown::Parsing
107
119
  end
108
120
  end
109
121
 
110
- module GraceDuration
111
- include Root
112
-
122
+ class GraceDuration < Root
113
123
  GRACE_KIND = {
114
124
  nil => :grace,
115
125
  '/' => :acciaccatura,
@@ -125,9 +135,7 @@ module Lydown::Parsing
125
135
  end
126
136
  end
127
137
 
128
- module Note
129
- include Root
130
-
138
+ class Note < Root
131
139
  def to_stream(stream, opts)
132
140
  note = event_hash(stream, opts, {
133
141
  type: :note, raw: text_value
@@ -165,9 +173,7 @@ module Lydown::Parsing
165
173
  end
166
174
  end
167
175
 
168
- module Chord
169
- include Root
170
-
176
+ class Chord < Root
171
177
  def to_stream(stream, opts)
172
178
  chord = event_hash(stream, opts, {
173
179
  type: :chord, notes: []
@@ -184,57 +190,55 @@ module Lydown::Parsing
184
190
  end
185
191
  end
186
192
 
187
- module StandAloneFigures
188
- include Root
189
-
193
+ class StandAloneFigures < Root
190
194
  def to_stream(stream, opts)
191
- note = {type: :stand_alone_figures}
195
+ note = event_hash(stream, opts, {
196
+ type: :stand_alone_figures
197
+ })
192
198
  _to_stream(self, note, opts)
193
199
  stream << note
194
200
  end
195
201
  end
196
202
 
197
203
  module Phrasing
198
- module BeamOpen
204
+ class BeamOpen < Root
199
205
  def to_stream(stream, opts)
200
- stream << {type: :beam_open}
206
+ add_event(stream, opts, :beam_open)
201
207
  end
202
208
  end
203
209
 
204
- module BeamClose
210
+ class BeamClose < Root
205
211
  def to_stream(stream, opts)
206
- stream << {type: :beam_close}
212
+ add_event(stream, opts, :beam_close)
207
213
  end
208
214
  end
209
215
 
210
- module SlurOpen
216
+ class SlurOpen < Root
211
217
  def to_stream(stream, opts)
212
- stream << {type: :slur_open}
218
+ add_event(stream, opts, :slur_open)
213
219
  end
214
220
  end
215
221
 
216
- module SlurClose
222
+ class SlurClose < Root
217
223
  def to_stream(stream, opts)
218
- stream << {type: :slur_close}
224
+ add_event(stream, opts, :slur_close)
219
225
  end
220
226
  end
221
227
  end
222
228
 
223
- module Tie
229
+ class Tie < Root
224
230
  def to_stream(stream, opts)
225
- stream << {type: :tie}
231
+ stream << event_hash(stream, opts, {type: :tie})
226
232
  end
227
233
  end
228
234
 
229
- module ShortTie
235
+ class ShortTie < Root
230
236
  def to_stream(stream, opts)
231
- stream << {type: :short_tie}
237
+ stream << event_hash(stream, opts, {type: :short_tie})
232
238
  end
233
239
  end
234
240
 
235
- module Rest
236
- include Root
237
-
241
+ class Rest < Root
238
242
  def to_stream(stream, opts)
239
243
  rest = event_hash(stream, opts, {
240
244
  type: :rest, raw: text_value, head: text_value[0]
@@ -249,9 +253,7 @@ module Lydown::Parsing
249
253
  end
250
254
  end
251
255
 
252
- module Silence
253
- include Root
254
-
256
+ class Silence < Root
255
257
  def to_stream(stream, opts)
256
258
  stream << event_hash(stream, opts, {
257
259
  type: :silence, raw: text_value, head: text_value[0]
@@ -260,8 +262,7 @@ module Lydown::Parsing
260
262
  end
261
263
 
262
264
  module DurationMacroExpression
263
- include Root
264
-
265
+ include RootMethods
265
266
  def to_stream(stream, opts)
266
267
  stream << event_hash(stream, opts, {
267
268
  type: :duration_macro, macro: text_value
@@ -269,8 +270,7 @@ module Lydown::Parsing
269
270
  end
270
271
  end
271
272
 
272
- module Lyrics
273
- include Root
273
+ class Lyrics < Root
274
274
  def to_stream(stream, opts)
275
275
  o = {type: :lyrics}
276
276
  _to_stream(self, o, opts)
@@ -304,7 +304,7 @@ module Lydown::Parsing
304
304
  end
305
305
  end
306
306
 
307
- module StreamIndex
307
+ class StreamIndex < Root
308
308
  def to_stream(o, opts)
309
309
  idx = (text_value =~ /\(([\d]+)\)/) && $1.to_i
310
310
  if idx.nil?
@@ -315,14 +315,16 @@ module Lydown::Parsing
315
315
  end
316
316
 
317
317
  module Barline
318
+ include RootMethods
318
319
  def to_stream(stream, opts)
319
- stream << {type: :barline, barline: text_value}
320
+ stream << event_hash(stream, opts, {
321
+ type: :barline,
322
+ barline: text_value
323
+ })
320
324
  end
321
325
  end
322
326
 
323
- module Command
324
- include Root
325
-
327
+ class Command < Root
326
328
  def to_stream(stream, opts)
327
329
  cmd = event_hash(stream, opts, {
328
330
  type: :command, raw: text_value
@@ -361,15 +363,14 @@ module Lydown::Parsing
361
363
  end
362
364
  end
363
365
 
364
- module VoiceSelector
366
+ class VoiceSelector < Root
365
367
  def to_stream(stream, opts)
366
368
  voice = (text_value =~ /^([1234])/) && $1.to_i
367
369
  stream << {type: :voice_select, voice: voice}
368
370
  end
369
371
  end
370
372
 
371
- module SourceRef
372
- include Root
373
+ class SourceRef < Root
373
374
  def to_stream(stream, opts)
374
375
  ref = {type: :source_ref, raw: text_value}
375
376
  _to_stream(self, ref, opts)