pslm 0.0.0

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.
@@ -0,0 +1,225 @@
1
+ require 'stringio'
2
+ require 'delegate'
3
+
4
+ module Pslm
5
+ # reads pslm files, provides their parsed data
6
+ class PslmReader
7
+
8
+ # public interface
9
+
10
+ def read_str(str, has_title=true, autodetect=true)
11
+ return load_psalm(CountingIStream.new(StringIO.new(str)), has_title, autodetect)
12
+ end
13
+
14
+ def read_file(fname, has_title=true, autodetect=true)
15
+ return load_psalm(CountingIStream.new(File.open(str)), has_title, autodetect)
16
+ end
17
+
18
+ attr_reader :header
19
+
20
+ # the following methods may be safely used, but aren't intended to
21
+
22
+ # has_title - does the psalm text begin with a title line and an empty line?
23
+ # autodetect - check if there really is a title to prevent wrong interpretation
24
+ def load_psalm(istream, has_title=true, autodetect=true)
25
+ ps = Pslm::Psalm.new
26
+
27
+ ps.header.title = load_title(istream, has_title, autodetect)
28
+
29
+ while v = load_verse(istream) do
30
+ if v == '' then
31
+ ps.add_strophe
32
+ else
33
+ ps.add_verse v
34
+ end
35
+ end
36
+
37
+ return ps
38
+ end
39
+
40
+ # verse parts: order, names, distinguishing regexes
41
+ VERSE_PARTS = [
42
+ { :name => :flex, :method => :flex=, :regex => /\+\s*$/ },
43
+ { :name => :first, :method => :first=, :regex => /\*\s*$/ },
44
+ { :name => :second, :method => :second=, :regex => /[^\s\*\+]\s*$/ }
45
+ ]
46
+
47
+ def load_verse(istream)
48
+ v = Psalm::Verse.new do |verse|
49
+ part_loaded = gets_drop_comments istream
50
+ VERSE_PARTS.each_with_index do |part, i|
51
+ if part_loaded == nil then
52
+ return part_loaded # eof
53
+ end
54
+
55
+ # empty line is strophe delimiter - return empty string to signal new strophe
56
+ while part_loaded =~ /^\s*$/ do
57
+ return ''
58
+ end
59
+
60
+ unless part_loaded =~ part[:regex]
61
+ if part[:name] == :flex then
62
+ next # probably a verse without flex - try to read the loaded line as a first half-verse
63
+ end
64
+
65
+ if istream.respond_to? :lineno then
66
+ raise PslmSyntaxError.new("Unexpected verse part on line #{istream.lineno}: \"#{part_loaded}\" Expecting #{part[:name]}")
67
+ else
68
+ raise PslmSyntaxError.new("Unexpected verse part: \"#{part_loaded}\" Expecting #{part[:name]}")
69
+ end
70
+ end
71
+
72
+ part_src = part_loaded.dup
73
+ if [:flex, :first].include? part[:name] then
74
+ part_loaded.sub!(/[\+\*]\s*$/, '') # there should be only one of these chars, at the very end of the line
75
+ part_loaded.strip!
76
+ end
77
+
78
+ # split words, be aware of accents (space between accent parenthesis doesn't break word)
79
+ words_raw = []
80
+ in_accent = false
81
+ word_start = 0
82
+ part_loaded.chars.each_with_index do |c, ci|
83
+ if c == '[' then
84
+ in_accent = true
85
+ elsif c == ']' then
86
+ in_accent = false
87
+ elsif in_accent == false && c == ' ' then
88
+ words_raw << part_loaded[word_start .. ci-1]
89
+ word_start = ci+1
90
+ end
91
+ end
92
+ words_raw << part_loaded[word_start .. -1] # last word
93
+
94
+ words = words_raw.collect do |w|
95
+ sylls = []
96
+ while w.size > 0 do
97
+ i = w.index(/[\/\[\]]/)
98
+ if i == nil then # last syllable
99
+ sylls << Psalm::Syllable.new(syll_strip_special_chars(w))
100
+ break
101
+ end
102
+ s = w.slice!(0..i)
103
+ accent = (s[-1] == ']')
104
+ s = s[0..-2] # discard the delimiter
105
+ next if s == '' # delimiter at the beginning/end of the string
106
+ sylls << Psalm::Syllable.new(syll_strip_special_chars(s), accent)
107
+ end
108
+ Psalm::Word.new(sylls)
109
+ end
110
+ versepart = Psalm::VersePart.new(words, part_src, part[:name])
111
+
112
+ verse.send(part[:method], versepart)
113
+
114
+ unless part[:name] == :second
115
+ part_loaded = gets_drop_comments istream
116
+ end
117
+ end
118
+ end
119
+
120
+ return v
121
+ end
122
+
123
+ private
124
+
125
+ # loads psalm title;
126
+ # unless autodetect is false, a check is always done, if there is any title to load
127
+ def load_title(istream, has_title, autodetect)
128
+ if not autodetect and not has_title then
129
+ return ''
130
+ end
131
+
132
+ title = gets_drop_comments istream
133
+ after_title = gets_drop_comments istream
134
+
135
+ unless autodetect then
136
+ return title
137
+ end
138
+
139
+ # TODO real logging, not direct output to STDERR
140
+ if after_title =~ /^\s*$/ then
141
+ if not has_title then
142
+ STDERR.puts "The psalm shouldn't have a title, but it has one."
143
+ end
144
+ else
145
+ title = ''
146
+ istream.rewind
147
+
148
+ if has_title then
149
+ STDERR.puts "The psalm should have a title, but none was found."
150
+ end
151
+ end
152
+
153
+ return title
154
+ end
155
+
156
+ # gets next line from the input stream with comments dropped;
157
+ # lines containing only whitespace+comment are thrown away
158
+ def gets_drop_comments(istream)
159
+ l = istream.gets
160
+ if l == nil then
161
+ return l # eof
162
+ end
163
+
164
+ l.strip!
165
+ if l == '' then
166
+ return l # strophe end
167
+ end
168
+
169
+ l = strip_comments(l)
170
+ if l == '' then
171
+ # line containing only a comment; try another one
172
+ return gets_drop_comments(istream)
173
+ end
174
+
175
+ return l
176
+ end
177
+
178
+ # anything from # to the end of line is a comment
179
+ def strip_comments(s)
180
+ ci = s.index('#')
181
+ if ci == 0 then
182
+ return ''
183
+ elsif ci != nil then
184
+ return s[0..ci-1].strip
185
+ else
186
+ return s
187
+ end
188
+ end
189
+
190
+ # removes special characters from a String before a Psalm Syllable
191
+ # is created
192
+ def syll_strip_special_chars(str)
193
+ str.gsub('_', ' ') # underscore is "syllable-not-dividing space" mark
194
+ end
195
+
196
+ public
197
+
198
+ class PslmSyntaxError < RuntimeError
199
+ end
200
+ end
201
+
202
+ # wraps an input stream; counts lines read
203
+ class CountingIStream < SimpleDelegator
204
+
205
+ def initialize(stream)
206
+ super(stream)
207
+ @stream = stream
208
+ @lineno = 0
209
+ end
210
+
211
+ attr_reader :lineno
212
+
213
+ def gets
214
+ l = @stream.gets
215
+ @lineno += 1 if l != nil
216
+ return l
217
+ end
218
+
219
+ def rewind
220
+ @lineno = 0
221
+ super
222
+ end
223
+ end
224
+
225
+ end
@@ -0,0 +1,74 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'delegate'
4
+ require 'deep_merge/core' # only the environment non polluting core of deep_merge
5
+
6
+ # Nested Hashes; automatic creation of missing nodes
7
+ class StructuredSetup < SimpleDelegator
8
+
9
+ def initialize(data={})
10
+ @data = deep_copy data
11
+ super(@data)
12
+ end
13
+
14
+ # set(:key1, :key2, :key3, value) ~ set[:key1][:key2][:key3] = value
15
+ # setter with automatic creation of missing nodes
16
+ def set(*args)
17
+ value = args.pop
18
+ last_key = args.pop
19
+
20
+ hsh = get_create(args)
21
+ hsh[last_key] = value
22
+ end
23
+
24
+ # getter with automatic creation of missing nodes
25
+ def get(*keys)
26
+ get_create(keys[0..-2])[keys[-1]]
27
+ end
28
+
29
+ # getter with default value
30
+ def get_dv(*args)
31
+ default = args.pop
32
+ innermost = get_create(args[0..-2])
33
+ unless innermost.has_key? args[-1] then
34
+ innermost[args[-1]] = default
35
+ end
36
+
37
+ return innermost[args[-1]]
38
+ end
39
+
40
+ def dup
41
+ deep_copy self
42
+ end
43
+
44
+ def update(s2)
45
+ DeepMerge.deep_merge! s2, @data
46
+ end
47
+
48
+ private
49
+
50
+ # returns the innermost Hash
51
+ def get_create(keys)
52
+ keys = keys.dup
53
+ hsh = @data
54
+ while keys.size > 0 do
55
+ key = keys.shift
56
+
57
+ unless hsh.has_key? key
58
+ hsh[key] = {}
59
+ end
60
+
61
+ unless hsh[key].is_a? Hash
62
+ raise RuntimeError.new "Cannot set to key '#{key}': it's value is #{hsh[key].class}, not Hash. Keys were #{keys}"
63
+ end
64
+
65
+ hsh = hsh[key]
66
+ end
67
+
68
+ return hsh
69
+ end
70
+
71
+ def deep_copy(obj)
72
+ Marshal.load(Marshal.dump(obj))
73
+ end
74
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pslm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jakub Pavlík
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
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: deep_merge
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email: jkb.pavlik@gmail.com
71
+ executables:
72
+ - pslm.rb
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - lib/pslm.rb
77
+ - lib/pslm/psalmpointer.rb
78
+ - lib/pslm/joinedinput.rb
79
+ - lib/pslm/consoleoutputter.rb
80
+ - lib/pslm/structuredsetup.rb
81
+ - lib/pslm/pslmoutputter.rb
82
+ - lib/pslm/pslmreader.rb
83
+ - lib/pslm/psalmpatterns.rb
84
+ - lib/pslm/latexoutputter.rb
85
+ - lib/pslm/psalm.rb
86
+ - lib/pslm/outputter.rb
87
+ - lib/pslm/psalmtones/solesmes196x.yml
88
+ - bin/pslm.rb
89
+ homepage: http://github.com/igneus/pslm
90
+ licenses:
91
+ - LGPL-3.0
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.1.11
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: plain text psalm format formatter utility and library
114
+ test_files: []