gly 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/gly.rb +2 -0
- data/lib/gly/cli.rb +53 -2
- data/lib/gly/document.rb +24 -0
- data/lib/gly/document_gabc_convertor.rb +7 -3
- data/lib/gly/document_ly_convertor.rb +58 -0
- data/lib/gly/document_ly_convertor.rb~ +37 -0
- data/lib/gly/options.rb~ +15 -0
- data/lib/gly/parser.rb +26 -6
- data/lib/gly/preview_builder.rb +38 -0
- data/lib/gly/preview_builder.rb~ +38 -0
- data/lib/gly/preview_generator.rb +86 -15
- data/lib/gly/templates/lualatex_document.tex +29 -5
- data/tests/examples/gly/expected/explicit_music.gabc +2 -0
- data/tests/examples/gly/expected/explicit_music.gabc~ +1 -0
- data/tests/examples/gly/given/explicit_music.gly +3 -0
- data/tests/examples/gly/given/explicit_music.gly~ +3 -0
- data/tests/string_helpers_test.rb +3 -1
- metadata +12 -106
- data/tests/examples/gly/expected/empty.gabc +0 -1
- data/tests/examples/gly/given/empty.gly +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2057f1f3a833d1b6bb382573386d7519fd1f83a
|
4
|
+
data.tar.gz: 2c770e056bc7c3c0a6a7aeb689a9888615334f17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6860d75f1e55af109e10535f76b404d074b6bc9bafad0b13784f98ca706f448705a1116216598052b364802e6c7e3b3b4dde87557d5926d13417dbb69ab488ba
|
7
|
+
data.tar.gz: fd1df56ea96ca42c779086e8b495fcb4da4dddeb77fa13a87238506dd5044802f9486ba220a617efef925671fee73afa7cb98f620ea9f508c7df70c0a7df0180
|
data/lib/gly.rb
CHANGED
data/lib/gly/cli.rb
CHANGED
@@ -1,16 +1,38 @@
|
|
1
1
|
require 'thor'
|
2
2
|
|
3
|
+
begin
|
4
|
+
require 'grely'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
3
8
|
module Gly
|
4
9
|
# implements the 'gly' executable
|
5
10
|
class CLI < Thor
|
11
|
+
class_option :separator, aliases: :s, banner: 'syllable separator (default is double dash "--")'
|
12
|
+
|
6
13
|
desc 'gabc FILE ...', 'convert gly to gabc'
|
7
14
|
def gabc(*files)
|
8
|
-
files.each
|
15
|
+
files.each do |f|
|
16
|
+
DocumentGabcConvertor.new(parser.parse(f)).convert
|
17
|
+
end
|
9
18
|
end
|
10
19
|
|
11
20
|
desc 'preview FILE ...', 'convert to gabc AND generate pdf preview'
|
21
|
+
option :no_build, type: :boolean, aliases: :B, banner: 'only generate preview assets, don\'t compile them'
|
22
|
+
option :no_document, type: :boolean, aliases: :D, banner: 'produce main LaTeX file without document definition; in this case --no-build is applied automatically'
|
23
|
+
option :full_headers, type: :boolean, aliases: :h, banner: 'include full document and score headers'
|
24
|
+
option :template, aliases: :t, banner: 'use custom document template'
|
12
25
|
def preview(*files)
|
13
|
-
|
26
|
+
tpl = nil
|
27
|
+
tpl = File.read(options[:template]) if options[:template]
|
28
|
+
|
29
|
+
opts = options.to_h
|
30
|
+
opts[:suffix_always] = true
|
31
|
+
|
32
|
+
files.each do |f|
|
33
|
+
gen = PreviewGenerator.new template: tpl, options: opts
|
34
|
+
gen.process(parser.parse(f))
|
35
|
+
end
|
14
36
|
end
|
15
37
|
|
16
38
|
desc 'list FILE ...', 'list scores contained in files'
|
@@ -37,5 +59,34 @@ module Gly
|
|
37
59
|
|
38
60
|
exit(lister.error? ? 1 : 0)
|
39
61
|
end
|
62
|
+
|
63
|
+
desc 'ly FILE ...', 'transform gly document to lilypond document'
|
64
|
+
def ly(*files)
|
65
|
+
unless defined? LilypondConvertor
|
66
|
+
STDERR.puts "'lygre' gem not found. Please, install lygre in order to run 'gly ly'."
|
67
|
+
exit 1
|
68
|
+
end
|
69
|
+
|
70
|
+
files.each do |f|
|
71
|
+
DocumentLyConvertor.new(parser.parse(f)).convert
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def parser
|
78
|
+
Parser.new options[:separator]
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
# override Thor's default handler
|
83
|
+
def handle_no_command_error(command, has_namespace=$thor_runner)
|
84
|
+
if has_namespace
|
85
|
+
fail Thor::UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace."
|
86
|
+
else
|
87
|
+
fail Thor::UndefinedCommandError, "Could not find command #{command.inspect}. Did you mean 'gly preview #{command}' ?"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
40
91
|
end
|
41
92
|
end
|
data/lib/gly/document.rb
CHANGED
@@ -5,11 +5,35 @@ module Gly
|
|
5
5
|
class Document
|
6
6
|
def initialize
|
7
7
|
@scores = []
|
8
|
+
@scores_by_id = {}
|
8
9
|
@header = Headers.new
|
9
10
|
@path = nil
|
10
11
|
end
|
11
12
|
|
12
13
|
attr_reader :scores, :header
|
13
14
|
attr_accessor :path
|
15
|
+
|
16
|
+
def <<(score)
|
17
|
+
@scores << score
|
18
|
+
|
19
|
+
sid = score.headers['id']
|
20
|
+
if sid
|
21
|
+
if @scores_by_id.has_key? sid
|
22
|
+
raise ArgumentError.new("More than one score with id '#{sid}'.")
|
23
|
+
end
|
24
|
+
|
25
|
+
@scores_by_id[sid] = score
|
26
|
+
end
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
if key.is_a? Integer
|
33
|
+
@scores[key]
|
34
|
+
else
|
35
|
+
@scores_by_id[key]
|
36
|
+
end
|
37
|
+
end
|
14
38
|
end
|
15
39
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
module Gly
|
2
2
|
class DocumentGabcConvertor
|
3
|
-
def initialize(document)
|
3
|
+
def initialize(document, **options)
|
4
4
|
@doc = document
|
5
|
+
@options = options
|
5
6
|
end
|
6
7
|
|
7
8
|
def convert
|
@@ -24,8 +25,11 @@ module Gly
|
|
24
25
|
private
|
25
26
|
|
26
27
|
def output_fname(score, score_index=nil)
|
27
|
-
|
28
|
-
|
28
|
+
if @doc.scores.size == 1 && !@options[:suffix_always]
|
29
|
+
score_id = ''
|
30
|
+
else
|
31
|
+
score_id = '_' + (score.headers['id'] || score_index.to_s)
|
32
|
+
end
|
29
33
|
|
30
34
|
File.basename(@doc.path)
|
31
35
|
.sub(/\.gly\Z/i, "#{score_id}.gabc")
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Gly
|
4
|
+
# Converts gly to modern-notation LilyPond
|
5
|
+
#
|
6
|
+
# expects the 'lygre' gem
|
7
|
+
# (which is only an optional dependency of gly)
|
8
|
+
class DocumentLyConvertor
|
9
|
+
def initialize(document)
|
10
|
+
@doc = document
|
11
|
+
end
|
12
|
+
|
13
|
+
def convert
|
14
|
+
gabcor = GabcConvertor.new
|
15
|
+
parser = GabcParser.new
|
16
|
+
lilyor = LilypondConvertor.new cadenza: true, version: false
|
17
|
+
|
18
|
+
ly_output = StringIO.new
|
19
|
+
|
20
|
+
ly_output.puts '\version "2.18.0"'
|
21
|
+
ly_output.puts
|
22
|
+
ly_output.puts header @doc.header
|
23
|
+
ly_output.puts
|
24
|
+
ly_output.puts default_style
|
25
|
+
ly_output.puts
|
26
|
+
|
27
|
+
@doc.scores.each do |score|
|
28
|
+
gabc = gabcor.convert(score).string
|
29
|
+
parsed_score = parser.parse gabc
|
30
|
+
begin
|
31
|
+
ly_output.puts lilyor.convert parsed_score.create_score
|
32
|
+
rescue NoMethodError
|
33
|
+
ly_output.puts "\\markup{error processing score \\italic{#{score.lyrics.readable}}}"
|
34
|
+
end
|
35
|
+
|
36
|
+
ly_output.puts
|
37
|
+
end
|
38
|
+
|
39
|
+
out_fname = File.basename(@doc.path) + '.ly'
|
40
|
+
File.open(out_fname, 'w') do |fw|
|
41
|
+
fw.puts ly_output.string
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def header(h)
|
48
|
+
fields = h.each_pair.collect do |k,v|
|
49
|
+
" #{k} = \"#{v}\""
|
50
|
+
end
|
51
|
+
"\\header {\n#{fields.join("\n")}\n}\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_style
|
55
|
+
'\layout { \context Score \override TimeSignature #\'stencil = ##f }'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Gly
|
2
|
+
class DocumentLyConvertor < DocumentGabcConvertor
|
3
|
+
def initialize(document)
|
4
|
+
@doc = document
|
5
|
+
end
|
6
|
+
|
7
|
+
def convert
|
8
|
+
each_score_with_gabcname do |score, out_fname|
|
9
|
+
File.open(out_fname, 'w') do |fw|
|
10
|
+
GabcConvertor.new.convert score, fw
|
11
|
+
end
|
12
|
+
yield score, out_fname if block_given?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# iterates over document scores,
|
17
|
+
# yields score and filename of it's generated gabc file
|
18
|
+
def each_score_with_gabcname
|
19
|
+
@doc.scores.each_with_index do |score, si|
|
20
|
+
yield score, output_fname(score, si)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def output_fname(score, score_index=nil)
|
27
|
+
if @doc.scores.size == 1
|
28
|
+
score_id = ''
|
29
|
+
else
|
30
|
+
score_id = '_' + (score.headers['id'] || score_index.to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
File.basename(@doc.path)
|
34
|
+
.sub(/\.gly\Z/i, "#{score_id}.gabc")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/gly/options.rb~
ADDED
data/lib/gly/parser.rb
CHANGED
@@ -3,8 +3,8 @@ require 'stringio'
|
|
3
3
|
module Gly
|
4
4
|
# parses gly source
|
5
5
|
class Parser
|
6
|
-
def initialize(syllable_separator=
|
7
|
-
@syllable_separator = syllable_separator
|
6
|
+
def initialize(syllable_separator=nil)
|
7
|
+
@syllable_separator = syllable_separator || '--'
|
8
8
|
end
|
9
9
|
|
10
10
|
def parse(source)
|
@@ -28,7 +28,7 @@ module Gly
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def parse_str(str)
|
31
|
-
parse_io(StringIO.new(
|
31
|
+
parse_io(StringIO.new(str))
|
32
32
|
end
|
33
33
|
|
34
34
|
def parse_io(io)
|
@@ -52,6 +52,10 @@ module Gly
|
|
52
52
|
@score = @doc.header
|
53
53
|
elsif header_line? line
|
54
54
|
parse_header line
|
55
|
+
elsif explicit_lyrics? line
|
56
|
+
parse_lyrics line
|
57
|
+
elsif explicit_music? line
|
58
|
+
parse_music line
|
55
59
|
elsif lyrics_line? line
|
56
60
|
parse_lyrics line
|
57
61
|
else
|
@@ -88,8 +92,18 @@ module Gly
|
|
88
92
|
|
89
93
|
EXPLICIT_LYRICS_RE = /\A\\l(yrics)?\s+/
|
90
94
|
|
95
|
+
def explicit_lyrics?(str)
|
96
|
+
str =~ EXPLICIT_LYRICS_RE
|
97
|
+
end
|
98
|
+
|
99
|
+
EXPLICIT_MUSIC_RE = /\A\\m(usic)?\s+/
|
100
|
+
|
101
|
+
def explicit_music?(str)
|
102
|
+
str =~ EXPLICIT_MUSIC_RE
|
103
|
+
end
|
104
|
+
|
91
105
|
def lyrics_line?(str)
|
92
|
-
str
|
106
|
+
!contains_square_brackets?(str) && (str.include?(@syllable_separator) || contains_unmusical_letters?(str))
|
93
107
|
end
|
94
108
|
|
95
109
|
def in_header_block?
|
@@ -98,7 +112,11 @@ module Gly
|
|
98
112
|
|
99
113
|
def contains_unmusical_letters?(str)
|
100
114
|
letters = str.gsub(/[\W\d_]+/, '')
|
101
|
-
letters !~ /\A[a-
|
115
|
+
letters !~ /\A[a-morsvwxz]*\Z/i # incomplete gabc music letters!
|
116
|
+
end
|
117
|
+
|
118
|
+
def contains_square_brackets?(str)
|
119
|
+
str.include? '['
|
102
120
|
end
|
103
121
|
|
104
122
|
def parse_header(str)
|
@@ -118,6 +136,8 @@ module Gly
|
|
118
136
|
end
|
119
137
|
|
120
138
|
def parse_music(str)
|
139
|
+
str = str.sub(EXPLICIT_MUSIC_RE, '')
|
140
|
+
|
121
141
|
# music chunks: split by whitespace out of brackets
|
122
142
|
StringHelpers.music_split(str).each do |chunk|
|
123
143
|
@score.music << chunk
|
@@ -126,7 +146,7 @@ module Gly
|
|
126
146
|
|
127
147
|
def push_score
|
128
148
|
if @score.is_a?(ParsedScore) && !@score.empty?
|
129
|
-
@doc
|
149
|
+
@doc << @score
|
130
150
|
end
|
131
151
|
end
|
132
152
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Gly
|
2
|
+
# *builds* the pdf preview from assets prepared
|
3
|
+
# by PreviewGenerator
|
4
|
+
class PreviewBuilder
|
5
|
+
def initialize
|
6
|
+
@gabcs = []
|
7
|
+
@main_tex = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_gabc(path)
|
11
|
+
@gabcs << path
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :main_tex
|
15
|
+
|
16
|
+
def build
|
17
|
+
@gabcs.each do |g|
|
18
|
+
exec 'gregorio', g
|
19
|
+
end
|
20
|
+
|
21
|
+
exec 'lualatex', @main_tex
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def exec(progname, *args)
|
27
|
+
ok = system progname, *args
|
28
|
+
unless ok
|
29
|
+
case $?.exitstatus
|
30
|
+
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
|
+
else
|
33
|
+
STDERR.puts "'#{progname}' exited with exit code #{$?.to_i}. Let's continue and see what happens ..."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Gly
|
2
|
+
# *builds* the pdf preview from assets prepared
|
3
|
+
# by PreviewGenerator
|
4
|
+
class PreviewBuilder
|
5
|
+
def initialize
|
6
|
+
@gabcs = []
|
7
|
+
@main_tex = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_gabc(path)
|
11
|
+
@gabcs << path
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_writer :main_tex
|
15
|
+
|
16
|
+
def build
|
17
|
+
@gabcs.each do |g|
|
18
|
+
exec 'gregorio', g
|
19
|
+
end
|
20
|
+
|
21
|
+
exec 'lualatex', @main_tex
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def exec(progname, *args)
|
27
|
+
ok = system progname, *args
|
28
|
+
unless ok
|
29
|
+
case $?.exitstatus
|
30
|
+
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
|
+
else
|
33
|
+
STDERR.puts "'#{progname}' exited with exit code #{$?.to_i}. Let's continue and see what happens ..."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -4,18 +4,38 @@ module Gly
|
|
4
4
|
# Takes Gly::Document, builds a pdf preview
|
5
5
|
# (or at least generates all necessary assets)
|
6
6
|
class PreviewGenerator
|
7
|
+
def initialize(**options)
|
8
|
+
@preview_dest = nil
|
9
|
+
|
10
|
+
@template = options.delete(:template) || default_template
|
11
|
+
@builder = options.delete(:builder) || PreviewBuilder.new
|
12
|
+
@options = options.delete(:options) || {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# IO to which the main LaTeX document should be written.
|
16
|
+
# If not set, a file will be created with name based on
|
17
|
+
# the source file name.
|
18
|
+
attr_accessor :preview_dest
|
19
|
+
|
7
20
|
def process(document)
|
8
|
-
|
9
|
-
|
21
|
+
convertor = DocumentGabcConvertor.new(document, **@options)
|
22
|
+
convertor.convert
|
10
23
|
|
11
24
|
doc_body = fw = StringIO.new
|
12
25
|
|
13
|
-
|
26
|
+
if @options[:full_headers]
|
27
|
+
fw.puts header_table document.header
|
28
|
+
end
|
29
|
+
|
14
30
|
convertor.each_score_with_gabcname do |score, gabc_fname|
|
15
|
-
|
31
|
+
@builder.add_gabc gabc_fname
|
32
|
+
|
33
|
+
if @options[:full_headers]
|
34
|
+
fw.puts header_table score.headers
|
35
|
+
end
|
36
|
+
|
16
37
|
gtex_fname = gabc_fname.sub /\.gabc/i, ''
|
17
|
-
piece_title = %w(book manuscript arranger author).collect do |m|
|
18
|
-
score.headers[m]
|
38
|
+
piece_title = %w(book manuscript arranger author).collect do |m| score.headers[m]
|
19
39
|
end.delete_if(&:nil?).join ', '
|
20
40
|
fw.puts "\\commentary{\\footnotesize{#{piece_title}}}\n" unless piece_title.empty?
|
21
41
|
|
@@ -30,19 +50,70 @@ module Gly
|
|
30
50
|
fw.puts "\\includescore{#{gtex_fname}}\n\\vspace{1cm}"
|
31
51
|
end
|
32
52
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
53
|
+
if @options['no_document']
|
54
|
+
tex = doc_body.string
|
55
|
+
else
|
56
|
+
replacements = {
|
57
|
+
glyvars: header_variables(document.header),
|
58
|
+
body: doc_body.string
|
59
|
+
}
|
60
|
+
tex = @template % replacements
|
61
|
+
end
|
62
|
+
|
63
|
+
with_preview_io(document.path) do |fw|
|
64
|
+
@builder.main_tex = fw.path if fw.respond_to? :path
|
39
65
|
|
40
|
-
out_fname = File.basename(document.path).sub(/\.gly\Z/i, '.tex')
|
41
|
-
File.open(out_fname, 'w') do |fw|
|
42
66
|
fw.puts tex
|
43
67
|
end
|
44
68
|
|
45
|
-
|
69
|
+
build_disabled = @options['no_build'] || @options['no_document']
|
70
|
+
if @builder.main_tex && !build_disabled
|
71
|
+
@builder.build
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def with_preview_io(src_name)
|
78
|
+
if @preview_dest
|
79
|
+
yield @preview_dest
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
File.open(preview_fname(src_name), 'w') do |fw|
|
84
|
+
yield fw
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def preview_fname(src_name)
|
89
|
+
File.basename(src_name).sub(/\.gly\Z/i, '.tex')
|
90
|
+
end
|
91
|
+
|
92
|
+
def default_template
|
93
|
+
File.read(File.join(File.dirname(__FILE__), 'templates/lualatex_document.tex'))
|
94
|
+
end
|
95
|
+
|
96
|
+
# full header of a score/file as table
|
97
|
+
def header_table(header)
|
98
|
+
return '' if header.empty?
|
99
|
+
|
100
|
+
cols = header.each_pair.collect {|k,v| "#{k} & #{v} \\\\" }
|
101
|
+
|
102
|
+
"\\begin{tabular}{ | r | l | } \\hline %s \\hline \\end{tabular}\n\n" % cols.join("\\hline\n")
|
103
|
+
end
|
104
|
+
|
105
|
+
# transforms header to LaTeX command definitions
|
106
|
+
def header_variables(header)
|
107
|
+
header.each_pair.collect do |k,v|
|
108
|
+
'\newcommand{\%s}{%s}' % [latex_cmd_name(k), v]
|
109
|
+
end.join("\n")
|
110
|
+
end
|
111
|
+
|
112
|
+
def latex_cmd_name(header_name)
|
113
|
+
sanitized = header_name
|
114
|
+
.gsub(/\s+/, '')
|
115
|
+
.gsub(/[-_]+(\w)/) {|m| m[1].upcase }
|
116
|
+
prefixed = 'gly' + sanitized[0].upcase + sanitized[1..-1]
|
46
117
|
end
|
47
118
|
end
|
48
119
|
end
|
@@ -5,23 +5,47 @@
|
|
5
5
|
\usepackage[left=2cm, right=2cm, top=2cm, bottom=2cm]{geometry}
|
6
6
|
|
7
7
|
\usepackage{fontspec}
|
8
|
+
\usepackage[hidelinks]{hyperref}
|
9
|
+
\usepackage{etoolbox}
|
8
10
|
|
9
11
|
%% for gregorio
|
10
12
|
\usepackage{luatextra}
|
11
13
|
\usepackage{graphicx}
|
12
14
|
\usepackage{gregoriotex}
|
13
15
|
|
14
|
-
|
16
|
+
%{glyvars}
|
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:
|
24
|
+
\newcommand{\glyPreviewTitle}{
|
25
|
+
\begin{center}
|
26
|
+
\ifdef{\glyTitle}{{\huge \glyTitle}}{}
|
27
|
+
|
28
|
+
\ifdef{\glyAuthor}{\emph{\glyAuthor}}{}
|
29
|
+
|
30
|
+
\ifdef{\glyYear}{\glyYear}{}
|
31
|
+
|
32
|
+
\vspace{6mm}
|
33
|
+
\end{center}
|
34
|
+
}
|
35
|
+
|
36
|
+
\newcommand{\glyPreviewTagline}{
|
37
|
+
\begin{center}
|
38
|
+
\texttt{\href{https://github.com/igneus/gly}{gly preview} \today}
|
39
|
+
\end{center}
|
40
|
+
}
|
15
41
|
|
16
42
|
\begin{document}
|
17
43
|
|
18
|
-
|
44
|
+
\glyPreviewTitle
|
19
45
|
|
20
46
|
%{body}
|
21
47
|
|
22
48
|
%% tagline
|
23
49
|
\vfill
|
24
|
-
\
|
25
|
-
\texttt{gly preview https://github.com/igneus/gly}
|
26
|
-
\end{center}
|
50
|
+
\glyPreviewTagline
|
27
51
|
\end{document}
|
@@ -0,0 +1 @@
|
|
1
|
+
\music č ř š
|
@@ -7,8 +7,10 @@ class StringHelpersTest < GlyTest
|
|
7
7
|
['single_chunk', 'a', ['a']],
|
8
8
|
['simple_whitespace', 'aa aa', ['aa', 'aa']],
|
9
9
|
['leading_trailing_whitespace', ' a a ', ['a', 'a']],
|
10
|
+
['repeated_inner_whitespace', 'a a', ['a', 'a']],
|
10
11
|
['bracketted', '(a)', ['a']],
|
11
|
-
['empty', '()', ['']]
|
12
|
+
['empty', '()', ['']],
|
13
|
+
['nested', '(a(b))', ['a(b', ')']] # nesting isn't supported
|
12
14
|
]
|
13
15
|
|
14
16
|
examples.each do |e|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakub Pavlík
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -38,108 +38,7 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1'
|
41
|
-
description:
|
42
|
-
is an acronym of \"Gregorio for liLYponders\" or\n\"Gregorio with separate LYrics.\n\n##
|
43
|
-
Why\n\nOne of the most popular solutions for typesetting quadratic\nnotation used
|
44
|
-
for the Gregorian chant is [Gregorio][gregorio].\n\nHowever, I have several objections
|
45
|
-
against it's input format.\nThat led me to designing an alternative, Gregorio-inspired\nnotation
|
46
|
-
format, which compiles to pure Gregorio GABC\n(which I don't like writing manually).\n\n##
|
47
|
-
Features\n\n* music separated from lyrics\n * no need of the ubiquitous and tedious
|
48
|
-
parentheses\n * separation of \"material and form\" -> easy copying of the music
|
49
|
-
or\n lyrics alone is possible (useful for a composer)\n * syllabified lyrics
|
50
|
-
entered in a format inspired by LilyPond\n* music and lyrics can be interspersed
|
51
|
-
as needed\n* no semicolons in the header\n* custom header fields supported (commented
|
52
|
-
out in the GABC output,\n as gregorio doesn't tolerate headers it doesn't know)\n*
|
53
|
-
several scores per file (when compiled to gabc, each becomes\n a separate file)\n*
|
54
|
-
compile pdf preview by a single command, without writing any (La)TeX\n\n## Real
|
55
|
-
world examples\n\n* [Proper Divine Office chants of Bohemian Premonstratensian houses][opraem_boh]\n\n##
|
56
|
-
Basic examples\n\nTypical GABC source of an antiphon looks like this:\n\n name:
|
57
|
-
Nativitas gloriosae;\n office-part: laudes, 1. ant.;\n occasion: In Nativitate
|
58
|
-
B. Mariae Virginis;\n book: Antiphonale Romanum 1912, pg. 704;\n mode: 8;\n
|
59
|
-
\ initial-style: 1;\n %%\n \n (c4) NA(g)TI(g)VI(g)TAS(gd) glo(f)ri(gh)ó(g)sae(g)
|
60
|
-
* (,)\n Vír(g)gi(g)nis(hi) Ma(gh)rí(gf)ae,(f) (;)\n ex(f) sé(g)mi(h)ne(h)
|
61
|
-
A(hiwji)bra(hg)hae,(g) (;)\n or(gh~)tae(g) de(g) tri(g)bu(fe/fgf) Ju(d)da,(d)
|
62
|
-
(;)\n cla(df!gh)ra(g) ex(f) stir(hg~)pe(hi) Da(h)vid.(g) (::)\n\nCorresponding
|
63
|
-
GLY may look like this:\n\n name: Nativitas gloriosae\n office-part: laudes,
|
64
|
-
1. ant.\n occasion: In Nativitate B. Mariae Virginis\n book: Antiphonale Romanum
|
65
|
-
1912, pg. 704\n mode: 8\n initial-style: 1\n \n c4 g g g gd f gh g g
|
66
|
-
,\n g g hi gh gf f ;\n f g h h hiwji hg g ;\n gh~ g g g fe/fgf d d ;\n
|
67
|
-
\ df!gh g f hg~ hi h g ::\n \n NA -- TI -- VI -- TAS glo -- ri -- ósae *\n
|
68
|
-
\ Vír -- gi -- nis Ma -- rí -- ae,\n ex sé -- mi -- ne A -- bra -- hae,\n or
|
69
|
-
-- tae de tri -- bu Ju -- da,\n cla -- ra ex stir -- pe Da -- vid.\n\nOr, with
|
70
|
-
music and lyrics interlaced\n(this arrangement may be handy for larger scores,\nlike
|
71
|
-
full-notated hymns, sequences or nocturnal responsories):\n\n name: Nativitas
|
72
|
-
gloriosae\n office-part: laudes, 1. ant.\n occasion: In Nativitate B. Mariae
|
73
|
-
Virginis\n book: Antiphonale Romanum 1912, pg. 704\n mode: 8\n initial-style:
|
74
|
-
1\n \n c4 g g g gd f gh g g ,\n NA -- TI -- VI -- TAS glo -- ri -- ósae
|
75
|
-
*\n \n g g hi gh gf f ;\n Vír -- gi -- nis Ma -- rí -- ae,\n \n f
|
76
|
-
g h h hiwji hg g ;\n ex sé -- mi -- ne A -- bra -- hae,\n \n gh~ g g g
|
77
|
-
fe/fgf d d ;\n or -- tae de tri -- bu Ju -- da,\n \n df!gh g f hg~ hi h
|
78
|
-
g ::\n cla -- ra ex stir -- pe Da -- vid.\n\nOther arrangements are also possible.
|
79
|
-
Order of music and lyrics\nis actually ignored during processing.\n\n## Installation\n\nInstall
|
80
|
-
Ruby (some 2.x version) runtime. Then install as any ruby gem:\n\n`gem install gly`\n\n##
|
81
|
-
Usage\n\nThis gem provides executable `gly`. Run `gly help` for full list\nof subcommands.
|
82
|
-
The most important ones are:\n\n`gly gabc FILE1 ...`\n\nconverts given gly file(s)
|
83
|
-
to one or more gabc files (one per score,\ni.e. one gly may spawn a bunch of gabcs).\n\n`gly
|
84
|
-
preview FILE1 ...`\n\nAttempts to create a pdf document with all scores contained
|
85
|
-
in each\ngly file. Expects `gregorio` and `lualatex` to be in PATH\nand `gregoriotex`
|
86
|
-
to be installed and accessible by `lualatex`.\n\n## Tools\n\n[Emacs mode with syntax
|
87
|
-
highlighting for gly][elisp]\n\n![Editing gly in emacs](/doc/img/gly_emacs_scr.png)\n\n##
|
88
|
-
Syntax reference\n\nGly syntax is line-based.\nThe interpreter reads the input line
|
89
|
-
by line,\nand depending on context it interprets each line as\ne.g. music, lyrics
|
90
|
-
or header field.\n\nThe syntax is quite permissive, not requiring a lot of delimiters\nor
|
91
|
-
hints for the parser concerning what each line means.\nMostly the parser guesses
|
92
|
-
the meaning correctly.\nWhere not, meaning of each line can be stated explicitly.\n\n###
|
93
|
-
1. Comments\n\nWhen a `%` sign is encountered, everything until the end of line\nis
|
94
|
-
considered a comment and not interpreted.\n(Comment syntax is the same as in gabc.)\n\nPlease
|
95
|
-
note, that when compiling to gabc, comments are dropped\nand don't appear in the
|
96
|
-
resulting gabc file.\n\n### 2. Whitespace\n\nEmpty lines are ignored.\n\n### 3.
|
97
|
-
Scores\n\nA new score begins at the beginning of a file or at a line containing\na
|
98
|
-
single keyword '\\score'.\n\nIt consists of\na header (optional, only permitted
|
99
|
-
at the beginning of a score)\nand music- and lyrics-lines.\nLines with music and
|
100
|
-
lyrics may appear in any order.\n\nScore ends with end of file or with explicit
|
101
|
-
beginning of a new score\nor another top-level element.\n\n#### 3.1 Score header\n\nScore
|
102
|
-
header consists of fields.\n\nEach header field is on it's own (one) line and consists
|
103
|
-
of\nidentifier, colon and value:\n\n`mode: 8`\n\nHeader field identifier may only
|
104
|
-
consist of alphanumeric characters,\nminus-sign and underscore. Value can contain
|
105
|
-
anything.\n\nScore header ends with first non-empty line identified by the parser\nas
|
106
|
-
music or lyrics.\n\nHeader field 'id' is special: if present, it is used as suffix\nof
|
107
|
-
the generated gabc file (instead of the default, which is\nnumeric position of the
|
108
|
-
score in the source document).\n\n#### 3.2 Lyrics\n\nSyntax of lyrics is inspired
|
109
|
-
by LilyPond.\nLyrics have to be manually syllabified. Default syllable delimiter\nis
|
110
|
-
double dash (minus) `--` with optional whitespace around.\n\n`cla -- ra ex stir
|
111
|
-
-- pe Da -- vid.`\n\nThe parser guesses meaning of the line by attempting to find\nsyllable
|
112
|
-
separator in it and by looking if it's alphanumeric\ncharacters contains something
|
113
|
-
that cannot be interpreted as music.\nIf any of these conditions is met, the line
|
114
|
-
is interpreted as lyrics.\n\nIf gly fails to guess your lyrics line correctly and
|
115
|
-
interprets\nit as music, place `\\lyrics` or short `\\l` at the beginning\nof the
|
116
|
-
unhappy line:\n\n`\\l a a a`\n\n#### 3.3 Music\n\nAny line appearing in a score
|
117
|
-
and not identified as header field\nor lyrics is music by default.\n\nMusic line
|
118
|
-
contains one or more music chunks separated by whitespace.\nFor music syntax see
|
119
|
-
[official gabc documentation][gabc] -\ngly doesn't change anything in this respect.\n\nMusic
|
120
|
-
chunks may be enclosed in parentheses as in gabc.\n(Of course you don't like the
|
121
|
-
parentheses and are happy that gly\nlet's you leave them out. But in some special
|
122
|
-
cases they come handy.)\n\n#### 3.4 Matching lyrics to music\n\nWhen processing
|
123
|
-
the gly source and producing gabc, music chunks\nare matched to lyric syllables.\n\nThere
|
124
|
-
are, however, a few special cases, to make it work conveniently:\n\nThese special
|
125
|
-
cases of music chunks don't get lyric syllable:\n\n* clef\n* music chunk containing
|
126
|
-
only a division - i.e. music chunk containing\n one of `,` , `;` , `:` , `::` alone\n\nException
|
127
|
-
to this rule are 'nonlyrical lyrics chunks'.\nCurrently there is only one built-in
|
128
|
-
nonlyrical lyric chunk:\nasterisk `*`.\nNormally it is treated as any other syllable,\nbut
|
129
|
-
if it meets a division, it is set as it's lyrics, while\na normal syllable wouldn't
|
130
|
-
be.\n\nIf you need to set some other syllable under a division,\nmake it 'nonlyrical'
|
131
|
-
by placing\nan exclamation mark at it's beginning, e.g. `!<i>Ps.</i>`\n\nIn the
|
132
|
-
other direction it is sometimes necessary to set a syllable\nnot matching any music
|
133
|
-
at all. In such cases empty music chunk\n`()` is what you need.\n\n### 4. Document
|
134
|
-
header\n\nEach gly document may optinally contain a document header.\nIt may appear
|
135
|
-
anywhere in the document, but best practice is to place\nit at the very beginning.\n\nDocument
|
136
|
-
header starts with keyword `\\header` and ends\nat the end of file or at the beginning
|
137
|
-
of another top-level element.\nThe syntax of it's content is the same\nas for [Score
|
138
|
-
header][].\n\nField 'title' in the document header is, if present,\nused by `gly
|
139
|
-
preview` as title of the generated pdf.\n\n## Run tests\n\nby executing `tests/run.rb`\n\n##
|
140
|
-
License\n\nMIT\n\n[gregorio]: https://github.com/gregorio-project/gregorio\n[gabc]:
|
141
|
-
http://gregorio-project.github.io/gabc/index.html\n[elisp]: /tree/master/elisp\n\n[opraem_boh]:
|
142
|
-
https://gist.github.com/igneus/1aed0b36e9b23b51526d\n"
|
41
|
+
description:
|
143
42
|
email: jkb.pavlik@gmail.com
|
144
43
|
executables:
|
145
44
|
- gly
|
@@ -156,6 +55,8 @@ files:
|
|
156
55
|
- lib/gly/document.rb~
|
157
56
|
- lib/gly/document_gabc_convertor.rb
|
158
57
|
- lib/gly/document_gabc_convertor.rb~
|
58
|
+
- lib/gly/document_ly_convertor.rb
|
59
|
+
- lib/gly/document_ly_convertor.rb~
|
159
60
|
- lib/gly/gabc_convertor.rb
|
160
61
|
- lib/gly/gabc_convertor.rb~
|
161
62
|
- lib/gly/headers.rb
|
@@ -164,10 +65,13 @@ files:
|
|
164
65
|
- lib/gly/lister.rb~
|
165
66
|
- lib/gly/lyrics.rb
|
166
67
|
- lib/gly/lyrics.rb~
|
68
|
+
- lib/gly/options.rb~
|
167
69
|
- lib/gly/parsed_score.rb
|
168
70
|
- lib/gly/parsed_score.rb~
|
169
71
|
- lib/gly/parser.rb
|
170
72
|
- lib/gly/parser.rb~
|
73
|
+
- lib/gly/preview_builder.rb
|
74
|
+
- lib/gly/preview_builder.rb~
|
171
75
|
- lib/gly/preview_generator.rb
|
172
76
|
- lib/gly/preview_generator.rb~
|
173
77
|
- lib/gly/string_helpers.rb
|
@@ -181,9 +85,10 @@ files:
|
|
181
85
|
- tests/examples/gly/expected/differentiae.gabc~
|
182
86
|
- tests/examples/gly/expected/document_header.gabc
|
183
87
|
- tests/examples/gly/expected/document_header.gabc~
|
184
|
-
- tests/examples/gly/expected/empty.gabc
|
185
88
|
- tests/examples/gly/expected/explicit_lyrics.gabc
|
186
89
|
- tests/examples/gly/expected/explicit_lyrics.gabc~
|
90
|
+
- tests/examples/gly/expected/explicit_music.gabc
|
91
|
+
- tests/examples/gly/expected/explicit_music.gabc~
|
187
92
|
- tests/examples/gly/expected/explicit_score.gabc
|
188
93
|
- tests/examples/gly/expected/force_divisio_lyrics.gabc
|
189
94
|
- tests/examples/gly/expected/header.gabc
|
@@ -203,8 +108,9 @@ files:
|
|
203
108
|
- tests/examples/gly/given/differentiae.gly
|
204
109
|
- tests/examples/gly/given/differentiae.gly~
|
205
110
|
- tests/examples/gly/given/document_header.gly
|
206
|
-
- tests/examples/gly/given/empty.gly
|
207
111
|
- tests/examples/gly/given/explicit_lyrics.gly
|
112
|
+
- tests/examples/gly/given/explicit_music.gly
|
113
|
+
- tests/examples/gly/given/explicit_music.gly~
|
208
114
|
- tests/examples/gly/given/explicit_score.gly
|
209
115
|
- tests/examples/gly/given/force_divisio_lyrics.gly
|
210
116
|
- tests/examples/gly/given/header.gly
|
@@ -1 +0,0 @@
|
|
1
|
-
%%
|
File without changes
|