gly 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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