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.
- checksums.yaml +7 -0
- data/bin/pslm.rb +150 -0
- data/lib/pslm.rb +17 -0
- data/lib/pslm/consoleoutputter.rb +281 -0
- data/lib/pslm/joinedinput.rb +42 -0
- data/lib/pslm/latexoutputter.rb +449 -0
- data/lib/pslm/outputter.rb +47 -0
- data/lib/pslm/psalm.rb +149 -0
- data/lib/pslm/psalmpatterns.rb +97 -0
- data/lib/pslm/psalmpointer.rb +89 -0
- data/lib/pslm/psalmtones/solesmes196x.yml +22 -0
- data/lib/pslm/pslmoutputter.rb +71 -0
- data/lib/pslm/pslmreader.rb +225 -0
- data/lib/pslm/structuredsetup.rb +74 -0
- metadata +114 -0
@@ -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: []
|