gly 0.0.1
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.
- 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\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:
|