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,42 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# "joins" several input streams, so that #gets behaves as if they were
|
4
|
+
# a single stream
|
5
|
+
class JoinedInput
|
6
|
+
|
7
|
+
# accepts any number of open IOs
|
8
|
+
def initialize(*ins)
|
9
|
+
@streams = ins
|
10
|
+
end
|
11
|
+
|
12
|
+
def gets
|
13
|
+
if @streams.empty? then
|
14
|
+
return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
l = @streams.first.gets
|
18
|
+
if l == nil
|
19
|
+
@streams.shift
|
20
|
+
return gets
|
21
|
+
end
|
22
|
+
|
23
|
+
unless l.end_with? "\n"
|
24
|
+
l += "\n"
|
25
|
+
end
|
26
|
+
|
27
|
+
return l
|
28
|
+
end
|
29
|
+
|
30
|
+
def read
|
31
|
+
@streams.collect {|s| s.read }.join "\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
def close
|
35
|
+
@streams.each {|s| s.close }
|
36
|
+
@streams = []
|
37
|
+
end
|
38
|
+
|
39
|
+
def closed?
|
40
|
+
@streams.empty?
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,449 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Pslm
|
4
|
+
|
5
|
+
# formats a psalm for LaTeX
|
6
|
+
class LatexOutputter < Outputter
|
7
|
+
|
8
|
+
# each Formatter must have it's symbol in the order, otherwise it won't be used
|
9
|
+
FORMATTER_ORDER = [
|
10
|
+
:pointing,
|
11
|
+
:break_hints,
|
12
|
+
:parts,
|
13
|
+
:strophes,
|
14
|
+
:verses,
|
15
|
+
:skip_verses,
|
16
|
+
:title,
|
17
|
+
:lettrine,
|
18
|
+
:final_add_content,
|
19
|
+
:wrapper,
|
20
|
+
:prepend_text,
|
21
|
+
:output_append_text,
|
22
|
+
:line_break_last_line,
|
23
|
+
:quote,
|
24
|
+
:mark_short_verses,
|
25
|
+
]
|
26
|
+
|
27
|
+
# takes a Psalm, returns a String with the psalm formatted
|
28
|
+
def process_psalm(psalm, opts={})
|
29
|
+
formatters = get_formatters(opts)
|
30
|
+
|
31
|
+
# build the output; on each step apply the appropriate method
|
32
|
+
# of each formatter in the given order
|
33
|
+
psalm_assembled = psalm.strophes.collect do |strophe|
|
34
|
+
process_strophe(strophe, opts, psalm, formatters)
|
35
|
+
end.join "\n"
|
36
|
+
|
37
|
+
return Formatter.format(formatters, :psalm,
|
38
|
+
psalm_assembled,
|
39
|
+
psalm)
|
40
|
+
end
|
41
|
+
|
42
|
+
alias :process :process_psalm
|
43
|
+
|
44
|
+
def process_strophe(strophe, opts, psalm, formatters=nil)
|
45
|
+
formatters = formatters || get_formatters(opts)
|
46
|
+
|
47
|
+
strophe_assembled = strophe.verses.collect do |verse|
|
48
|
+
process_verse(verse, opts, strophe, psalm, formatters)
|
49
|
+
end.delete_if {|v| v == '' }.join "\n"
|
50
|
+
|
51
|
+
return Formatter.format(formatters, :strophe,
|
52
|
+
strophe_assembled,
|
53
|
+
strophe,
|
54
|
+
psalm)
|
55
|
+
end
|
56
|
+
|
57
|
+
def process_verse(verse, opts, strophe=nil, psalm=nil, formatters=nil)
|
58
|
+
formatters = formatters || get_formatters(opts)
|
59
|
+
|
60
|
+
verse_assembled = verse.parts.collect do |part|
|
61
|
+
|
62
|
+
part_assembled = part.words.reverse.collect do |word|
|
63
|
+
|
64
|
+
word_assembled = word.syllables.reverse.collect do |syll|
|
65
|
+
Formatter.format(formatters, :syllable,
|
66
|
+
syll, syll, word, part, verse, strophe, psalm)
|
67
|
+
end.reverse.join ''
|
68
|
+
|
69
|
+
Formatter.format(formatters, :word,
|
70
|
+
word_assembled, word, part, verse, strophe, psalm)
|
71
|
+
end.reverse.join ' '
|
72
|
+
|
73
|
+
Formatter.format(formatters, :part,
|
74
|
+
part_assembled, part, verse, strophe, psalm)
|
75
|
+
end.join "\n"
|
76
|
+
|
77
|
+
return Formatter.format(formatters, :verse,
|
78
|
+
verse_assembled, verse, strophe, psalm)
|
79
|
+
end
|
80
|
+
|
81
|
+
# takes a Hash of options,
|
82
|
+
# returns a list of accordingly initialized Formatter instances
|
83
|
+
def get_formatters(options)
|
84
|
+
return FORMATTER_ORDER.collect do |f|
|
85
|
+
next unless options.include? f
|
86
|
+
next unless options[f]
|
87
|
+
|
88
|
+
get_formatter(f, options[f])
|
89
|
+
end.compact!
|
90
|
+
end
|
91
|
+
|
92
|
+
# takes a Symbol - name of a configuration option, and option/s value;
|
93
|
+
# returns an instance of a corresponding Formatter class or nil
|
94
|
+
def get_formatter(sym, options)
|
95
|
+
cls_name = sym.to_s.gsub(/_(\w)/) {|m| m[1].upcase }
|
96
|
+
cls_name[0] = cls_name[0].upcase
|
97
|
+
cls_name += 'Formatter'
|
98
|
+
|
99
|
+
if self.class.const_defined? cls_name then
|
100
|
+
return self.class.const_get(cls_name).new(options)
|
101
|
+
else
|
102
|
+
#STDERR.puts "formatter #{cls_name} not found"
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# abstract superclass of the formatters providing dumb implementation
|
108
|
+
# of all formatting methods
|
109
|
+
class Formatter
|
110
|
+
|
111
|
+
class << self
|
112
|
+
# lets all the :formatters: subsequently format :text: assembled from :obj: on the assembly :level:
|
113
|
+
def format(formatters, level, text, *args)
|
114
|
+
formatters.each do |f|
|
115
|
+
text = f.send("#{level}_format", text, *args)
|
116
|
+
end
|
117
|
+
return text
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize(options)
|
122
|
+
@options = options
|
123
|
+
@syll_counter = 0
|
124
|
+
@word_counter = 0
|
125
|
+
@part_counter = 0
|
126
|
+
@verse_counter = 0
|
127
|
+
end
|
128
|
+
|
129
|
+
def psalm_format(text, psalm)
|
130
|
+
@syll_counter = 0
|
131
|
+
@word_counter = 0
|
132
|
+
@part_counter = 0
|
133
|
+
@verse_counter = 0
|
134
|
+
text
|
135
|
+
end
|
136
|
+
|
137
|
+
def strophe_format(text, strophe, psalm)
|
138
|
+
text
|
139
|
+
end
|
140
|
+
|
141
|
+
def verse_format(text, verse, strophe, psalm)
|
142
|
+
@verse_counter += 1
|
143
|
+
@syll_counter = 0
|
144
|
+
@word_counter = 0
|
145
|
+
@part_counter = 0
|
146
|
+
text
|
147
|
+
end
|
148
|
+
|
149
|
+
def part_format(text, part, verse, strophe, psalm)
|
150
|
+
@part_counter += 1
|
151
|
+
@syll_counter = 0
|
152
|
+
@word_counter = 0
|
153
|
+
text
|
154
|
+
end
|
155
|
+
|
156
|
+
def word_format(text, word, part, verse, strophe, psalm)
|
157
|
+
@word_counter += 1
|
158
|
+
text
|
159
|
+
end
|
160
|
+
|
161
|
+
def syllable_format(text, syll, word, part, verse, strophe, psalm)
|
162
|
+
@syll_counter += 1
|
163
|
+
text
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# marks accentuated and preparatory syllables
|
168
|
+
class PointingFormatter < Formatter
|
169
|
+
def initialize(options)
|
170
|
+
super(options)
|
171
|
+
@accent_counter = 0
|
172
|
+
@preparatories_counter = 0
|
173
|
+
|
174
|
+
# validate options
|
175
|
+
if @options.has_key? :tone and
|
176
|
+
(@options.has_key? :accents or @options.has_key? :preparatory) then
|
177
|
+
raise RuntimeError.new('Overconfigured: both accents/preparatories number and psalm tone specified.')
|
178
|
+
elsif @options.has_key? :accents then
|
179
|
+
# ok, nothing to do
|
180
|
+
elsif @options.has_key? :tone
|
181
|
+
# convert psalm tone identifier to numbers
|
182
|
+
tone = PsalmPatterns.default.tone_data_str(@options[:tone])
|
183
|
+
@options[:accents] = tone.collect {|part| part[0] }
|
184
|
+
@options[:preparatory] = tone.collect {|part| part[1] }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def part_format(text, part, verse, strophe, psalm)
|
189
|
+
super(text, part, verse, strophe, psalm)
|
190
|
+
@accent_counter = 0
|
191
|
+
@preparatories_counter = 0
|
192
|
+
text
|
193
|
+
end
|
194
|
+
|
195
|
+
MARKS = {
|
196
|
+
:underline => 'underline',
|
197
|
+
:bold => 'textbf',
|
198
|
+
:semantic => 'accent'
|
199
|
+
}
|
200
|
+
|
201
|
+
def syllable_format(text, syll, word, part, verse, strophe, psalm)
|
202
|
+
super(text, syll, word, part, verse, strophe, psalm)
|
203
|
+
r = text
|
204
|
+
if syll.accent? then
|
205
|
+
@accent_counter += 1
|
206
|
+
if @accent_counter <= num_accents_for(part) then
|
207
|
+
r = "\\#{MARKS[@options[:accent_style]]}{#{r}}"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
if num_preparatory_syllables_for(part) > 0 and
|
212
|
+
@accent_counter >= num_accents_for(part) then
|
213
|
+
|
214
|
+
if @accent_counter == num_accents_for(part) and
|
215
|
+
@preparatories_counter == 1 then
|
216
|
+
r = r + "}"
|
217
|
+
end
|
218
|
+
if @preparatories_counter == num_preparatory_syllables_for(part) then
|
219
|
+
r = '\textit{' + r
|
220
|
+
end
|
221
|
+
|
222
|
+
@preparatories_counter += 1
|
223
|
+
end
|
224
|
+
|
225
|
+
return r
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
# how many accents to mark in this verse-part?
|
231
|
+
def num_accents_for(part)
|
232
|
+
case part.pos
|
233
|
+
when :flex
|
234
|
+
1
|
235
|
+
when :first
|
236
|
+
@options[:accents][0]
|
237
|
+
when :second
|
238
|
+
@options[:accents][1]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# how many preparatory syllables to mark in this verse-part?
|
243
|
+
def num_preparatory_syllables_for(part)
|
244
|
+
case part.pos
|
245
|
+
when :flex
|
246
|
+
0
|
247
|
+
when :first
|
248
|
+
@options[:preparatory][0]
|
249
|
+
when :second
|
250
|
+
@options[:preparatory][1]
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# inserts break hints between syllables
|
256
|
+
class BreakHintsFormatter < Formatter
|
257
|
+
def syllable_format(text, syll, word, part, verse, strophe, psalm)
|
258
|
+
unless syll == word.syllables.last or
|
259
|
+
(word.syllables.size >= 2 and syll == word.syllables[-2] and word.syllables[-1] =~ /^[\.,!?]+$/)
|
260
|
+
return text + '\-'
|
261
|
+
else
|
262
|
+
return text
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# formatting of verse parts -
|
268
|
+
# adds part dividing marks (flex, asterisk),
|
269
|
+
# eventually inserts newlines
|
270
|
+
class PartsFormatter < Formatter
|
271
|
+
|
272
|
+
MARKS = {
|
273
|
+
:simple => { :flex => '~\dag\mbox{}', :first => '~*', :second => '' },
|
274
|
+
:semantic => { :flex => '\flex', :first => '\asterisk', :second => '' },
|
275
|
+
:no => { :flex => '', :first => '', :second => '' }
|
276
|
+
}
|
277
|
+
|
278
|
+
def part_format(text, part, verse, strophe, psalm)
|
279
|
+
text +
|
280
|
+
MARKS[@options[:marks_type]][part.pos] +
|
281
|
+
((@options[:novydvur_newlines] && part.pos != :second) ? "\\\\" : '') # insert two backslashes
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# wraps the whole psalm in a LaTeX environment
|
286
|
+
class WrapperFormatter < Formatter
|
287
|
+
def psalm_format(text, psalm)
|
288
|
+
"\\begin{#{@options[:environment_name]}}\n" + text + "\n\\end{#{@options[:environment_name]}}\n"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
class StrophesFormatter < Formatter
|
293
|
+
|
294
|
+
END_MARKS = {
|
295
|
+
:semantic => '\psalmStrophe',
|
296
|
+
:simple => '\hspace*{0pt}\hfill--'
|
297
|
+
}
|
298
|
+
|
299
|
+
def initialize(options)
|
300
|
+
super
|
301
|
+
@options[:paragraph_space] ||= false
|
302
|
+
@options[:end_marks] ||= false
|
303
|
+
@options[:mark_last_strophe] ||= false
|
304
|
+
end
|
305
|
+
|
306
|
+
def verse_format(text, verse, strophe, psalm)
|
307
|
+
if strophe == psalm.strophes.last and not @options[:mark_last_strophe] then
|
308
|
+
return text
|
309
|
+
end
|
310
|
+
if verse != strophe.verses.last then
|
311
|
+
return text
|
312
|
+
end
|
313
|
+
|
314
|
+
if @options[:end_marks] then
|
315
|
+
text += END_MARKS[@options[:end_marks]]
|
316
|
+
end
|
317
|
+
if @options[:paragraph_space] then
|
318
|
+
text += '\\'
|
319
|
+
end
|
320
|
+
return text
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# inserts a newline between verses
|
325
|
+
class VersesFormatter < Formatter
|
326
|
+
def verse_format(text, verse, strophe, psalm)
|
327
|
+
if verse != psalm.verses.last and text != '' then
|
328
|
+
return text + "\n"
|
329
|
+
else
|
330
|
+
return text
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# skips verses at the beginning
|
336
|
+
class SkipVersesFormatter < Formatter
|
337
|
+
def initialize(options)
|
338
|
+
super(options)
|
339
|
+
@skip_verses = @options # takes just one number as a parameter
|
340
|
+
end
|
341
|
+
|
342
|
+
def verse_format(text, verse, strophe, psalm)
|
343
|
+
#super(text, psalm, verse)
|
344
|
+
@verse_counter += 1
|
345
|
+
if @verse_counter <= @skip_verses then
|
346
|
+
return ""
|
347
|
+
end
|
348
|
+
|
349
|
+
return text
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# formats the first word of the first verse as a lettrine
|
354
|
+
class LettrineFormatter < Formatter
|
355
|
+
def initialize(options)
|
356
|
+
super(options)
|
357
|
+
@digraphs = []
|
358
|
+
if @options and @options[:digraphs] then
|
359
|
+
@digraphs = @options[:digraphs]
|
360
|
+
end
|
361
|
+
@done = false
|
362
|
+
end
|
363
|
+
|
364
|
+
def verse_format(text, verse, strophe, psalm)
|
365
|
+
return text if @done
|
366
|
+
|
367
|
+
@done = true
|
368
|
+
return text.sub(/^([^\s]+)/) {
|
369
|
+
initial_size = 1
|
370
|
+
digraph = @digraphs.find {|d| text.downcase.start_with? d }
|
371
|
+
if digraph then
|
372
|
+
initial_size = digraph.size
|
373
|
+
end
|
374
|
+
'\lettrine{'+$1[0...initial_size].upcase+'}{'+$1[initial_size..-1]+'}'
|
375
|
+
}
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# formats title
|
380
|
+
class TitleFormatter < Formatter
|
381
|
+
TEMPLATE = {
|
382
|
+
:no => "", # "" % anything => ""
|
383
|
+
:plain => "%s\n\n",
|
384
|
+
:semantic => "\\titulusPsalmi{%s}\n\n"
|
385
|
+
}
|
386
|
+
|
387
|
+
def psalm_format(text, psalm)
|
388
|
+
if @options[:template].is_a? Symbol then
|
389
|
+
return TEMPLATE[@options[:template]] % psalm.header.title +
|
390
|
+
text
|
391
|
+
elsif @options[:template].is_a? String then
|
392
|
+
return @options[:template] % psalm.header.title +
|
393
|
+
text
|
394
|
+
else
|
395
|
+
raise ArgumentError, "Don't know what to do with template of type #{@options[:template].class}."
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# replaces dumb quotation marks "" by smarter ones
|
401
|
+
class QuoteFormatter < Formatter
|
402
|
+
|
403
|
+
STYLES = {
|
404
|
+
:double => ["``", "''"],
|
405
|
+
:single => ["'", "'"],
|
406
|
+
:guillemets => ['\guillemotright ', '\guillemotleft '],
|
407
|
+
:delete => ['', '']
|
408
|
+
}
|
409
|
+
|
410
|
+
def initialize(options)
|
411
|
+
super(options)
|
412
|
+
@style = @options
|
413
|
+
unless STYLES.has_key? @style
|
414
|
+
raise "Quotation marks style '#{@style}' unknown."
|
415
|
+
end
|
416
|
+
@quote_counter = 0
|
417
|
+
end
|
418
|
+
|
419
|
+
def psalm_format(text, psalm)
|
420
|
+
super(text, psalm)
|
421
|
+
@quote_counter = 0
|
422
|
+
text
|
423
|
+
end
|
424
|
+
|
425
|
+
def verse_format(text, verse, strophe, psalm)
|
426
|
+
return text.gsub('"') do
|
427
|
+
@quote_counter += 1
|
428
|
+
if @quote_counter % 2 == 1 then
|
429
|
+
STYLES[@style].first
|
430
|
+
else
|
431
|
+
STYLES[@style].last
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# appends arbitrary text to the final output
|
438
|
+
class FinalAddContentFormatter < Formatter
|
439
|
+
def psalm_format(text, psalm)
|
440
|
+
super(text, psalm)
|
441
|
+
if @options[:append] then
|
442
|
+
return text + "\n" + @options[:append]
|
443
|
+
else
|
444
|
+
return text
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|