lydown 0.7.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -14
- data/bin/lydown +1 -122
- data/lib/lydown.rb +3 -3
- data/lib/lydown/cli.rb +4 -0
- data/lib/lydown/cli/commands.rb +98 -0
- data/lib/lydown/cli/compiler.rb +11 -14
- data/lib/lydown/cli/diff.rb +3 -3
- data/lib/lydown/cli/output.rb +18 -0
- data/lib/lydown/cli/proofing.rb +8 -6
- data/lib/lydown/cli/support.rb +34 -0
- data/lib/lydown/cli/translation.rb +73 -0
- data/lib/lydown/core_ext.rb +1 -1
- data/lib/lydown/lilypond.rb +49 -11
- data/lib/lydown/parsing.rb +37 -6
- data/lib/lydown/parsing/nodes.rb +57 -56
- data/lib/lydown/rendering.rb +0 -9
- data/lib/lydown/rendering/base.rb +2 -2
- data/lib/lydown/rendering/command.rb +2 -2
- data/lib/lydown/rendering/comments.rb +1 -1
- data/lib/lydown/rendering/figures.rb +14 -14
- data/lib/lydown/rendering/lib.ly +22 -0
- data/lib/lydown/rendering/lyrics.rb +2 -2
- data/lib/lydown/rendering/movement.rb +2 -2
- data/lib/lydown/rendering/music.rb +46 -46
- data/lib/lydown/rendering/notes.rb +149 -70
- data/lib/lydown/rendering/settings.rb +21 -16
- data/lib/lydown/rendering/skipping.rb +1 -1
- data/lib/lydown/rendering/source_ref.rb +4 -4
- data/lib/lydown/rendering/staff.rb +8 -4
- data/lib/lydown/rendering/voices.rb +9 -9
- data/lib/lydown/templates.rb +9 -0
- data/lib/lydown/templates/lilypond_doc.erb +1 -1
- data/lib/lydown/templates/movement.erb +9 -1
- data/lib/lydown/templates/part.erb +12 -3
- data/lib/lydown/translation.rb +17 -0
- data/lib/lydown/translation/ripple.rb +30 -0
- data/lib/lydown/translation/ripple/nodes.rb +191 -0
- data/lib/lydown/translation/ripple/ripple.treetop +100 -0
- data/lib/lydown/version.rb +1 -1
- data/lib/lydown/work.rb +144 -198
- data/lib/lydown/work_context.rb +152 -0
- 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
|
data/lib/lydown/core_ext.rb
CHANGED
data/lib/lydown/lilypond.rb
CHANGED
@@ -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
|
-
|
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
|
57
|
+
cmd = 'lilypond '
|
51
58
|
cmd << "-o #{opts[:output_filename]} "
|
52
59
|
cmd << "-dno-point-and-click "
|
53
|
-
cmd << "--#{opts[:format]} " if
|
54
|
-
cmd << '
|
60
|
+
cmd << "--#{opts[:format]} " if format
|
61
|
+
cmd << ' - '
|
55
62
|
|
56
63
|
err_info = ''
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
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
|
data/lib/lydown/parsing.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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[:
|
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
|
data/lib/lydown/parsing/nodes.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Lydown::Parsing
|
2
|
-
module
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
188
|
-
include Root
|
189
|
-
|
193
|
+
class StandAloneFigures < Root
|
190
194
|
def to_stream(stream, opts)
|
191
|
-
note = {
|
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
|
-
|
204
|
+
class BeamOpen < Root
|
199
205
|
def to_stream(stream, opts)
|
200
|
-
stream
|
206
|
+
add_event(stream, opts, :beam_open)
|
201
207
|
end
|
202
208
|
end
|
203
209
|
|
204
|
-
|
210
|
+
class BeamClose < Root
|
205
211
|
def to_stream(stream, opts)
|
206
|
-
stream
|
212
|
+
add_event(stream, opts, :beam_close)
|
207
213
|
end
|
208
214
|
end
|
209
215
|
|
210
|
-
|
216
|
+
class SlurOpen < Root
|
211
217
|
def to_stream(stream, opts)
|
212
|
-
stream
|
218
|
+
add_event(stream, opts, :slur_open)
|
213
219
|
end
|
214
220
|
end
|
215
221
|
|
216
|
-
|
222
|
+
class SlurClose < Root
|
217
223
|
def to_stream(stream, opts)
|
218
|
-
stream
|
224
|
+
add_event(stream, opts, :slur_close)
|
219
225
|
end
|
220
226
|
end
|
221
227
|
end
|
222
228
|
|
223
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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 <<
|
320
|
+
stream << event_hash(stream, opts, {
|
321
|
+
type: :barline,
|
322
|
+
barline: text_value
|
323
|
+
})
|
320
324
|
end
|
321
325
|
end
|
322
326
|
|
323
|
-
|
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
|
-
|
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
|
-
|
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)
|