gly 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/gly +11 -0
- data/bin/glyfy +4 -0
- data/lib/gly.rb +5 -0
- data/lib/gly.rb~ +5 -0
- data/lib/gly/cli.rb +94 -0
- data/lib/gly/cli.rb~ +13 -0
- data/lib/gly/document.rb +15 -0
- data/lib/gly/document.rb~ +8 -0
- data/lib/gly/gabc_convertor.rb +60 -0
- data/lib/gly/gabc_convertor.rb~ +5 -0
- data/lib/gly/headers.rb +80 -0
- data/lib/gly/headers.rb~ +13 -0
- data/lib/gly/lyrics.rb +39 -0
- data/lib/gly/lyrics.rb~ +21 -0
- data/lib/gly/parsed_score.rb +16 -0
- data/lib/gly/parsed_score.rb~ +6 -0
- data/lib/gly/parser.rb +106 -0
- data/lib/gly/parser.rb~ +6 -0
- data/lib/gly/string_helpers.rb +34 -0
- data/lib/gly/string_helpers.rb~ +36 -0
- data/tests/examples.rb +28 -0
- data/tests/examples.rb~ +9 -0
- data/tests/examples/gly/expected/block_lyrics.gabc +2 -0
- data/tests/examples/gly/expected/differentiae.gabc +2 -0
- data/tests/examples/gly/expected/differentiae.gabc~ +2 -0
- data/tests/examples/gly/expected/document_header.gabc +4 -0
- data/tests/examples/gly/expected/document_header.gabc~ +4 -0
- data/tests/examples/gly/expected/empty.gabc +1 -0
- data/tests/examples/gly/expected/explicit_lyrics.gabc +2 -0
- data/tests/examples/gly/expected/explicit_lyrics.gabc~ +2 -0
- data/tests/examples/gly/expected/explicit_score.gabc +2 -0
- data/tests/examples/gly/expected/force_divisio_lyrics.gabc +2 -0
- data/tests/examples/gly/expected/header.gabc +3 -0
- data/tests/examples/gly/expected/header_unofficial.gabc +4 -0
- data/tests/examples/gly/expected/header_unofficial.gabc~ +3 -0
- data/tests/examples/gly/expected/multiline.gabc +2 -0
- data/tests/examples/gly/expected/multiline_interlaced.gabc +2 -0
- data/tests/examples/gly/expected/nonlyrical_lyrics.gabc +2 -0
- data/tests/examples/gly/expected/nonlyrical_lyrics.gabc~ +2 -0
- data/tests/examples/gly/expected/notes_bracketted.gabc +2 -0
- data/tests/examples/gly/expected/notes_bracketted.gabc~ +1 -0
- data/tests/examples/gly/expected/notes_only.gabc +2 -0
- data/tests/examples/gly/expected/notes_with_lyrics.gabc +2 -0
- data/tests/examples/gly/expected/notes_with_lyrics.gabc~ +2 -0
- data/tests/examples/gly/expected/unsyllabified_lyrics.gabc +2 -0
- data/tests/examples/gly/given/block_lyrics.gly +3 -0
- data/tests/examples/gly/given/differentiae.gly +3 -0
- data/tests/examples/gly/given/differentiae.gly~ +3 -0
- data/tests/examples/gly/given/document_header.gly +9 -0
- data/tests/examples/gly/given/empty.gly +0 -0
- data/tests/examples/gly/given/explicit_lyrics.gly +2 -0
- data/tests/examples/gly/given/explicit_score.gly +2 -0
- data/tests/examples/gly/given/force_divisio_lyrics.gly +2 -0
- data/tests/examples/gly/given/header.gly +2 -0
- data/tests/examples/gly/given/header_unofficial.gly +3 -0
- data/tests/examples/gly/given/header_unofficial.gly~ +2 -0
- data/tests/examples/gly/given/multiline.gly +4 -0
- data/tests/examples/gly/given/multiline_interlaced.gly +5 -0
- data/tests/examples/gly/given/multiline_interlaced.gly~ +4 -0
- data/tests/examples/gly/given/nonlyrical_lyrics.gly +2 -0
- data/tests/examples/gly/given/nonlyrical_lyrics.gly~ +2 -0
- data/tests/examples/gly/given/notes_bracketted.gabc~ +1 -0
- data/tests/examples/gly/given/notes_bracketted.gly +1 -0
- data/tests/examples/gly/given/notes_only.gly +1 -0
- data/tests/examples/gly/given/notes_with_lyrics.gly +2 -0
- data/tests/examples/gly/given/unsyllabified_lyrics.gly +2 -0
- data/tests/examples/gly/no_crash/document_header.gly~ +4 -0
- data/tests/examples/gly/no_crash/multiple_scores.gly +10 -0
- data/tests/examples/gly/no_crash/multiple_scores.gly~ +7 -0
- data/tests/no_crash.rb +20 -0
- data/tests/programmed_examples.rb~ +14 -0
- data/tests/run.rb +3 -0
- data/tests/run.rb~ +3 -0
- data/tests/string_helpers_test.rb +19 -0
- data/tests/string_helpers_test.rb~ +9 -0
- data/tests/test_helper.rb +17 -0
- metadata +193 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 341817505f45deff8e6c9815061fa6cffa571858
|
4
|
+
data.tar.gz: 64ff6cb093190e69c37ed874af5c9b34e1499c3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 41ba343096d37f095b7e9ca1724a8692d7bf4ca6dfcac689e681e9c61c1c95067ec42ba18f6fa8de259f6bf70b1960e5108f1f2503be1044e8df64bb0ec6fa96
|
7
|
+
data.tar.gz: a14a88215832854321ecadff5fd0f0c01a057d3ad991873aede7d91b879da35edfe197bca214144a55a4ad4ff893d0a365505849ce62020b9da7361a5fe9a2e8
|
data/bin/gly
ADDED
data/bin/glyfy
ADDED
data/lib/gly.rb
ADDED
data/lib/gly.rb~
ADDED
data/lib/gly/cli.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Gly
|
4
|
+
# implements the 'gly' executable
|
5
|
+
class CLI < Thor
|
6
|
+
desc 'gabc FILE ...', 'convert gly to gabc'
|
7
|
+
def gabc(*files)
|
8
|
+
files.each {|f| gabc_convert(parse(f)) }
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'preview FILE ...', 'convert to gabc AND generate pdf preview'
|
12
|
+
def preview(*files)
|
13
|
+
files.each {|f| make_preview f }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parse(gly_file)
|
19
|
+
document = File.open(gly_file) do |fr|
|
20
|
+
Parser.new.parse(fr)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def gabc_convert(doc)
|
25
|
+
doc.scores.each_with_index do |score, si|
|
26
|
+
score_id = score.headers['id'] || si.to_s
|
27
|
+
out_fname = File.basename(doc.path)
|
28
|
+
.sub(/\.gly\Z/i, "_#{score_id}.gabc")
|
29
|
+
File.open(out_fname, 'w') do |fw|
|
30
|
+
GabcConvertor.new.convert score, fw
|
31
|
+
end
|
32
|
+
yield score, out_fname if block_given?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def make_preview(gly_file)
|
37
|
+
doc = parse(gly_file)
|
38
|
+
|
39
|
+
tex_header = <<EOS
|
40
|
+
% LuaLaTeX
|
41
|
+
|
42
|
+
\\documentclass[a4paper, 12pt]{article}
|
43
|
+
\\usepackage[latin]{babel}
|
44
|
+
\\usepackage[left=2cm, right=2cm, top=2cm, bottom=2cm]{geometry}
|
45
|
+
|
46
|
+
\\usepackage{fontspec}
|
47
|
+
|
48
|
+
% for gregorio
|
49
|
+
\\usepackage{luatextra}
|
50
|
+
\\usepackage{graphicx}
|
51
|
+
\\usepackage{gregoriotex}
|
52
|
+
|
53
|
+
\\title{#{doc.header['title']}}
|
54
|
+
|
55
|
+
\\begin{document}
|
56
|
+
|
57
|
+
#{doc.header['title'] && '\\maketitle'}
|
58
|
+
|
59
|
+
EOS
|
60
|
+
|
61
|
+
tex_fname = File.basename(gly_file).sub(/\.gly\Z/i, '.tex')
|
62
|
+
File.open(tex_fname, 'w') do |fw|
|
63
|
+
fw.puts tex_header
|
64
|
+
|
65
|
+
gabc_convert(doc) do |score, gabc_fname|
|
66
|
+
system "gregorio #{gabc_fname}"
|
67
|
+
gtex_fname = gabc_fname.sub /\.gabc/i, ''
|
68
|
+
piece_title = %w(book manuscript arranger author).collect do |m|
|
69
|
+
score.headers[m]
|
70
|
+
end.delete_if(&:nil?).join ', '
|
71
|
+
fw.puts "\\commentary{\\footnotesize{#{piece_title}}}\n" unless piece_title.empty?
|
72
|
+
|
73
|
+
annotations = score.headers.each_value('annotation')
|
74
|
+
begin
|
75
|
+
fw.puts "\\setfirstannotation{#{annotations.next}}"
|
76
|
+
fw.puts "\\setsecondannotation{#{annotations.next}}"
|
77
|
+
rescue StopIteration
|
78
|
+
# ok, no more annotations
|
79
|
+
end
|
80
|
+
|
81
|
+
fw.puts "\\includescore{#{gtex_fname}}\n\\vspace{1cm}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# tagline
|
85
|
+
fw.puts "\n\\vfill\n\\begin{center}"
|
86
|
+
fw.puts "\\texttt{gly preview https://github.com/igneus/gly}"
|
87
|
+
fw.puts "\\end{center}\n"
|
88
|
+
fw.puts "\n\\end{document}"
|
89
|
+
end
|
90
|
+
|
91
|
+
system "lualatex #{tex_fname}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/gly/cli.rb~
ADDED
data/lib/gly/document.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Gly
|
2
|
+
# Result of parsing of a gly file.
|
3
|
+
# Usually contains one or more scores
|
4
|
+
# and possibly a document header.
|
5
|
+
class Document
|
6
|
+
def initialize
|
7
|
+
@scores = []
|
8
|
+
@header = Headers.new
|
9
|
+
@path = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :scores, :header
|
13
|
+
attr_accessor :path
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Gly
|
2
|
+
# converts parsed gly to gabc
|
3
|
+
class GabcConvertor
|
4
|
+
def convert(score, out=StringIO.new)
|
5
|
+
score.headers.each_pair do |key,value|
|
6
|
+
out.print '% ' unless Headers.gregorio_supported?(key)
|
7
|
+
out.puts "#{key}: #{value};"
|
8
|
+
end
|
9
|
+
|
10
|
+
out.puts '%%'
|
11
|
+
|
12
|
+
lyric_enum = score.lyrics.each_syllable.to_enum
|
13
|
+
score.music.each_with_index do |mus_chunk,i|
|
14
|
+
begin
|
15
|
+
next_syl = lyric_enum.peek
|
16
|
+
rescue StopIteration
|
17
|
+
next_syl = ''
|
18
|
+
end
|
19
|
+
|
20
|
+
if clef?(mus_chunk) ||
|
21
|
+
(nonlyrical_chunk?(mus_chunk) && ! nonlyrical_lyrics?(next_syl))
|
22
|
+
# music chunk normally not having lyrics
|
23
|
+
out.print ' ' if i != 0
|
24
|
+
else
|
25
|
+
# regular music chunk
|
26
|
+
begin
|
27
|
+
out.print strip_directives lyric_enum.next
|
28
|
+
rescue StopIteration
|
29
|
+
out.print ' ' if i != 0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
out.print "(#{mus_chunk})"
|
33
|
+
# out.puts if differentia?(mus_chunk) # newline after each differentia
|
34
|
+
end
|
35
|
+
|
36
|
+
return out
|
37
|
+
end
|
38
|
+
|
39
|
+
def clef?(chunk)
|
40
|
+
chunk =~ /\A[cf][1-4]\Z/
|
41
|
+
end
|
42
|
+
|
43
|
+
def differentia?(chunk)
|
44
|
+
chunk =~ /\A*[,;:]+\Z/ # differentia
|
45
|
+
end
|
46
|
+
|
47
|
+
# is the given music chunk capable of bearing lyrics?
|
48
|
+
def nonlyrical_chunk?(chunk)
|
49
|
+
differentia? chunk
|
50
|
+
end
|
51
|
+
|
52
|
+
def nonlyrical_lyrics?(syl)
|
53
|
+
syl =~ /\A\s*!/ || syl =~ /\A\s*\*\Z/
|
54
|
+
end
|
55
|
+
|
56
|
+
def strip_directives(syl)
|
57
|
+
syl.sub(/(\s*)!/, '\1') # exclamation mark at the beginning - place even under nonlyrical music chunk
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/gly/headers.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Gly
|
4
|
+
# score or document header
|
5
|
+
class Headers
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
# header fields supported by gregorio.
|
9
|
+
# Based on http://gregorio-project.github.io/gabc/index.html#header
|
10
|
+
# last checked 2015-12-06
|
11
|
+
GREGORIO_HEADERS = %w(
|
12
|
+
name
|
13
|
+
gabc-copyright
|
14
|
+
score-copyright
|
15
|
+
office-part
|
16
|
+
occasion
|
17
|
+
meter
|
18
|
+
commentary
|
19
|
+
arranger
|
20
|
+
gabc-version
|
21
|
+
author
|
22
|
+
date
|
23
|
+
manuscript
|
24
|
+
manuscript-reference
|
25
|
+
manuscript-storage-place
|
26
|
+
book
|
27
|
+
transcriber
|
28
|
+
transcription-date
|
29
|
+
gregoriotex-font
|
30
|
+
mode
|
31
|
+
initial-style
|
32
|
+
centering-scheme
|
33
|
+
user-notes
|
34
|
+
annotation
|
35
|
+
)
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
# for quick lookup. Only holds a single value per header
|
39
|
+
@headers = {}
|
40
|
+
# holds order and as many values for a header as given in the
|
41
|
+
# score (typically annotation occurs more than once)
|
42
|
+
@pairs = []
|
43
|
+
end
|
44
|
+
|
45
|
+
def []=(key, value)
|
46
|
+
@headers[key] = value
|
47
|
+
@pairs << [key, value]
|
48
|
+
end
|
49
|
+
|
50
|
+
def_delegator :@headers, :[]
|
51
|
+
def_delegator :@pairs, :empty?
|
52
|
+
|
53
|
+
# some header fields may appear more than once;
|
54
|
+
# this method provides access to values of all occurrences
|
55
|
+
# of a given header field
|
56
|
+
def each_value(key)
|
57
|
+
return to_enum(:each_value, key) unless block_given?
|
58
|
+
|
59
|
+
each_pair do |k,v|
|
60
|
+
yield v if k == key
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def each_pair
|
65
|
+
return to_enum(:each_pair) unless block_given?
|
66
|
+
|
67
|
+
@pairs.each do |k|
|
68
|
+
yield k[0], k[1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.gregorio_supported?(key)
|
73
|
+
GREGORIO_HEADERS.include? key
|
74
|
+
end
|
75
|
+
|
76
|
+
def headers
|
77
|
+
self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/gly/headers.rb~
ADDED
data/lib/gly/lyrics.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Gly
|
4
|
+
class Lyrics
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@words = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def each_syllable
|
12
|
+
return enum_for(:each_syllable) unless block_given?
|
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
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def_delegator :@words, :each, :each_word
|
26
|
+
def_delegators :@words, :empty?, :<<
|
27
|
+
end
|
28
|
+
|
29
|
+
class Word
|
30
|
+
extend Forwardable
|
31
|
+
|
32
|
+
def initialize(syllables=[])
|
33
|
+
@syllables = syllables
|
34
|
+
end
|
35
|
+
|
36
|
+
def_delegators :@syllables, :<<, :push
|
37
|
+
def_delegator :@syllables, :each, :each_syllable
|
38
|
+
end
|
39
|
+
end
|
data/lib/gly/lyrics.rb~
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Gly
|
4
|
+
class Lyrics
|
5
|
+
def initialize
|
6
|
+
@words = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def_delegator :@words, :each
|
10
|
+
alias_method :each_word, :each
|
11
|
+
end
|
12
|
+
|
13
|
+
class Word
|
14
|
+
def initialize
|
15
|
+
@syllables = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def_delegator :@syllables, :<<, :push, :each
|
19
|
+
alias_method :each_syllable, :each
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Gly
|
2
|
+
# result of a gly score parsing
|
3
|
+
class ParsedScore
|
4
|
+
def initialize
|
5
|
+
@headers = Headers.new
|
6
|
+
@lyrics = Lyrics.new
|
7
|
+
@music = []
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :headers, :lyrics, :music
|
11
|
+
|
12
|
+
def empty?
|
13
|
+
@headers.empty? && @lyrics.empty? && @music.empty?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/gly/parser.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
module Gly
|
2
|
+
# parses gly source
|
3
|
+
class Parser
|
4
|
+
SYLLABLE_SEP = '--'
|
5
|
+
|
6
|
+
def parse(io)
|
7
|
+
@doc = Document.new
|
8
|
+
@score = ParsedScore.new
|
9
|
+
|
10
|
+
if io.respond_to? :path
|
11
|
+
@doc.path = io.path
|
12
|
+
end
|
13
|
+
|
14
|
+
io.each do |line|
|
15
|
+
line = strip_comments(line)
|
16
|
+
|
17
|
+
if empty? line
|
18
|
+
next
|
19
|
+
elsif new_score? line
|
20
|
+
push_score
|
21
|
+
@score = ParsedScore.new
|
22
|
+
elsif header_start? line
|
23
|
+
push_score
|
24
|
+
@score = @doc.header
|
25
|
+
elsif header_line? line
|
26
|
+
parse_header line
|
27
|
+
elsif lyrics_line? line
|
28
|
+
parse_lyrics line
|
29
|
+
else
|
30
|
+
parse_music line
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
push_score
|
35
|
+
|
36
|
+
return @doc
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def empty?(str)
|
42
|
+
str =~ /\A\s*\Z/
|
43
|
+
end
|
44
|
+
|
45
|
+
def new_score?(str)
|
46
|
+
str =~ /\A\s*\\score/
|
47
|
+
end
|
48
|
+
|
49
|
+
def header_start?(str)
|
50
|
+
str =~ /\A\s*\\header/
|
51
|
+
end
|
52
|
+
|
53
|
+
def strip_comments(str)
|
54
|
+
str.sub(/%.*\Z/, '')
|
55
|
+
end
|
56
|
+
|
57
|
+
def header_line?(str)
|
58
|
+
in_header_block? || @score.lyrics.empty? && @score.music.empty? && str =~ /\w+:\s*./
|
59
|
+
end
|
60
|
+
|
61
|
+
EXPLICIT_LYRICS_RE = /\A\\l(yrics)?\s+/
|
62
|
+
|
63
|
+
def lyrics_line?(str)
|
64
|
+
str =~ EXPLICIT_LYRICS_RE || str.include?(SYLLABLE_SEP) || contains_unmusical_letters?(str)
|
65
|
+
end
|
66
|
+
|
67
|
+
def in_header_block?
|
68
|
+
@score.is_a? Headers
|
69
|
+
end
|
70
|
+
|
71
|
+
def contains_unmusical_letters?(str)
|
72
|
+
letters = str.gsub(/[\W\d_]+/, '')
|
73
|
+
letters !~ /\A[a-mvwoxz]*\Z/i # incomplete gabc music letters!
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_header(str)
|
77
|
+
hid, hvalue = str.split(':').collect(&:strip)
|
78
|
+
@score.headers[hid] = hvalue
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_lyrics(str)
|
82
|
+
# words: split by whitespace not being part of syllable
|
83
|
+
# separator
|
84
|
+
str
|
85
|
+
.sub(EXPLICIT_LYRICS_RE, '')
|
86
|
+
.split(/(?<!#{SYLLABLE_SEP})\s+(?!#{SYLLABLE_SEP})/)
|
87
|
+
.each do |word|
|
88
|
+
@score.lyrics << Word.new(word.split(/\s*#{SYLLABLE_SEP}\s*/))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_music(str)
|
93
|
+
# music chunks: split by whitespace out of brackets
|
94
|
+
str.split(/\s+/).each do |chunk|
|
95
|
+
chunk.sub!(/\A\((.*?)\)\Z/, '\1') # unparenthesize
|
96
|
+
@score.music << chunk
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def push_score
|
101
|
+
if @score.is_a?(ParsedScore) && !@score.empty?
|
102
|
+
@doc.scores << @score
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/gly/parser.rb~
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Gly
|
2
|
+
module StringHelpers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# splits string by whitespace that is not enclosed
|
6
|
+
# in brackets.
|
7
|
+
# At the same time removes brackets.
|
8
|
+
def bracket_aware_whitespace_split(str)
|
9
|
+
str = str.strip
|
10
|
+
chunk_start = 0
|
11
|
+
chunks = []
|
12
|
+
in_brackets = false
|
13
|
+
str.chars.each_with_index do |char, chi|
|
14
|
+
if in_brackets
|
15
|
+
next if char != ')'
|
16
|
+
|
17
|
+
else
|
18
|
+
if char =~ /^\s$/
|
19
|
+
if chunk_start != chi
|
20
|
+
chunks << str[chunk_start .. chi-1]
|
21
|
+
end
|
22
|
+
chunk_start = chi+1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
chunks << str[chunk_start .. -1]
|
28
|
+
|
29
|
+
chunks
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :music_split, :bracket_aware_whitespace_split
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Gly
|
2
|
+
module StringHelpers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# splits string by whitespace that is not enclosed
|
6
|
+
# in brackets.
|
7
|
+
# At the same time removes brackets.
|
8
|
+
def bracket_aware_whitespace_split(str)
|
9
|
+
str = str.strip
|
10
|
+
chunk_start = 0
|
11
|
+
chunks = []
|
12
|
+
in_brackets = false
|
13
|
+
str.chars.each_with_index do |char, chi|
|
14
|
+
if in_brackets
|
15
|
+
next if char != ')'
|
16
|
+
|
17
|
+
else
|
18
|
+
if char =~ /^\s$/
|
19
|
+
if chunk_start != chi
|
20
|
+
chunks << str[chunk_start .. chi-1]
|
21
|
+
end
|
22
|
+
chunk_start = chi+1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if chunk_start != chi
|
28
|
+
chunks << str[chunk_start .. chi-1]
|
29
|
+
end
|
30
|
+
|
31
|
+
chunks
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :music_split, :bracket_aware_whitespace_split
|
35
|
+
end
|
36
|
+
end
|
data/tests/examples.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# test-suite dynamically created from examples in subfolders
|
2
|
+
|
3
|
+
require_relative 'test_helper'
|
4
|
+
|
5
|
+
# Each test case is defined by a pair of files
|
6
|
+
# in directories examples/given and examples/expected
|
7
|
+
# which define what is an expected gabc result of a single-score
|
8
|
+
# gly file.
|
9
|
+
class TestExamples < GlyTest
|
10
|
+
def self.example_test_case(given_file, expected_file)
|
11
|
+
# filename without extension
|
12
|
+
case_name = File.basename(given_file).sub(/\.[^\.]*\Z/, '')
|
13
|
+
|
14
|
+
define_method "test_#{case_name}" do
|
15
|
+
expected = File.read expected_file
|
16
|
+
File.open given_file do |fr|
|
17
|
+
given = gly_process(fr).string
|
18
|
+
assert_equal expected, given
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
here = File.dirname __FILE__
|
24
|
+
Dir.glob(File.join(here, 'examples/gly/given/*.gly')).each do |given|
|
25
|
+
expected = given.sub('/given', '/expected').sub('.gly', '.gabc')
|
26
|
+
example_test_case given, expected
|
27
|
+
end
|
28
|
+
end
|
data/tests/examples.rb~
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
%%
|
@@ -0,0 +1 @@
|
|
1
|
+
c4 g g' g gd f (gh) g g_' () (g a ij)
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
c4 g g' g gd f gh g g_'
|
@@ -0,0 +1 @@
|
|
1
|
+
c4 g g' g gd f (gh) g g_' () (g a ij)
|
@@ -0,0 +1 @@
|
|
1
|
+
c4 g g' g gd f gh g g_'
|
data/tests/no_crash.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
# tests of cases that cannot be easily described by a pair
|
4
|
+
# of single gly file and resulting gabc file
|
5
|
+
class TestNoCrash < GlyTest
|
6
|
+
def self.nocrash_test_case(filename)
|
7
|
+
case_name = File.basename(filename).sub(/\.[^\.]*\Z/, '')
|
8
|
+
define_method "test_#{case_name}" do
|
9
|
+
File.open filename do |fr|
|
10
|
+
# simply let it convert to test that it does not crash
|
11
|
+
gly_process(fr).string
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
here = File.dirname __FILE__
|
17
|
+
Dir.glob(File.join(here, 'examples/gly/no_crash/*.gly')).each do |f|
|
18
|
+
nocrash_test_case f
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
# tests of cases that cannot be easily described by a pair
|
4
|
+
# of single gly file and resulting gabc file
|
5
|
+
class TestSpecialCases < MiniTest::Test
|
6
|
+
def self.loads_without_crash_test(filename)
|
7
|
+
case_name = File.basename(given_file).sub(/\.[^\.]*\Z/, '')
|
8
|
+
define_method "test_#{case_name}" do
|
9
|
+
File.open given_file do |fr|
|
10
|
+
gly_process(fr).string
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/tests/run.rb
ADDED
data/tests/run.rb~
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class StringHelpersTest < GlyTest
|
4
|
+
SH = Gly::StringHelpers
|
5
|
+
|
6
|
+
examples = [
|
7
|
+
['single_chunk', 'a', ['a']],
|
8
|
+
['simple_whitespace', 'aa aa', ['aa', 'aa']],
|
9
|
+
['leading_trailing_whitespace', ' a a ', ['a', 'a']],
|
10
|
+
['bracketted', '(a)', ['a']],
|
11
|
+
]
|
12
|
+
|
13
|
+
examples.each do |e|
|
14
|
+
name, given, expected = e
|
15
|
+
define_method "test_#{name}" do
|
16
|
+
assert_equal expected, SH.music_split(given)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
require 'gly'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
require 'minitest/reporters'
|
7
|
+
Minitest::Reporters.use!
|
8
|
+
|
9
|
+
# parent of all gly test classes
|
10
|
+
class GlyTest < MiniTest::Test
|
11
|
+
# shortcut performing gly->gabc conversion and returning
|
12
|
+
# it's results
|
13
|
+
def gly_process(gly_io)
|
14
|
+
doc = Gly::Parser.new.parse(gly_io)
|
15
|
+
Gly::GabcConvertor.new.convert(doc.scores[0])
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jakub Pavlík
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest-reporters
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
description: "# gly\n\nWriter-friendly Gregorian notation format compiling to gabc.\n\n*GLY*
|
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 crashes on headers it doesn't know)\n* several
|
53
|
+
scores per file (when compiled to gabc, each becomes\n a separate file)\n* compile
|
54
|
+
pdf preview by a single command, without writing any (La)TeX\n\n## Examples\n\nTypical
|
55
|
+
GABC source of an antiphon looks like this:\n\n name: Nativitas gloriosae;\n
|
56
|
+
\ office-part: laudes, 1. ant.;\n occasion: In Nativitate B. Mariae Virginis;\n
|
57
|
+
\ book: Antiphonale Romanum 1912, pg. 704;\n mode: 8;\n initial-style: 1;\n
|
58
|
+
\ %%\n \n (c4) NA(g)TI(g)VI(g)TAS(gd) glo(f)ri(gh)ó(g)sae(g) * (,)\n Vír(g)gi(g)nis(hi)
|
59
|
+
Ma(gh)rí(gf)ae,(f) (;)\n ex(f) sé(g)mi(h)ne(h) A(hiwji)bra(hg)hae,(g) (;)\n or(gh~)tae(g)
|
60
|
+
de(g) tri(g)bu(fe/fgf) Ju(d)da,(d) (;)\n cla(df!gh)ra(g) ex(f) stir(hg~)pe(hi)
|
61
|
+
Da(h)vid.(g) (::)\n\nCorresponding GLY may look like this:\n\n name: Nativitas
|
62
|
+
gloriosae\n office-part: laudes, 1. ant.\n occasion: In Nativitate B. Mariae
|
63
|
+
Virginis\n book: Antiphonale Romanum 1912, pg. 704\n mode: 8\n initial-style:
|
64
|
+
1\n \n c4 g g g gd f gh g g ,\n g g hi gh gf f ;\n f g h h hiwji hg
|
65
|
+
g ;\n gh~ g g g fe/fgf d d ;\n df!gh g f hg~ hi h g ::\n \n NA -- TI
|
66
|
+
-- VI -- TAS glo -- ri -- ósae *\n Vír -- gi -- nis Ma -- rí -- ae,\n ex sé
|
67
|
+
-- mi -- ne A -- bra -- hae,\n or -- tae de tri -- bu Ju -- da,\n cla -- ra
|
68
|
+
ex stir -- pe Da -- vid.\n\nOr, with music and lyrics interlaced:\n\n name: Nativitas
|
69
|
+
gloriosae\n office-part: laudes, 1. ant.\n occasion: In Nativitate B. Mariae
|
70
|
+
Virginis\n book: Antiphonale Romanum 1912, pg. 704\n mode: 8\n initial-style:
|
71
|
+
1\n \n c4 g g g gd f gh g g ,\n NA -- TI -- VI -- TAS glo -- ri -- ósae
|
72
|
+
*\n \n g g hi gh gf f ;\n Vír -- gi -- nis Ma -- rí -- ae,\n \n f
|
73
|
+
g h h hiwji hg g ;\n ex sé -- mi -- ne A -- bra -- hae,\n \n gh~ g g g
|
74
|
+
fe/fgf d d ;\n or -- tae de tri -- bu Ju -- da,\n \n df!gh g f hg~ hi h
|
75
|
+
g ::\n cla -- ra ex stir -- pe Da -- vid.\n\nOther arrangements are also possible.
|
76
|
+
Order of music and lyrics\nis actually ignored during processing.\n\n## Usage\n\nThis
|
77
|
+
gem provides executable `gly`. Run `gly help` for full list\nof subcommands. The
|
78
|
+
most important ones are:\n\n`gly gabc FILE1 ...`\n\nconverts given gly file(s) to
|
79
|
+
one or more gabc files (one per score,\ni.e. one gly may spawn a bunch of gabcs).\n\n`gly
|
80
|
+
preview FILE1 ...`\n\nAttempts to create a pdf document with all scores contained
|
81
|
+
in each\ngly file. Expects gregorio and lualatex to be in PATH\nand gregoriotex
|
82
|
+
to be installed and accessible by lualatex.\n\n## Tools\n\n[Emacs mode with syntax
|
83
|
+
highlighting for gly][elisp]\n\n![Editing gly in emacs](/doc/img/gly_emacs_scr.png)\n\n##
|
84
|
+
Run tests\n\nby executing `tests/run.rb`\n\n## License\n\nMIT\n\n[gregorio]: https://github.com/gregorio-project/gregorio\n[elisp]:
|
85
|
+
/tree/master/elisp\n"
|
86
|
+
email: jkb.pavlik@gmail.com
|
87
|
+
executables:
|
88
|
+
- gly
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- bin/gly
|
93
|
+
- bin/glyfy
|
94
|
+
- lib/gly.rb
|
95
|
+
- lib/gly.rb~
|
96
|
+
- lib/gly/cli.rb
|
97
|
+
- lib/gly/cli.rb~
|
98
|
+
- lib/gly/document.rb
|
99
|
+
- lib/gly/document.rb~
|
100
|
+
- lib/gly/gabc_convertor.rb
|
101
|
+
- lib/gly/gabc_convertor.rb~
|
102
|
+
- lib/gly/headers.rb
|
103
|
+
- lib/gly/headers.rb~
|
104
|
+
- lib/gly/lyrics.rb
|
105
|
+
- lib/gly/lyrics.rb~
|
106
|
+
- lib/gly/parsed_score.rb
|
107
|
+
- lib/gly/parsed_score.rb~
|
108
|
+
- lib/gly/parser.rb
|
109
|
+
- lib/gly/parser.rb~
|
110
|
+
- lib/gly/string_helpers.rb
|
111
|
+
- lib/gly/string_helpers.rb~
|
112
|
+
- tests/examples.rb
|
113
|
+
- tests/examples.rb~
|
114
|
+
- tests/examples/gly/expected/block_lyrics.gabc
|
115
|
+
- tests/examples/gly/expected/differentiae.gabc
|
116
|
+
- tests/examples/gly/expected/differentiae.gabc~
|
117
|
+
- tests/examples/gly/expected/document_header.gabc
|
118
|
+
- tests/examples/gly/expected/document_header.gabc~
|
119
|
+
- tests/examples/gly/expected/empty.gabc
|
120
|
+
- tests/examples/gly/expected/explicit_lyrics.gabc
|
121
|
+
- tests/examples/gly/expected/explicit_lyrics.gabc~
|
122
|
+
- tests/examples/gly/expected/explicit_score.gabc
|
123
|
+
- tests/examples/gly/expected/force_divisio_lyrics.gabc
|
124
|
+
- tests/examples/gly/expected/header.gabc
|
125
|
+
- tests/examples/gly/expected/header_unofficial.gabc
|
126
|
+
- tests/examples/gly/expected/header_unofficial.gabc~
|
127
|
+
- tests/examples/gly/expected/multiline.gabc
|
128
|
+
- tests/examples/gly/expected/multiline_interlaced.gabc
|
129
|
+
- tests/examples/gly/expected/nonlyrical_lyrics.gabc
|
130
|
+
- tests/examples/gly/expected/nonlyrical_lyrics.gabc~
|
131
|
+
- tests/examples/gly/expected/notes_bracketted.gabc
|
132
|
+
- tests/examples/gly/expected/notes_bracketted.gabc~
|
133
|
+
- tests/examples/gly/expected/notes_only.gabc
|
134
|
+
- tests/examples/gly/expected/notes_with_lyrics.gabc
|
135
|
+
- tests/examples/gly/expected/notes_with_lyrics.gabc~
|
136
|
+
- tests/examples/gly/expected/unsyllabified_lyrics.gabc
|
137
|
+
- tests/examples/gly/given/block_lyrics.gly
|
138
|
+
- tests/examples/gly/given/differentiae.gly
|
139
|
+
- tests/examples/gly/given/differentiae.gly~
|
140
|
+
- tests/examples/gly/given/document_header.gly
|
141
|
+
- tests/examples/gly/given/empty.gly
|
142
|
+
- tests/examples/gly/given/explicit_lyrics.gly
|
143
|
+
- tests/examples/gly/given/explicit_score.gly
|
144
|
+
- tests/examples/gly/given/force_divisio_lyrics.gly
|
145
|
+
- tests/examples/gly/given/header.gly
|
146
|
+
- tests/examples/gly/given/header_unofficial.gly
|
147
|
+
- tests/examples/gly/given/header_unofficial.gly~
|
148
|
+
- tests/examples/gly/given/multiline.gly
|
149
|
+
- tests/examples/gly/given/multiline_interlaced.gly
|
150
|
+
- tests/examples/gly/given/multiline_interlaced.gly~
|
151
|
+
- tests/examples/gly/given/nonlyrical_lyrics.gly
|
152
|
+
- tests/examples/gly/given/nonlyrical_lyrics.gly~
|
153
|
+
- tests/examples/gly/given/notes_bracketted.gabc~
|
154
|
+
- tests/examples/gly/given/notes_bracketted.gly
|
155
|
+
- tests/examples/gly/given/notes_only.gly
|
156
|
+
- tests/examples/gly/given/notes_with_lyrics.gly
|
157
|
+
- tests/examples/gly/given/unsyllabified_lyrics.gly
|
158
|
+
- tests/examples/gly/no_crash/document_header.gly~
|
159
|
+
- tests/examples/gly/no_crash/multiple_scores.gly
|
160
|
+
- tests/examples/gly/no_crash/multiple_scores.gly~
|
161
|
+
- tests/no_crash.rb
|
162
|
+
- tests/programmed_examples.rb~
|
163
|
+
- tests/run.rb
|
164
|
+
- tests/run.rb~
|
165
|
+
- tests/string_helpers_test.rb
|
166
|
+
- tests/string_helpers_test.rb~
|
167
|
+
- tests/test_helper.rb
|
168
|
+
homepage: http://github.com/igneus/gly
|
169
|
+
licenses:
|
170
|
+
- MIT
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
requirements: []
|
187
|
+
rubyforge_project:
|
188
|
+
rubygems_version: 2.2.2
|
189
|
+
signing_key:
|
190
|
+
specification_version: 4
|
191
|
+
summary: Writer-friendly Gregorian notation format compiling to gabc
|
192
|
+
test_files: []
|
193
|
+
has_rdoc:
|