gly 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gly.rb +4 -1
  3. data/lib/gly/cli.rb +71 -8
  4. data/lib/gly/document.rb +20 -8
  5. data/lib/gly/document_gabc_convertor.rb +10 -2
  6. data/lib/gly/exceptions.rb +16 -0
  7. data/lib/gly/exceptions.rb~ +3 -0
  8. data/lib/gly/gabc_convertor.rb +29 -11
  9. data/lib/gly/gly_convertor.rb +52 -0
  10. data/lib/gly/gly_convertor.rb~ +11 -0
  11. data/lib/gly/glyfy_cli.rb~ +92 -0
  12. data/lib/gly/headers.rb +3 -1
  13. data/lib/gly/lister.rb +41 -6
  14. data/lib/gly/lyrics.rb +7 -7
  15. data/lib/gly/markup.rb +15 -0
  16. data/lib/gly/markup.rb~ +11 -0
  17. data/lib/gly/parser.rb +65 -23
  18. data/lib/gly/preview_builder.rb +13 -4
  19. data/lib/gly/preview_generator.rb +44 -23
  20. data/lib/gly/{parsed_score.rb → score.rb} +1 -1
  21. data/lib/gly/templates/lualatex_document.tex +11 -11
  22. data/tests/examples.rb +27 -7
  23. data/tests/examples/gly/expected/block_lyrics.gabc +1 -1
  24. data/tests/examples/gly/expected/block_lyrics.gabc~ +2 -0
  25. data/tests/examples/gly/expected/block_music.gabc +2 -0
  26. data/tests/examples/gly/expected/block_music.gabc~ +4 -0
  27. data/tests/examples/gly/expected/blocks.gabc +3 -0
  28. data/tests/examples/gly/expected/differentia_final.gabc +2 -0
  29. data/tests/examples/gly/expected/empty_lyric_syllable.gabc +2 -0
  30. data/tests/examples/gly/expected/empty_lyric_syllable.gabc~ +1 -0
  31. data/tests/examples/gly/expected/explicit_lyrics.gabc +1 -1
  32. data/tests/examples/gly/expected/header_colon.gabc +3 -0
  33. data/tests/examples/gly/expected/header_colon.gabc~ +3 -0
  34. data/tests/examples/gly/expected/header_empty.gabc +2 -0
  35. data/tests/examples/gly/expected/header_empty.gabc~ +2 -0
  36. data/tests/examples/gly/expected/nabc.gabc +3 -0
  37. data/tests/examples/gly/expected/no_clef.gabc +2 -0
  38. data/tests/examples/gly/expected/no_clef.gabc~ +1 -0
  39. data/tests/examples/gly/expected/real1.gabc~ +15 -0
  40. data/tests/examples/gly/expected/unsingables.gabc +2 -0
  41. data/tests/examples/gly/expected/unsingables.gabc~ +2 -0
  42. data/tests/examples/gly/given/block_lyrics.gly~ +3 -0
  43. data/tests/examples/gly/given/block_music.gly +4 -0
  44. data/tests/examples/gly/given/blocks.gly +6 -0
  45. data/tests/examples/gly/given/differentia_final.gly +5 -0
  46. data/tests/examples/gly/given/differentia_final.gly~ +2 -0
  47. data/tests/examples/gly/given/empty_lyric_syllable.gly +2 -0
  48. data/tests/examples/gly/given/empty_lyric_syllable.gly~ +2 -0
  49. data/tests/examples/gly/given/header_colon.gly +3 -0
  50. data/tests/examples/gly/given/header_colon.gly~ +4 -0
  51. data/tests/examples/gly/given/header_empty.gly +1 -0
  52. data/tests/examples/gly/given/header_empty.gly~ +2 -0
  53. data/tests/examples/gly/given/nabc.gly +3 -0
  54. data/tests/examples/gly/given/nabc.gly~ +2 -0
  55. data/tests/examples/gly/given/no_clef.gly +2 -0
  56. data/tests/examples/gly/given/no_clef.gly~ +1 -0
  57. data/tests/examples/gly/given/real1.gly~ +18 -0
  58. data/tests/examples/gly/given/unsingables.gly +2 -0
  59. data/tests/examples/gly/given/unsingables.gly~ +2 -0
  60. data/tests/examples/glyfy/expected/header.gly +3 -0
  61. data/tests/examples/glyfy/expected/header.gly~ +2 -0
  62. data/tests/examples/glyfy/expected/simple.gly +3 -0
  63. data/tests/examples/glyfy/expected/simple.gly~ +2 -0
  64. data/tests/examples/glyfy/given/header.gabc +3 -0
  65. data/tests/examples/glyfy/given/simple.gabc +2 -0
  66. data/tests/examples/parser/markup_after_header.gly +4 -0
  67. data/tests/examples/parser/markups.gly +10 -0
  68. data/tests/examples/parser/markups.gly~ +5 -0
  69. data/tests/examples/parser/score_markup_order.gly +19 -0
  70. data/tests/examples/parser/score_markup_order.gly~ +13 -0
  71. data/tests/parser.rb +51 -0
  72. data/tests/parser.rb~ +12 -0
  73. data/tests/run.rb +1 -0
  74. data/tests/test_helper.rb +19 -0
  75. metadata +59 -10
  76. data/bin/glyfy +0 -4
  77. data/tests/examples.rb~ +0 -9
  78. data/tests/programmed_examples.rb~ +0 -14
  79. data/tests/run.rb~ +0 -3
  80. data/tests/string_helpers_test.rb~ +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2057f1f3a833d1b6bb382573386d7519fd1f83a
4
- data.tar.gz: 2c770e056bc7c3c0a6a7aeb689a9888615334f17
3
+ metadata.gz: 3a2ffe57b4f0e19ec4b23c18288c6ebc3f72409d
4
+ data.tar.gz: 0f12ea9c6bb6d325e3b988c78229ffad8bc38a03
5
5
  SHA512:
6
- metadata.gz: 6860d75f1e55af109e10535f76b404d074b6bc9bafad0b13784f98ca706f448705a1116216598052b364802e6c7e3b3b4dde87557d5926d13417dbb69ab488ba
7
- data.tar.gz: fd1df56ea96ca42c779086e8b495fcb4da4dddeb77fa13a87238506dd5044802f9486ba220a617efef925671fee73afa7cb98f620ea9f508c7df70c0a7df0180
6
+ metadata.gz: ea90186703af9c3aa87388f876ef6e7332d7b78ef9babd564a28fa04318051a0b418b705eb5a5f7cda2bd8e8ea22a6e8cda7162b9be7d4f3baccf72a67b3c20e
7
+ data.tar.gz: 03ebc311afc420dfbebe8bd808c72be8884c3140fde9d802ccfdb8c33525efae27f3fa53203d959963b6c78cad331f56948bd422624953a332b8d3911f1a8a04
data/lib/gly.rb CHANGED
@@ -1,13 +1,16 @@
1
1
  module Gly; end
2
2
 
3
3
  %w(parser
4
+ exceptions
4
5
  gabc_convertor
5
- parsed_score
6
+ score
6
7
  headers
7
8
  lyrics
9
+ markup
8
10
  document
9
11
  document_gabc_convertor
10
12
  document_ly_convertor
13
+ gly_convertor
11
14
  preview_generator
12
15
  preview_builder
13
16
  lister
@@ -15,6 +15,8 @@ module Gly
15
15
  files.each do |f|
16
16
  DocumentGabcConvertor.new(parser.parse(f)).convert
17
17
  end
18
+ rescue Gly::Exception => ex
19
+ error_exit! ex
18
20
  end
19
21
 
20
22
  desc 'preview FILE ...', 'convert to gabc AND generate pdf preview'
@@ -24,19 +26,42 @@ module Gly
24
26
  option :template, aliases: :t, banner: 'use custom document template'
25
27
  def preview(*files)
26
28
  tpl = nil
27
- tpl = File.read(options[:template]) if options[:template]
29
+ if options[:template]
30
+ begin
31
+ tpl = File.read(options[:template])
32
+ rescue Errno::ENOENT
33
+ raise Gly::Exception.new("File not found: '#{options[:template]}'")
34
+ end
35
+ end
36
+
37
+ # convert HashWithIndifferentAccess to Hash
38
+ # with Symbol keys - options.to_h would make Hash
39
+ # with String keys
40
+ opts = options.each_pair.collect {|k,v| [k.to_sym,v]}.to_h
28
41
 
29
- opts = options.to_h
30
42
  opts[:suffix_always] = true
31
43
 
32
44
  files.each do |f|
33
45
  gen = PreviewGenerator.new template: tpl, options: opts
34
- gen.process(parser.parse(f))
46
+ begin
47
+ gen.process(parser.parse(f))
48
+ rescue Errno::ENOENT
49
+ with_extension = f + '.gly'
50
+ if File.exist? with_extension
51
+ gen.process(parser.parse(with_extension))
52
+ else
53
+ raise Gly::Exception.new("File not found: '#{f}'")
54
+ end
55
+ end
35
56
  end
57
+
58
+ rescue Gly::Exception => ex
59
+ error_exit! ex
36
60
  end
37
61
 
38
62
  desc 'list FILE ...', 'list scores contained in files'
39
63
  option :recursive, type: :boolean, aliases: :r, banner: 'recursively traverse directories', default: false
64
+ option :format, type: :string, aliases: :f, banner: 'grep|overview'
40
65
  def list(*files)
41
66
  if files.empty?
42
67
  STDERR.puts 'No file specified.'
@@ -54,22 +79,48 @@ module Gly
54
79
  files.flatten!
55
80
  end
56
81
 
57
- lister = Lister.new(files)
82
+ lister = Lister.new(files, options[:format])
58
83
  lister.list(STDOUT, STDERR)
59
84
 
60
85
  exit(lister.error? ? 1 : 0)
86
+ rescue Gly::Exception => ex
87
+ error_exit! ex
61
88
  end
62
89
 
63
90
  desc 'ly FILE ...', 'transform gly document to lilypond document'
64
91
  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
92
+ check_lygre_available!
69
93
 
70
94
  files.each do |f|
71
95
  DocumentLyConvertor.new(parser.parse(f)).convert
72
96
  end
97
+
98
+ rescue Gly::Exception => ex
99
+ error_exit! ex
100
+ end
101
+
102
+ desc 'fy FILE ...', 'transform gabc to gly'
103
+ def fy(*files)
104
+ check_lygre_available!
105
+
106
+ files.each_with_index do |f,i|
107
+ input = File.read f
108
+
109
+ parser = GabcParser.new
110
+ result = parser.parse(input)
111
+
112
+ if result then
113
+ puts if i >= 1
114
+ GlyConvertor.new.convert result.create_score, STDOUT
115
+ else
116
+ STDERR.puts 'glyfy considers the input invalid gabc:'
117
+ STDERR.puts
118
+ STDERR.puts "'#{parser.failure_reason}' on line #{parser.failure_line} column #{parser.failure_column}:"
119
+ STDERR.puts input.split("\n")[parser.failure_line-1]
120
+ STDERR.puts (" " * parser.failure_column) + "^"
121
+ return false
122
+ end
123
+ end
73
124
  end
74
125
 
75
126
  private
@@ -78,6 +129,13 @@ module Gly
78
129
  Parser.new options[:separator]
79
130
  end
80
131
 
132
+ def check_lygre_available!
133
+ unless defined? LilypondConvertor
134
+ STDERR.puts "'lygre' gem not found. Please, install lygre in order to run 'gly ly'."
135
+ exit 1
136
+ end
137
+ end
138
+
81
139
  class << self
82
140
  # override Thor's default handler
83
141
  def handle_no_command_error(command, has_namespace=$thor_runner)
@@ -88,5 +146,10 @@ module Gly
88
146
  end
89
147
  end
90
148
  end
149
+
150
+ def error_exit!(exception)
151
+ STDERR.puts 'ERROR: ' + exception.message
152
+ exit 1
153
+ end
91
154
  end
92
155
  end
@@ -5,24 +5,36 @@ module Gly
5
5
  class Document
6
6
  def initialize
7
7
  @scores = []
8
+ @content = []
8
9
  @scores_by_id = {}
9
10
  @header = Headers.new
10
11
  @path = nil
11
12
  end
12
13
 
13
- attr_reader :scores, :header
14
+ # only Scores
15
+ attr_reader :scores
16
+
17
+ # Scores and Markups
18
+ attr_reader :content
19
+
20
+ attr_reader :header
14
21
  attr_accessor :path
15
22
 
23
+ # append a new Score (or Markup)
16
24
  def <<(score)
17
- @scores << score
25
+ @content << score
18
26
 
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
27
+ if score.is_a? Score
28
+ @scores << score
24
29
 
25
- @scores_by_id[sid] = score
30
+ sid = score.headers['id']
31
+ if sid
32
+ if @scores_by_id.has_key? sid
33
+ raise ArgumentError.new("More than one score with id '#{sid}'.")
34
+ end
35
+
36
+ @scores_by_id[sid] = score
37
+ end
26
38
  end
27
39
 
28
40
  self
@@ -17,14 +17,18 @@ module Gly
17
17
  # iterates over document scores,
18
18
  # yields score and filename of it's generated gabc file
19
19
  def each_score_with_gabcname
20
+ return to_enum(:each_score_with_gabcname) unless block_given?
21
+
20
22
  @doc.scores.each_with_index do |score, si|
21
- yield score, output_fname(score, si)
23
+ gabc = gabc_fname(score, si)
24
+ gtex = gtex_fname(score, si)
25
+ yield score, gabc, gtex
22
26
  end
23
27
  end
24
28
 
25
29
  private
26
30
 
27
- def output_fname(score, score_index=nil)
31
+ def gabc_fname(score, score_index=nil)
28
32
  if @doc.scores.size == 1 && !@options[:suffix_always]
29
33
  score_id = ''
30
34
  else
@@ -34,5 +38,9 @@ module Gly
34
38
  File.basename(@doc.path)
35
39
  .sub(/\.gly\Z/i, "#{score_id}.gabc")
36
40
  end
41
+
42
+ def gtex_fname(score, score_index=nil)
43
+ gabc_fname(score, score_index).sub('.gabc', '.gtex')
44
+ end
37
45
  end
38
46
  end
@@ -0,0 +1,16 @@
1
+ module Gly
2
+ # general application-specific error
3
+ class Exception < StandardError
4
+ def self.wrap(exception)
5
+ e = new exception.message
6
+ e.wrapped_exception = exception
7
+ e
8
+ end
9
+
10
+ # may be nil
11
+ attr_accessor :wrapped_exception
12
+ end
13
+
14
+ # error when parsing gly input file
15
+ class ParseError < Exception; end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Gly
2
+ class Exception < StandardError; end
3
+ end
@@ -1,7 +1,7 @@
1
1
  require 'stringio'
2
2
 
3
3
  module Gly
4
- # takes ParsedScore, translates it to gabc
4
+ # takes Score, translates it to gabc
5
5
  class GabcConvertor
6
6
  def convert(score, out=StringIO.new)
7
7
  score.headers.each_pair do |key,value|
@@ -12,43 +12,61 @@ module Gly
12
12
  out.puts '%%'
13
13
 
14
14
  lyric_enum = score.lyrics.each_syllable.to_enum
15
+
15
16
  score.music.each_with_index do |mus_chunk,i|
16
17
  begin
17
18
  next_syl = lyric_enum.peek
19
+ out.print lyric_enum.next if next_syl == ' '
18
20
  rescue StopIteration
19
21
  next_syl = ''
20
- end
22
+ end until next_syl != ' '
21
23
 
22
- if clef?(mus_chunk) ||
23
- (nonlyrical_chunk?(mus_chunk) && ! nonlyrical_lyrics?(next_syl))
24
- # music chunk normally not having lyrics
25
- out.print ' ' if i != 0
24
+ if no_lyrics? mus_chunk, next_syl
25
+ if i == score.music.size - 1
26
+ out.print ' '
27
+ end
26
28
  else
27
29
  # regular music chunk
28
30
  begin
29
31
  out.print strip_directives lyric_enum.next
30
32
  rescue StopIteration
31
- out.print ' ' if i != 0
33
+ out.print ' ' if i > 0 # don't add space at the very beginning
32
34
  end
33
35
  end
36
+
34
37
  out.print "(#{mus_chunk})"
35
- # out.puts if differentia?(mus_chunk) # newline after each differentia
38
+ if no_lyrics?(mus_chunk, next_syl) &&
39
+ i != (score.music.size - 1) &&
40
+ ! score.lyrics.empty?
41
+ out.print ' '
42
+ end
36
43
  end
37
44
 
38
45
  return out
39
46
  end
40
47
 
48
+ def no_lyrics?(music_chunk, syllable)
49
+ clef?(music_chunk) ||
50
+ (nonlyrical_chunk?(music_chunk) &&
51
+ ! nonlyrical_lyrics?(syllable))
52
+ end
53
+
41
54
  def clef?(chunk)
42
55
  chunk =~ /\A[cf][1-4]\Z/
43
56
  end
44
57
 
45
- def differentia?(chunk)
46
- chunk =~ /\A*[,;:`]+\Z/ # differentia
58
+ def without_differentiae(chunk)
59
+ chunk.gsub /(([,`])|(:[:']?)|(;[1-6]?))/, ''
60
+ end
61
+
62
+ def without_breaks(chunk)
63
+ chunk.gsub /[zZ]/, ''
47
64
  end
48
65
 
49
66
  # is the given music chunk capable of bearing lyrics?
50
67
  def nonlyrical_chunk?(chunk)
51
- differentia? chunk
68
+ chunk.size > 0 &&
69
+ without_breaks(without_differentiae(chunk)).empty?
52
70
  end
53
71
 
54
72
  def nonlyrical_lyrics?(syl)
@@ -0,0 +1,52 @@
1
+ require 'delegate'
2
+
3
+ module Gly
4
+ # converts gabc to gly
5
+ class GlyConvertor
6
+ # score - GabcScore (from the lygre gem)
7
+ def convert(score, output_stream=StringIO.new)
8
+ out = NonemptyPrinter.new output_stream
9
+ out.puts '\score'
10
+ out.puts header score
11
+ out.puts music score
12
+ out.puts lyrics score
13
+ out
14
+ end
15
+
16
+ def header(score)
17
+ score.header.each_pair.collect do |key,value|
18
+ "#{key}: #{value}"
19
+ end.join "\n"
20
+ end
21
+
22
+ def music(score)
23
+ score.music.words.collect do |word|
24
+ word.each_syllable.collect do |syl|
25
+ syl = syl.notes.collect(&:text_value).join ''
26
+ if syl.include? ' ' || syl.empty?
27
+ syl = "(#{syl})"
28
+ end
29
+ syl
30
+ end.join ' '
31
+ end.flatten.join ' '
32
+ end
33
+
34
+ def lyrics(score)
35
+ score.music.words
36
+ .collect {|w| word w }
37
+ .select {|w_str| ! w_str.empty? }
38
+ .join(' ')
39
+ end
40
+
41
+ def word(word)
42
+ word.each_syllable.collect {|s| s.lyrics }.join ' -- '
43
+ end
44
+ end
45
+
46
+ class NonemptyPrinter < SimpleDelegator
47
+ def puts(what)
48
+ return if what.nil? || what.empty?
49
+ super(what)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ module Gly
2
+ # converts gabc to gly
3
+ class GlyConvertor
4
+ # score - GabcScore (from the lygre gem)
5
+ def convert(score, out=StringIO.new)
6
+ out.puts '\header'
7
+
8
+ out
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,92 @@
1
+ require 'thor'
2
+
3
+ begin
4
+ require 'grely'
5
+ rescue LoadError
6
+ end
7
+
8
+ module Gly
9
+ # implements the 'gly' executable
10
+ class CLI < Thor
11
+ class_option :separator, aliases: :s, banner: 'syllable separator (default is double dash "--")'
12
+
13
+ desc 'gabc FILE ...', 'convert gly to gabc'
14
+ def gabc(*files)
15
+ files.each do |f|
16
+ DocumentGabcConvertor.new(parser.parse(f)).convert
17
+ end
18
+ end
19
+
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'
25
+ def preview(*files)
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
36
+ end
37
+
38
+ desc 'list FILE ...', 'list scores contained in files'
39
+ option :recursive, type: :boolean, aliases: :r, banner: 'recursively traverse directories', default: false
40
+ def list(*files)
41
+ if files.empty?
42
+ STDERR.puts 'No file specified.'
43
+ exit 1
44
+ end
45
+
46
+ if options[:recursive]
47
+ files = files.collect do |f|
48
+ if File.directory?(f)
49
+ Dir[File.join(f, '**/*.gly')]
50
+ else
51
+ f
52
+ end
53
+ end
54
+ files.flatten!
55
+ end
56
+
57
+ lister = Lister.new(files)
58
+ lister.list(STDOUT, STDERR)
59
+
60
+ exit(lister.error? ? 1 : 0)
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
91
+ end
92
+ end