text-reform 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 27d8e634b12cde87ef73ad3a3b11f2791dff8fec
4
+ data.tar.gz: 1a40ed4ea0a2a15cdc759b5d00e3bc4cc698d52a
5
+ SHA512:
6
+ metadata.gz: 50bb0124e382eb5fdda4d0f0cc37deed2484aebadb678e9f57d4373d6f63e24e706963c4214dd565b10f35f3f1aea9a90841786aeccf1602fab230f26034745c
7
+ data.tar.gz: ba466965064a6fc9fe0fb306b11061860405c2c269540223512993fdafe767962a350eb66adcb5f4787b3d20b2c70dbcf7f5f3846ccbce9859a05b1b4c385d0b
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/HACKING ADDED
@@ -0,0 +1,5 @@
1
+
2
+ # RUNNING TESTS
3
+
4
+ ruby -I lib test/test_text_reform.rb
5
+
@@ -0,0 +1,14 @@
1
+ === 0.3.0 / 2014-01-29
2
+
3
+ * Ruby 2.1.0 anniversary release. Still nothing wrong with this library.
4
+
5
+ === 0.2.1 / 2011-01-25
6
+
7
+ * First Ruby 1.9.2 compatibility release. Thanks to Don March for the input
8
+ for this fix. No significant changes have been made to the functionality.
9
+
10
+ === 0.2.0 / 2005-01-19
11
+
12
+ * Initial release. Ported from the Perl by Kaspar Schiess with assistance from
13
+ Austin Ziegler.
14
+
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+
2
+ Copyright (c) 2005-2014 Kaspar Schiess
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ .gitignore
2
+ Gemfile
3
+ Gemfile.lock
4
+ HACKING
5
+ History.txt
6
+ LICENSE
7
+ Manifest.txt
8
+ README
9
+ lib/text/reform.rb
10
+ test/test_text_reform.rb
11
+ text-reform.gemspec
data/README CHANGED
@@ -1,36 +1,49 @@
1
- Text::Reform README
2
- ===================
3
-
4
- Text::Reform class is a rewrite from the Perl module with the same name by
5
- Damian Conway (damian@conway.org). Much of this documentation has been copied
6
- from the original documentation and adapted to the Ruby version.
7
-
8
- The interface is subject to change, since it will undergo major Rubyfication;
9
- additionally, some features may have not been ported yet.
10
-
11
- Synopsis
12
- --------
13
- require 'text/reform'
14
- f = Text::Reform.new
15
-
16
- puts f.format(template, data)
17
-
18
- Author
19
- ------
20
- Kaspar Schiess (eule@space.ch).
21
-
22
- Ported from the original Perl library and documentation by Damian Conway
23
- (damian@conway.org).
24
-
25
- Bugs
26
- ----
27
- There are undoubtedly serious bugs lurking somewhere in code this funky :-) Bug
28
- reports and other feedback are most welcome.
29
-
30
- Copyright
31
- ---------
32
- Copyright (c) 2005, Kaspar Schiess. All Rights Reserved. This module is free
33
- software. It may be used, redistributed and/or modified under the terms of the
34
- Ruby License (see http://www.ruby-lang.org/en/LICENSE.txt)
35
-
36
- $Id: README,v 1.1.1.1 2005/01/18 11:15:40 eule Exp $
1
+ = text-reform
2
+
3
+ http://github.com/kschiess/text-reform/
4
+
5
+ == DESCRIPTION:
6
+
7
+ Text::Reform reformats text according to formatting picture templates. It's a
8
+ port from the Perl module of the same name originally by Damian Conway
9
+ (damian@conway.org). Much of the documentation has been copied from the
10
+ original documentation and adapted to the Ruby version.
11
+
12
+ The interface is subject to change, since it will undergo major Rubyfication;
13
+ additionally, some features may have not been ported yet.
14
+
15
+ == SYNOPSIS:
16
+
17
+ require 'text/reform'
18
+ f = Text::Reform.new
19
+
20
+ puts f.format(template, data)
21
+
22
+ == REQUIREMENTS:
23
+
24
+ * FIX (list of requirements)
25
+
26
+ == FUTURE ENHANCEMENTS:
27
+
28
+ * For page header and footer, if you mix :center, :left and :right in the same
29
+ hash, only one of them will get used. It would be nice if those were
30
+ combined.
31
+
32
+ == INSTALL:
33
+
34
+ * This release of text-reform is only installed with RubyGems.
35
+
36
+ == DEVELOPERS:
37
+
38
+ After checking out the source, run:
39
+
40
+ $ rake newb
41
+
42
+ This task will install any missing dependencies, run the tests/specs,
43
+ and generate the RDoc.
44
+
45
+ == LICENSE:
46
+
47
+ Copyright (c) 2005, Kaspar Schiess. All Rights Reserved. This module is free
48
+ software. It may be used, redistributed and/or modified under the terms of the
49
+ Ruby License (see http://www.ruby-lang.org/en/LICENSE.txt) or the MIT License.
@@ -1,1541 +1,1541 @@
1
- # :title: Text::Reform
2
- # :main: Text::Reform
3
- #--
4
- # Text::Reform for Ruby
5
- # Version 0.2.0
6
- #
7
- # Copyright (c) 2004 by Kaspar Schiess
8
- #
9
- # $Id: reform.rb,v 1.1.1.1 2005/01/18 11:15:51 eule Exp $
10
- #++
11
-
12
- require 'scanf'
13
- unless defined?(Text)
14
- module Text; end
15
- end
16
-
17
- # = Introduction
18
- #
19
- # Text::Reform class is a rewrite from the perl module with the same name
20
- # by Damian Conway (damian@conway.org). Much of this documentation has
21
- # been copied from the original documentation and adapted to the Ruby
22
- # version.
23
- #
24
- # The interface is subject to change, since it will undergo major
25
- # Rubyfication.
26
- #
27
- # = Synopsis
28
- # require 'text/reform'
29
- # f = Text::Reform.new
30
- #
31
- # puts f.format(template, data)
32
- #
33
- # = Description
34
- # == The Reform#format method
35
- #
36
- # Reform#format takes a series of format (or "picture") strings followed
37
- # by replacement values, interpolates those values into each picture
38
- # string, and returns the result.
39
- #
40
- # A picture string consists of sequences of the following characters:
41
- # [<] Left-justified field indicator. A series of two or
42
- # more sequential +<+'s specify a left-justified
43
- # field to be filled by a subsequent value. A single
44
- # +<+ is formatted as the literal character '<'.
45
- # [>] Right-justified field indicator. A series of two
46
- # or more sequential >'s specify a right-justified
47
- # field to be filled by a subsequent value. A single
48
- # < is formatted as the literal character '<'.
49
- # [<<>>] Fully-justified field indicator. Field may be of
50
- # any width, and brackets need not balance, but
51
- # there must be at least 2 '<' and 2 '>'.
52
- # [^] Centre-justified field indicator. A series of two
53
- # or more sequential ^'s specify a centred field to
54
- # be filled by a subsequent value. A single ^ is
55
- # formatted as the literal character '<'.
56
- # [>>.<<<<] A numerically formatted field with the specified
57
- # number of digits to either side of the decimal
58
- # place. See _Numerical formatting_ below.
59
- # [[] Left-justified block field indicator. Just like a
60
- # < field, except it repeats as required on
61
- # subsequent lines. See below. A single [ is
62
- # formatted as the literal character '['.
63
- # []] Right-justified block field indicator. Just like a
64
- # > field, except it repeats as required on
65
- # subsequent lines. See below. A single ] is
66
- # formatted as the literal character ']'.
67
- # [[[]]] Fully-justified block field indicator. Just like a
68
- # <<<>>> field, except it repeats as required on
69
- # subsequent lines. See below. Field may be of any
70
- # width, and brackets need not balance, but there
71
- # must be at least 2 '[' and 2 ']'.
72
- # [|] Centre-justified block field indicator. Just like
73
- # a ^ field, except it repeats as required on
74
- # subsequent lines. See below. A single | is
75
- # formatted as the literal character '|'.
76
- # []]].[[[[] A numerically formatted block field with the
77
- # specified number of digits to either side of the
78
- # decimal place. Just like a +>>>.<<<<+ field,
79
- # except it repeats as required on subsequent lines.
80
- # See below.
81
- # [~] A one-character wide block field.
82
- # [\] Literal escape of next character (e.g. +\+ is
83
- # formatted as '~', not a one character wide block
84
- # field).
85
- # [Any other character] That literal character.
86
- #
87
- # Any substitution value which is +nil+ (either explicitly so, or because
88
- # it is missing) is replaced by an empty string.
89
- #
90
- # == Controlling Reform instance options
91
- # There are several ways to influence options set in the Reform instance:
92
- #
93
- # 1. At creation:
94
- # # using a hash
95
- # r1 = Text::Reform.new(:squeeze => true)
96
- #
97
- # # using a block
98
- # r2 = Text::Reform.new do |rf|
99
- # rf.squeeze = true
100
- # rf.fill = true
101
- # end
102
- #
103
- # 2. Using accessors:
104
- # r = Text::Reform.new
105
- # r.squeeze = true
106
- # r.fill = true
107
- #
108
- # The Perl way of interleaving option changes with picture strings and
109
- # data is currently *NOT* supported.
110
- #
111
- # == Controlling line filling
112
- # #squeeze replaces sequences of spaces or tabs to be replaced with a
113
- # single space; #fill removes newlines from the input. To minimize all
114
- # whitespace, you need to specify both options. Hence:
115
- #
116
- # format = "EG> [[[[[[[[[[[[[[[[[[[[["
117
- # data = "h e\t l lo\nworld\t\t\t\t\t"
118
- # r = Text::Reform.new
119
- # r.squeeze = false # default, implied
120
- # r.fill = false # default, implied
121
- # puts r.format(format, data)
122
- # # all whitespace preserved:
123
- # #
124
- # # EG> h e l lo
125
- # # EG> world
126
- #
127
- # r.squeeze = true
128
- # r.fill = false # default, implied
129
- # puts r.format(format, data)
130
- # # only newlines preserved
131
- # #
132
- # # EG> h e l lo
133
- # # EG> world
134
- #
135
- # r.squeeze = false # default, implied
136
- # r.fill = true
137
- # puts r.format(format, data)
138
- # # only spaces/tabs preserved:
139
- # #
140
- # # EG> h e l lo world
141
- #
142
- # r.fill = true
143
- # r.squeeze = true
144
- # puts r.format(format, data)
145
- # # no whitespace preserved:
146
- # #
147
- # # EG> h e l lo world
148
- #
149
- # Whether or not filling or squeezing is in effect, #format can also be
150
- # directed to trim any extra whitespace from the end of each line it
151
- # formats, using the #trim option. If this option is specified with a
152
- # +true+ value, every line returned by #format will automatically have the
153
- # substitution +.gsub!(/[ \t]+/, '')+ applied to it.
154
- #
155
- # r.format("[[[[[[[[[[[", 'short').length # => 11
156
- # r.trim = true
157
- # r.format("[[[[[[[[[[[", 'short').length # => 6
158
- #
159
- # It is also possible to control the character used to fill lines that are
160
- # too short, using the #filler option. If this option is specified the
161
- # value of the #filler flag is used as the fill string, rather than the
162
- # default +" "+.
163
- #
164
- # For example:
165
- # r.filler = '*'
166
- # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$123.4')
167
- # prints:
168
- # Pay bearer: *******$123.4*******
169
- #
170
- # If the filler string is longer than one character, it is truncated to
171
- # the appropriate length. So:
172
- # r.filler = '-->'
173
- # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$123.4')
174
- # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$13.4')
175
- # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$1.4')
176
- # prints:
177
- # Pay bearer: -->-->-$123.4-->-->-
178
- # Pay bearer: -->-->--$13.4-->-->-
179
- # Pay bearer: -->-->--$1.4-->-->--
180
- #
181
- # If the value of the #filler option is a hash, then its +:left+ and
182
- # +:right+ entries specify separate filler strings for each side of an
183
- # interpolated value.
184
- #
185
- # == Options
186
- # The Perl variant supports option switching during processing of the
187
- # arguments of a single call to #format. This has been removed while
188
- # porting to Ruby, since I believe that this does not add to clarity
189
- # of code. So you have to change options explicitly.
190
- #
191
- # == Data argument types and handling
192
- # The +data+ part of the call to format can be either in String form, the
193
- # items being newline separated, or in Array form. The array form can
194
- # contain any kind of type you want, as long as it supports #to_s.
195
- #
196
- # So all of the following examples return the same result:
197
- # # String form
198
- # r.format("]]]].[[", "1234\n123")
199
- # # Array form
200
- # r.format("]]]].[[", [ 1234, 123 ])
201
- # # Array with another type
202
- # r.format("]]]].[[", [ 1234.0, 123.0 ])
203
- #
204
- # == Multi-line format specifiers and interleaving
205
- # By default, if a format specifier contains two or more lines (i.e. one
206
- # or more newline characters), the entire format specifier is repeatedly
207
- # filled as a unit, until all block fields have consumed their
208
- # corresponding arguments. For example, to build a simple look-up table:
209
- # values = (1..12).to_a
210
- # squares = values.map { |el| sprintf "%.6g", el**2 }
211
- # roots = values.map { |el| sprintf "%.6g", Math.sqrt(el) }
212
- # logs = values.map { |el| sprintf "%.6g", Math.log(el) }
213
- # inverses = values.map { |el| sprintf "%.6g", 1/el }
214
- #
215
- # puts reform.format(
216
- # " N N**2 sqrt(N) log(N) 1/N",
217
- # "=====================================================",
218
- # "| [[ | [[[ | [[[[[[[[[[ | [[[[[[[[[ | [[[[[[[[[ |" +
219
- # "-----------------------------------------------------",
220
- # values, squares, roots, logs, inverses
221
- # )
222
- #
223
- # The multiline format specifier:
224
- # "| [[ | [[[ | [[[[[[[[[[ | [[[[[[[[[ | [[[[[[[[[ |" +
225
- # "-----------------------------------------------------"
226
- #
227
- # is treated as a single logical line. So #format alternately fills the
228
- # first physical line (interpolating one value from each of the arrays)
229
- # and the second physical line (which puts a line of dashes between each
230
- # row of the table) producing:
231
- # N N**2 sqrt(N) log(N) 1/N
232
- # =====================================================
233
- # | 1 | 1 | 1 | 0 | 1 |
234
- # -----------------------------------------------------
235
- # | 2 | 4 | 1.41421 | 0.693147 | 0.5 |
236
- # -----------------------------------------------------
237
- # | 3 | 9 | 1.73205 | 1.09861 | 0.333333 |
238
- # -----------------------------------------------------
239
- # | 4 | 16 | 2 | 1.38629 | 0.25 |
240
- # -----------------------------------------------------
241
- # | 5 | 25 | 2.23607 | 1.60944 | 0.2 |
242
- # -----------------------------------------------------
243
- # | 6 | 36 | 2.44949 | 1.79176 | 0.166667 |
244
- # -----------------------------------------------------
245
- # | 7 | 49 | 2.64575 | 1.94591 | 0.142857 |
246
- # -----------------------------------------------------
247
- # | 8 | 64 | 2.82843 | 2.07944 | 0.125 |
248
- # -----------------------------------------------------
249
- # | 9 | 81 | 3 | 2.19722 | 0.111111 |
250
- # -----------------------------------------------------
251
- # | 10 | 100 | 3.16228 | 2.30259 | 0.1 |
252
- # -----------------------------------------------------
253
- # | 11 | 121 | 3.31662 | 2.3979 | 0.0909091 |
254
- # -----------------------------------------------------
255
- # | 12 | 144 | 3.4641 | 2.48491 | 0.0833333 |
256
- # -----------------------------------------------------
257
- #
258
- # This implies that formats and the variables from which they're filled
259
- # need to be interleaved. That is, a multi-line specification like this:
260
- # puts r.format(
261
- # "Passed: ##
262
- # [[[[[[[[[[[[[[[ # single format specification
263
- # Failed: # (needs two sets of data)
264
- # [[[[[[[[[[[[[[[", ##
265
- # passes, fails) ## data for previous format
266
- # would print:
267
- # Passed:
268
- # <pass 1>
269
- # Failed:
270
- # <fail 1>
271
- # Passed:
272
- # <pass 2>
273
- # Failed:
274
- # <fail 2>
275
- # Passed:
276
- # <pass 3>
277
- # Failed:
278
- # <fail 3>
279
- #
280
- # because the four-line format specifier is treated as a single unit, to
281
- # be repeatedly filled until all the data in +passes+ and +fails+ has been
282
- # consumed.
283
- #
284
- # Unlike the table example, where this unit filling correctly put a line
285
- # of dashes between lines of data, in this case the alternation of passes
286
- # and fails is probably /not/ the desired effect.
287
- #
288
- # Judging by the labels, it is far more likely that the user wanted:
289
- # Passed:
290
- # <pass 1>
291
- # <pass 2>
292
- # <pass 3>
293
- # Failed:
294
- # <fail 4>
295
- # <fail 5>
296
- # <fail 6>
297
- #
298
- # To achieve that, either explicitly interleave the formats and their data
299
- # sources:
300
- # puts r.format(
301
- # "Passed:", ## single format (no data required)
302
- # " [[[[[[[[[[[[[[[", ## single format (needs one set of data)
303
- # passes, ## data for previous format
304
- # "Failed:", ## single format (no data required)
305
- # " [[[[[[[[[[[[[[[", ## single format (needs one set of data)
306
- # fails) ## data for previous format
307
- # or instruct #format to do it for you automagically, by setting the
308
- # 'interleave' flag +true+:
309
- #
310
- # r.interleave = true
311
- # puts r.format(
312
- # "Passed: ##
313
- # [[[[[[[[[[[[[[[ # single format
314
- # Failed: # (needs two sets of data)
315
- # [[[[[[[[[[[[[[[", ##
316
- # ## data to be automagically interleaved
317
- # passes, fails) # as necessary between lines of previous
318
- # ## format
319
- #
320
- # == How #format hyphenates
321
- # Any line with a block field repeats on subsequent lines until all block
322
- # fields on that line have consumed all their data. Non-block fields on
323
- # these lines are replaced by the appropriate number of spaces.
324
- #
325
- # Words are wrapped whole, unless they will not fit into the field at all,
326
- # in which case they are broken and (by default) hyphenated. Simple
327
- # hyphenation is used (i.e. break at the +N-1+th character and insert a
328
- # '-'), unless a suitable alternative subroutine is specified instead.
329
- #
330
- # Words will not be broken if the break would leave less than 2 characters
331
- # on the current line. This minimum can be varied by setting the
332
- # +min_break+ option to a numeric value indicating the minumum total broken
333
- # characters (including hyphens) required on the current line. Note that,
334
- # for very narrow fields, words will still be broken (but
335
- # __unhyphenated__). For example:
336
- #
337
- # puts r.format('~', 'split')
338
- #
339
- # would print:
340
- #
341
- # s
342
- # p
343
- # l
344
- # i
345
- # t
346
- #
347
- # whilst:
348
- #
349
- # r.min_break= 1
350
- # puts r.format('~', 'split')
351
- #
352
- # would print:
353
- #
354
- # s-
355
- # p-
356
- # l-
357
- # i-
358
- # t
359
- #
360
- # Alternative breaking strategies can be specified using the "break"
361
- # option in a configuration hash. For example:
362
- #
363
- # r.break = MyBreaker.new
364
- # r.format(fmt, data)
365
- #
366
- # #format expects a user-defined line-breaking strategy to listen to the
367
- # method #break that takes three arguments (the string to be broken, the
368
- # maximum permissible length of the initial section, and the total width
369
- # of the field being filled). #break must return a list of two strings:
370
- # the initial (broken) section of the word, and the remainder of the
371
- # string respectivly).
372
- #
373
- # For example:
374
- # class MyBreaker
375
- # def break(str, initial, total)
376
- # [ str[0, initial-1].'~'], str[initial-1..-1] ]
377
- # end
378
- # end
379
- #
380
- # r.break = MyBreaker.new
381
- #
382
- # makes '~' the hyphenation character, whilst:
383
- # class WrapAndSlop
384
- # def break(str, initial, total)
385
- # if (initial == total)
386
- # str =~ /\A(\s*\S*)(.*)/
387
- # [ $1, $2 ]
388
- # else
389
- # [ '', str ]
390
- # end
391
- # end
392
- # end
393
- #
394
- # r.break = WrapAndSlop.new
395
- #
396
- # wraps excessively long words to the next line and "slops" them over the
397
- # right margin if necessary.
398
- #
399
- # The Text::Reform class provides three functions to simplify the use of
400
- # variant hyphenation schemes. Text::Reform::break_wrap returns an
401
- # instance implementing the "wrap-and-slop" algorithm shown in the last
402
- # example, which could therefore be rewritten:
403
- #
404
- # r.break = Text::Reform.break_wrap
405
- #
406
- # Text::Reform::break_with takes a single string argument and returns an
407
- # instance of a class which hyphenates by cutting off the text at the
408
- # right margin and appending the string argument. Hence the first of the
409
- # two examples could be rewritten:
410
- #
411
- # r.break = Text::Reform.break_with('~')
412
- #
413
- # The method Text::Reform::break_at takes a single string argument and
414
- # returns a reference to a sub which hyphenates by breaking immediately
415
- # after that string. For example:
416
- #
417
- # r.break = Text::Reform.break_at('-')
418
- # r.format("[[[[[[[[[[[[[[", "The Newton-Raphson methodology")
419
- #
420
- # returns:
421
- # "The Newton-
422
- # Raphson
423
- # methodology"
424
- #
425
- # Note that this differs from the behaviour of Text::Reform::break_with,
426
- # which would be:
427
- #
428
- # r.break = Text::Reform.break_width('-')
429
- # r.format("[[[[[[[[[[[[[[", "The Newton-Raphson methodology")
430
- #
431
- # returns:
432
- # "The Newton-R-
433
- # aphson metho-
434
- # dology"
435
- #
436
- # Choosing the correct breaking strategy depends on your kind of data.
437
- #
438
- # The method Text::Reform::break_hyphen returns an instance of a class
439
- # which hyphenates using a Ruby hyphenator. The hyphenator must be
440
- # provided to the method. At the time of release, there are two
441
- # implementations of hyphenators available: TeX::Hyphen by Martin DeMello
442
- # and Austin Ziegler (a Ruby port of Jan Pazdziora's TeX::Hyphen module);
443
- # and Text::Hyphen by Austin Ziegler (a significant recoding of
444
- # TeX::Hyphen to better support non-English languages).
445
- #
446
- # For example:
447
- # r.break = Text::Reform.break_hyphen
448
- #
449
- # Note that in the previous example the calls to .break_at, .break_wrap
450
- # and .break_hyphen produce instances of the corresponding strategy class.
451
- #
452
- # == The algorithm #format uses is:
453
- #
454
- # 1. If interleaving is specified, split the first string in the
455
- # argument list into individual format lines and add a
456
- # terminating newline (unless one is already present).
457
- # therwise, treat the entire string as a single "line" (like
458
- # /s does in regexes)
459
- #
460
- # 2. For each format line...
461
- #
462
- # 1. determine the number of fields and shift
463
- # that many values off the argument list and
464
- # into the filling list. If insufficient
465
- # arguments are available, generate as many
466
- # empty strings as are required.
467
- #
468
- # 2. generate a text line by filling each field
469
- # in the format line with the initial contents
470
- # of the corresponding arg in the filling list
471
- # (and remove those initial contents from the arg).
472
- #
473
- # 3. replace any <,>, or ^ fields by an equivalent
474
- # number of spaces. Splice out the corresponding
475
- # args from the filling list.
476
- #
477
- # 4. Repeat from step 2.2 until all args in the
478
- # filling list are empty.
479
- #
480
- # 3. concatenate the text lines generated in step 2
481
- #
482
- # Note that in difference to the Perl version of Text::Reform,
483
- # this version does not currently loop over several format strings
484
- # in one function call.
485
- #
486
- #
487
- # == Reform#format examples
488
- #
489
- # As an example of the use of #format, the following:
490
- #
491
- # count = 1
492
- # text = "A big long piece of text to be formatted exquisitely"
493
- # output = ''
494
- # output << r.format(" |||| <<<<<<<<<< ", count, text)
495
- # output << r.format(" ---------------- ",
496
- # " ^^^^ ]]]]]]]]]]| ", count+11, text)
497
- #
498
- # results in +output+:
499
- # 1 A big lon-
500
- # ----------------
501
- # 12 g piece|
502
- # of text|
503
- # to be for-|
504
- # matted ex-|
505
- # quisitely|
506
- #
507
- # Note that block fields in a multi-line format string,
508
- # cause the entire multi-line format to be repeated as
509
- # often as necessary.
510
- #
511
- # Unlike traditional Perl #format arguments, picture strings and
512
- # arguments cannot be interleaved in Ruby version. This is partly
513
- # by intention to see if the feature is a feature or if it
514
- # can be disposed with. Another example:
515
- #
516
- # report = ''
517
- # report << r.format(
518
- # 'Name Rank Serial Number',
519
- # '==== ==== =============',
520
- # '<<<<<<<<<<<<< ^^^^ <<<<<<<<<<<<<',
521
- # name, rank, serial_number
522
- # )
523
- #
524
- # results in:
525
- #
526
- # Name Rank Serial Number
527
- # ==== ==== =============
528
- # John Doe high 314159
529
- #
530
- # == Numerical formatting
531
- #
532
- # The ">>>.<<<" and "]]].[[[" field specifiers may be used to format
533
- # numeric values about a fixed decimal place marker. For example:
534
- #
535
- # puts r.format('(]]]]].[[)', %w{
536
- # 1
537
- # 1.0
538
- # 1.001
539
- # 1.009
540
- # 123.456
541
- # 1234567
542
- # one two
543
- # })
544
- #
545
- # would print:
546
- #
547
- # ( 1.0)
548
- # ( 1.0)
549
- # ( 1.00)
550
- # ( 1.01)
551
- # ( 123.46)
552
- # (#####.##)
553
- # (?????.??)
554
- # (?????.??)
555
- #
556
- # Fractions are rounded to the specified number of places after the
557
- # decimal, but only significant digits are shown. That's why, in the
558
- # above example, 1 and 1.0 are formatted as "1.0", whilst 1.001 is
559
- # formatted as "1.00".
560
- #
561
- # You can specify that the maximal number of decimal places always be used
562
- # by giving the configuration option 'numeric' the value NUMBERS_ALL_PLACES.
563
- # For example:
564
- #
565
- # r.numeric = Text::Reform::NUMBERS_ALL_PLACES
566
- # puts r.format('(]]]]].[[)', <<EONUMS)
567
- # 1
568
- # 1.0
569
- # EONUMS
570
- #
571
- # would print:
572
- #
573
- # ( 1.00)
574
- # ( 1.00)
575
- #
576
- # Note that although decimal digits are rounded to fit the specified width, the
577
- # integral part of a number is never modified. If there are not enough places
578
- # before the decimal place to represent the number, the entire number is
579
- # replaced with hashes.
580
- #
581
- # If a non-numeric sequence is passed as data for a numeric field, it is
582
- # formatted as a series of question marks. This querulous behaviour can be
583
- # changed by giving the configuration option 'numeric' a value that
584
- # matches /\bSkipNaN\b/i in which case, any invalid numeric data is simply
585
- # ignored. For example:
586
- #
587
- #
588
- # r.numeric = Text::Reform::NUMBERS_SKIP_NAN
589
- # puts r.format('(]]]]].[[)', %w{
590
- # 1
591
- # two three
592
- # 4
593
- # })
594
- #
595
- #
596
- # would print:
597
- #
598
- # ( 1.0)
599
- # ( 4.0)
600
- #
601
- # == Filling block fields with lists of values
602
- #
603
- # If an argument contains an array, then #format
604
- # automatically joins the elements of the array into a single string, separating
605
- # each element with a newline character. As a result, a call like this:
606
- #
607
- # svalues = %w{ 1 10 100 1000 }
608
- # nvalues = [1, 10, 100, 1000]
609
- # puts r.format(
610
- # "(]]]].[[)",
611
- # svalues # you could also use nvalues here.
612
- # )
613
- #
614
- # will print out
615
- #
616
- # ( 1.00)
617
- # ( 10.00)
618
- # (100.00)
619
- # (1000.00)
620
- #
621
- # as might be expected.
622
- #
623
- # Note: While String arguments are consumed during formatting process
624
- # and will be empty at the end of formatting, array arguments are not.
625
- # So svalues (nvalues) still contains [1,10,100,1000] after the call
626
- # to #format.
627
- #
628
- # == Headers, footers, and pages
629
- #
630
- # The #format method can also insert headers, footers, and page-feeds
631
- # as it formats. These features are controlled by the "header", "footer",
632
- # "page_feed", "page_len", and "page_num" options.
633
- #
634
- # If the +page_num+ option is set to an Integer value, page numbering
635
- # will start at that value.
636
- #
637
- # The +page_len+ option specifies the total number of lines in a page (including
638
- # headers, footers, and page-feeds).
639
- #
640
- # The +page_width+ option specifies the total number of columns in a page.
641
- #
642
- # If the +header+ option is specified with a string value, that string is
643
- # used as the header of every page generated. If it is specified as a block,
644
- # that block is called at the start of every page and
645
- # its return value used as the header string. When called, the block is
646
- # passed the current page number.
647
- #
648
- # Likewise, if the +footer+ option is specified with a string value, that
649
- # string is used as the footer of every page generated. If it is specified
650
- # as a block, that block is called at the *start*
651
- # of every page and its return value used as the footer string. When called,
652
- # the footer block is passed the current page number.
653
- #
654
- # Both the header and footer options can also be specified as hash references.
655
- # In this case the hash entries for keys +left+, +centre+ (or +center+), and
656
- # +right+ specify what is to appear on the left, centre, and right of the
657
- # header/footer. The entry for the key +width+ specifies how wide the
658
- # footer is to be. If the +width+ key is omitted, the +page_width+ configuration
659
- # option (which defaults to 72 characters) is used.
660
- #
661
- # The +:left+, +:centre+, and +:right+ values may be literal
662
- # strings, or blocks (just as a normal header/footer specification may
663
- # be.) See the second example, below.
664
- #
665
- # Another alternative for header and footer options is to specify them as a
666
- # block that returns a hash reference. The subroutine is called for each
667
- # page, then the resulting hash is treated like the hashes described in the
668
- # preceding paragraph. See the third example, below.
669
- #
670
- # The +page_feed+ option acts in exactly the same way, to produce a
671
- # page_feed which is appended after the footer. But note that the page_feed
672
- # is not counted as part of the page length.
673
- #
674
- # All three of these page components are recomputed at the *start of each
675
- # new page*, before the page contents are formatted (recomputing the header
676
- # and footer first makes it possible to determine how many lines of data to
677
- # format so as to adhere to the specified page length).
678
- #
679
- # When the call to #format is complete and the data has been fully formatted,
680
- # the footer subroutine is called one last time, with an extra argument of +true+.
681
- # The string returned by this final call is used as the final footer.
682
- #
683
- # So for example, a 60-line per page report, starting at page 7,
684
- # with appropriate headers and footers might be set up like so:
685
- #
686
- # small = Text::Reform.new
687
- # r.header = lambda do |page| "Page #{page}\n\n" end
688
- # r.footer = lambda do |page, last|
689
- # if last
690
- # ''
691
- # else
692
- # ('-'*50 + "\n" + small.format('>'*50, "...#{page+1}"))
693
- # end
694
- # end
695
- # r.page_feed = "\n\n"
696
- # r.page_len = 60
697
- # r.page_num = 7
698
- #
699
- # r.format(template, data)
700
- #
701
- # Note that you can't reuse the +r+ instance of Text::Reform inside
702
- # the footer, it will end up calling itself recursivly until stack
703
- # exhaustion.
704
- #
705
- # Alternatively, to set up headers and footers such that the running
706
- # head is right justified in the header and the page number is centred
707
- # in the footer:
708
- #
709
- # r.header = { :right => 'Running head' }
710
- # r.footer = { :centre => lambda do |page| "page #{page}" end }
711
- # r.page_len = 60
712
- #
713
- # r.format(template, data)
714
- #
715
- # The footer in the previous example could also have been specified the other
716
- # way around, as a block that returns a hash (rather than a hash containing
717
- # a block):
718
- #
719
- # r.header = { :right => 'Running head' }
720
- # r.footer = lambda do |page| { :center => "page #{page}" } end
721
- #
722
- #
723
- # = AUTHOR
724
- #
725
- # Original Perl library and documentation:
726
- # Damian Conway (damian at conway dot org)
727
- #
728
- # Translating everything to Ruby (and leaving a lot of stuff out):
729
- # Kaspar Schiess (eule at space dot ch)
730
- #
731
- # = BUGS
732
- #
733
- # There are undoubtedly serious bugs lurking somewhere in code this funky :-)
734
- # Bug reports and other feedback are most welcome.
735
- #
736
- # = COPYRIGHT
737
- #
738
- # Copyright (c) 2005, Kaspar Schiess. All Rights Reserved.
739
- # This module is free software. It may be used, redistributed
740
- # and/or modified under the terms of the Ruby License
741
- # (see http://www.ruby-lang.org/en/LICENSE.txt)
742
- class Text::Reform
743
- VERSION = "0.2.0"
744
-
745
- # various regexp parts for matching patterns.
746
- BSPECIALS = %w{ [ | ] }
747
- LSPECIALS = %w{ < ^ > }
748
- LJUSTIFIED = "[<]{2,} [>]{2,}"
749
- BJUSTIFIED = "[\\[]{2,} [\\]]{2,}"
750
- BSINGLE = "~+"
751
- SPECIALS = [BSPECIALS, LSPECIALS].flatten.map { |spec| Regexp.escape(spec)+"{2,}" }
752
- FIXED_FIELDPAT = [LJUSTIFIED, BJUSTIFIED, BSINGLE, SPECIALS ].flatten.join('|')
753
-
754
- DECIMAL = '.' # TODO: Make this locale dependent
755
- # Matches one or more > followed by . followed by one or more <
756
- LNUMERICAL = "[>]+ (?:#{Regexp.escape(DECIMAL)}[<]{1,})"
757
- # Matches one or more ] followed by . followed by one or more [
758
- BNUMERICAL = "[\\]]+ (?: #{Regexp.escape(DECIMAL)} [\\[]{1,})"
759
-
760
- FIELDPAT = [LNUMERICAL, BNUMERICAL, FIXED_FIELDPAT].join('|')
761
-
762
- LFIELDMARK = [LNUMERICAL, LJUSTIFIED, LSPECIALS.map { |l| Regexp.escape(l) + "{2}" } ].flatten.join('|')
763
- BFIELDMARK = [BNUMERICAL, BJUSTIFIED, BSINGLE, BSPECIALS.map { |l| Regexp.escape(l) + "{2}" } ].flatten.join('|')
764
-
765
- FIELDMARK = [LNUMERICAL, BNUMERICAL, BSINGLE, LJUSTIFIED, BJUSTIFIED, LFIELDMARK, BFIELDMARK].flatten.join('|')
766
-
767
- # For use with #header, #footer, and #page_feed; this will clear the
768
- # header, footer, or page feed block result to be an empty block.
769
- CLEAR_BLOCK = lambda { "" }
770
-
771
- # Proc returning page header. This is called before the page actually
772
- # gets formatted to permit calculation of page length.
773
- #
774
- # *Default*:: +CLEAR_BLOCK+
775
- attr_accessor :header
776
-
777
- # Proc returning the page footer. This gets called before the
778
- # page gets formatted to permit calculation of page length.
779
- #
780
- # *Default*:: +CLEAR_BLOCK+
781
- attr_accessor :footer
782
-
783
- # Proc to be called for page feed text. This is also called at
784
- # the start of each page, but does not count towards page length.
785
- #
786
- # *Default*:: +CLEAR_BLOCK+
787
- attr_accessor :page_feed
788
-
789
- # Specifies the total number of lines in a page (including headers,
790
- # footers, and page-feeds).
791
- #
792
- # *Default*:: +nil+
793
- attr_accessor :page_len
794
-
795
- # Where to start page numbering.
796
- #
797
- # *Default*:: +nil+
798
- attr_accessor :page_num
799
-
800
- # Specifies the total number of columns in a page.
801
- #
802
- # *Default*:: 72
803
- attr_accessor :page_width
804
-
805
- # Break class instance that is used to break words in hyphenation. This
806
- # class must have a #break method accepting the three arguments +str+,
807
- # +initial_max_length+ and +maxLength+.
808
- #
809
- # You can directly call the break_* methods to produce such a class
810
- # instance for you; Available methods are #break_width, #break_at,
811
- # #break_wrap, #break_hyphenator.
812
- #
813
- # *Default*:: Text::Hyphen::break_with('-')
814
- attr_accessor :break
815
-
816
- # Specifies the minimal number of characters that must be left on a
817
- # line. This prevents breaking of words below its value.
818
- #
819
- # *Default*:: 2
820
- attr_accessor :min_break
821
-
822
- # If +true+, causes any sequence of spaces and/or tabs (but not
823
- # newlines) in an interpolated string to be replaced with a single
824
- # space.
825
- #
826
- # *Default*:: +false+
827
- attr_accessor :squeeze
828
-
829
- # If +true+, causes newlines to be removed from the input. If you want
830
- # to squeeze all whitespace, set #fill and #squeeze to true.
831
- #
832
- # *Default*:: +false+
833
- attr_accessor :fill
834
-
835
- # Controls character that is used to fill lines that are too short.
836
- # If this attribute has a hash value, the symbols :left and :right
837
- # store the filler character to use on the left and the right,
838
- # respectivly.
839
- #
840
- # *Default*:: +' '+ on both sides
841
- attr_accessor :filler
842
- def filler=(value) #:nodoc:
843
- if value.kind_of?(Hash)
844
- unless value[:left] and value[:right]
845
- raise ArgumentError, "If #filler is provided as a Hash, it must contain the keys :left and :right"
846
- else
847
- @filler = value
848
- end
849
- else
850
- @filler = { :left => value, :right => value }
851
- end
852
- end
853
-
854
- # This implies that formats and the variables from which they're filled
855
- # need to be interleaved. That is, a multi-line specification like this:
856
- #
857
- # print format(
858
- # "Passed: ##
859
- # [[[[[[[[[[[[[[[ # single format specification
860
- # Failed: # (needs two sets of data)
861
- # [[[[[[[[[[[[[[[", ##
862
- #
863
- # fails, passes) ## two arrays, data for previous format
864
- #
865
- # would print:
866
- #
867
- # Passed:
868
- # <pass 1>
869
- # Failed:
870
- # <fail 1>
871
- # Passed:
872
- # <pass 2>
873
- # Failed:
874
- # <fail 2>
875
- # Passed:
876
- # <pass 3>
877
- # Failed:
878
- # <fail 3>
879
- #
880
- # because the four-line format specifier is treated as a single unit, to
881
- # be repeatedly filled until all the data in +passes+ and +fails+ has
882
- # been consumed.
883
- #
884
- # *Default*:: false
885
- attr_accessor :interleave
886
-
887
- # Numbers are printed, leaving off unnecessary decimal places. Non-
888
- # numeric data is printed as a series of question marks. This is the
889
- # default for formatting numbers.
890
- NUMBERS_NORMAL = 0
891
- # Numbers are printed, retaining all decimal places. Non-numeric data is
892
- # printed as a series of question marks.
893
- #
894
- # [[[[[.]] # format
895
- # 1.0 -> 1.00
896
- # 1 -> 1.00
897
- NUMBERS_ALL_PLACES = 1
898
- # Numbers are printed as ffor +NUMBERS_NORMAL+, but NaN ("not a number")
899
- # values are skipped.
900
- NUMBERS_SKIP_NAN = 2
901
- # Numbers are printed as for +NUMBERS_ALL_PLACES+, but NaN values are
902
- # skipped.
903
- NUMBERS_ALL_AND_SKIP = NUMBERS_ALL_PLACES | NUMBERS_SKIP_NAN
904
-
905
- # Specifies handling method for numerical data. Allowed values include:
906
- # * +NUMBERS_NORMAL+
907
- # * +NUMBERS_ALL_PLACES+
908
- # * +NUMBERS_SKIP_NAN+
909
- # * +NUMBERS_ALL_AND_SKIP+
910
- #
911
- # *Default*:: NUMBERS_NORMAL
912
- attr_accessor :numeric
913
-
914
- # Controls trimming of whitespace at end of lines.
915
- #
916
- # *Default*:: +true+
917
- attr_accessor :trim
918
-
919
- # Create a Text::Reform object. Accepts an optional hash of
920
- # construction option (this will change to named parameters in Ruby
921
- # 2.0). After the initial object is constructed (with either the
922
- # provided or default values), the object will be yielded (as +self+) to
923
- # an optional block for further construction and operation.
924
-
925
- def initialize(options = {}) #:yields self:
926
- @debug = options[:debug] || false
927
- @header = options[:header] || CLEAR_BLOCK
928
- @footer = options[:footer] || CLEAR_BLOCK
929
- @page_feed = options[:page_feed] || CLEAR_BLOCK
930
- @page_len = options[:page_len] || nil
931
- @page_num = options[:page_num] || nil
932
- @page_width = options[:page_width] || 72
933
- @break = options[:break] || Text::Reform.break_with('-')
934
- @min_break = options[:min_break] || 2
935
- @squeeze = options[:squeeze] || false
936
- @fill = options[:fill] || false
937
- @filler = options[:filler] || { :left => ' ', :right => ' ' }
938
- @interleave = options[:interleave] || false
939
- @numeric = options[:numeric] || 0
940
- @trim = options[:trim] || false
941
-
942
- yield self if block_given?
943
- end
944
-
945
- # Format data according to +format+.
946
- def format(*args)
947
- @page_num ||= 1
948
-
949
- __debug("Acquiring header and footer: ", @page_num)
950
- header = __header(@page_num)
951
- footer = __footer(@page_num, false)
952
-
953
- previous_footer = footer
954
-
955
- line_count = count_lines(header, footer)
956
- hf_count = line_count
957
-
958
- text = header
959
- format_stack = []
960
-
961
- while (args and not args.empty?) or (not format_stack.empty?)
962
- __debug("Arguments: ", args)
963
- __debug("Formats left: ", format_stack)
964
-
965
- if format_stack.empty?
966
- if @interleave
967
- # split format in its parts and recombine line by line
968
- format_stack += args.shift.split(%r{\n}o).collect { |fmtline| fmtline << "\n" }
969
- else
970
- format_stack << args.shift
971
- end
972
- end
973
-
974
- format = format_stack.shift
975
-
976
- parts = format.split(%r{( # Capture
977
- \n | # newline... OR
978
- (?:\\.)+ | # one or more escapes... OR
979
- #{FIELDPAT} | # patterns
980
- )}ox)
981
- parts << "\n" unless parts[-1] == "\n"
982
-
983
- # Count all fields (inject 0, increment when field) and prepare
984
- # data.
985
- field_count = parts.inject(0) do |count, el|
986
- if (el =~ /#{LFIELDMARK}/ox or el =~ /#{FIELDMARK}/ox)
987
- count + 1
988
- else
989
- count
990
- end
991
- end
992
-
993
- if field_count.nonzero?
994
- data = args.first(field_count).collect do |el|
995
- if el.kind_of?(Array)
996
- el.join("\n")
997
- else
998
- el.to_s
999
- end
1000
- end
1001
- # shift all arguments that we have just consumed
1002
- args = args[field_count..-1]
1003
- # Is argument count correct ?
1004
- data += [''] * (field_count-data.length) unless data.length == field_count
1005
- else
1006
- data = [[]] # one line of data, contains nothing
1007
- end
1008
-
1009
- first_line = true
1010
- data_left = true
1011
- while data_left
1012
- idx = 0
1013
- data_left = false
1014
-
1015
- parts.each do |part|
1016
- # Is part an escaped format literal ?
1017
- if part =~ /\A (?:\\.)+/ox
1018
- __debug("esc literal: ", part)
1019
- text << part.gsub(/\\(.)/, "\1")
1020
- # Is part a once field mark ?
1021
- elsif part =~ /(#{LFIELDMARK})/ox
1022
- if first_line
1023
- type = __construct_type($1, LJUSTIFIED)
1024
-
1025
- __debug("once field: ", part)
1026
- __debug("data is: ", data[idx])
1027
- text << replace(type, part.length, data[idx])
1028
- __debug("data now: ", data[idx])
1029
- else
1030
- text << (@filler[:left] * part.length)[0, part.length]
1031
- __debug("missing once field: ", part)
1032
- end
1033
- idx += 1
1034
- # Is part a multi field mark ?
1035
- elsif part =~ /(#{FIELDMARK})/ox and part[0, 2] != '~~'
1036
- type = __construct_type($1, BJUSTIFIED)
1037
-
1038
- __debug("multi field: ", part)
1039
- __debug("data is: ", data[idx])
1040
- text << replace(type, part.length, data[idx])
1041
- __debug("data now: ", data[idx])
1042
- data_left = true if data[idx].strip.length > 0
1043
- idx += 1
1044
- # Part is a literal.
1045
- else
1046
- __debug("literal: ", part)
1047
- text << part.gsub(/\0(\0*)/, '\1') # XXX: What is this gsub for ?
1048
-
1049
- # New line ?
1050
- if part == "\n"
1051
- line_count += 1
1052
- if @page_len && line_count >= @page_len
1053
- __debug("\tejecting page: #@page_num")
1054
-
1055
- @page_num += 1
1056
- page_feed = __pagefeed
1057
- header = __header(@page_num)
1058
-
1059
- text << footer + page_feed + header
1060
- previous_footer = footer
1061
-
1062
- footer = __footer(@page_num, false)
1063
-
1064
- line_count = hf_count = (header.count("\n") + footer.count("\n"))
1065
-
1066
- header = page_feed + header
1067
- end
1068
- end
1069
- end # multiway if on part
1070
- end # parts.each
1071
-
1072
- __debug("Accumulated: ", text)
1073
-
1074
- first_line = false
1075
- end
1076
- end # while args or formats left
1077
-
1078
- # Adjust final page header or footer as required
1079
- if hf_count > 0 and line_count == hf_count
1080
- # there is a header that we don't need
1081
- text.sub!(/#{Regexp.escape(header)}\Z/, '')
1082
- elsif line_count > 0 and @page_len and @page_len > 0
1083
- # missing footer:
1084
- text << "\n" * (@page_len - line_count) + footer
1085
- previous_footer = footer
1086
- end
1087
-
1088
- # Replace last footer
1089
- if previous_footer and not previous_footer.empty?
1090
- lastFooter = __footer(@page_num, true)
1091
- footerDiff = lastFooter.count("\n") - previous_footer.count("\n")
1092
-
1093
- # Enough space to squeeze the longer final footer in ?
1094
- if footerDiff > 0 && text =~ /(#{'^[^\S\n]*\n' * footerDiff}#{Regexp.escape(previous_footer)})\Z/
1095
- previous_footer = $1
1096
- footerDiff = 0
1097
- end
1098
-
1099
- # If not, create an empty page for it.
1100
- if footerDiff > 0
1101
- @page_num += 1
1102
- lastHeader = __header(@page_num)
1103
- lastFooter = __footer(@page_num, true)
1104
-
1105
- text << lastHeader
1106
- text << "\n" * (@page_len - lastHeader.count("\n") - lastFooter.count("\n"))
1107
- text << lastFooter
1108
- else
1109
- lastFooter = "\n" * (-footerDiff) + lastFooter
1110
- text[-(previous_footer.length), text.length] = lastFooter
1111
- end
1112
- end
1113
-
1114
- # Trim text
1115
- text.gsub!(/[ ]+$/m, '') if @trim
1116
- text
1117
- end
1118
-
1119
- # Replaces a placeholder with the text given. The +format+ string gives
1120
- # the type of the replace match: When exactly two chars, this indicates
1121
- # a text replace field, when longer, this is a numeric field.
1122
- def replace(format, length, value)
1123
- text = ''
1124
- remaining = length
1125
- filled = 0
1126
-
1127
- __debug("value is: ", value)
1128
-
1129
- if @fill
1130
- value.sub!(/\A\s*/m, '')
1131
- else
1132
- value.sub!(/\A[ \t]*/, '')
1133
- end
1134
-
1135
- if value and format.length > 2
1136
- # find length of numerical fields
1137
- if format =~ /([\]>]+)#{Regexp.escape(DECIMAL)}([\[<]+)/
1138
- ilen, dlen = $1.length, $2.length
1139
- end
1140
-
1141
- # Try to extract a numeric value from +value+
1142
- done = false
1143
- while not done
1144
- num, extra = scanf_remains(value, "%f")
1145
- __debug "Number split into: ", [num, extra]
1146
- done = true
1147
-
1148
- if extra.length == value.length
1149
- value.sub!(/\s*\S*/, '') # skip offending non number value
1150
- if (@numeric & NUMBERS_SKIP_NAN) > 0 && value =~ /\S/
1151
- __debug("Not a Number, retrying ", value)
1152
- done = false
1153
- else
1154
- text = '?' * ilen + DECIMAL + '?' * dlen
1155
- return text
1156
- end
1157
- end
1158
- end
1159
-
1160
- __debug("Finally number is: ", num)
1161
-
1162
- formatted = "%#{format.length}.#{dlen}f"% num
1163
- if formatted.length > format.length
1164
- text = '#' * ilen + DECIMAL + '#'*dlen
1165
- else
1166
- text = formatted
1167
- end
1168
-
1169
- # Only output significant digits. Unless not all places were
1170
- # explicitly requested or the number has more digits than we just
1171
- # output replace trailing zeros with spaces.
1172
- unless (@numeric & NUMBERS_ALL_PLACES > 0) or num.to_s =~ /#{Regexp.escape(DECIMAL)}\d\d{#{dlen},}$/
1173
- text.sub!(/(#{Regexp.escape(DECIMAL)}\d+?)(0+)$/) do |match|
1174
- $1 + ' '*$2.length
1175
- end
1176
- end
1177
-
1178
- value.replace(extra)
1179
- remaining = 0
1180
- else
1181
- while not (value =~ /\S/o).nil?
1182
- # Only whitespace remaining ?
1183
- if ! @fill && value.sub!(/\A[ \t]*\n/, '')
1184
- filled = 2
1185
- break
1186
- end
1187
- break unless value =~ /\A(\s*)(\S+)(.*)\z/om;
1188
-
1189
- ws, word, extra = $1, $2, $3
1190
-
1191
- # Replace all newlines by spaces when fill was specified.
1192
- nonnl = (ws =~ /[^\n]/o)
1193
- if @fill
1194
- ws.gsub!(/\n/) do |match|
1195
- nonnl ? '' : ' '
1196
- end
1197
- end
1198
-
1199
- # Replace all whitespace by one space if squeeze was specified.
1200
- lead = @squeeze ? (ws.length > 0 ? ' ' : '') : ws
1201
- match = lead + word
1202
-
1203
- __debug("Extracted: ", match)
1204
- break if text and match =~ /\n/o
1205
-
1206
- if match.length <= remaining
1207
- __debug("Accepted: ", match)
1208
- text << match
1209
- remaining -= match.length
1210
- value.replace(extra)
1211
- else
1212
- __debug("Need to break: ", match)
1213
- if (remaining - lead.length) >= @min_break
1214
- __debug("Trying to break: ", match)
1215
- broken, left = @break.break(match, remaining, length)
1216
- text << broken
1217
- __debug("Broke as: ", [broken, left])
1218
- value.replace left + extra
1219
-
1220
- # Adjust remaining chars, but allow for underflow.
1221
- t = remaining-broken.length
1222
- if t < 0
1223
- remaining = 0
1224
- else
1225
- remaining = t
1226
- end
1227
- end
1228
- break
1229
- end
1230
-
1231
- filled = 1
1232
- end
1233
- end
1234
-
1235
- if filled.zero? and remaining > 0 and value =~ /\S/ and text.empty?
1236
- value.sub!(/^\s*(.{1,#{remaining}})/, '')
1237
- text = $1
1238
- remaining -= text.length
1239
- end
1240
-
1241
- # Justify format?
1242
- if text =~ / /o and format == 'J' and value =~ /\S/o and filled != 2
1243
- # Fully justified
1244
- text.reverse!
1245
- text.gsub!(/( +)/o) do |match|
1246
- remaining -= 1
1247
- if remaining > 0
1248
- " #{$1}"
1249
- else
1250
- $1
1251
- end
1252
- end while remaining > 0
1253
- text.reverse!
1254
- elsif format =~ /\>|\]/o
1255
- # Right justified
1256
- text[0, 0] = (@filler[:left] * remaining)[0, remaining] if remaining > 0
1257
- elsif format =~ /\^|\|/o
1258
- # Center justified
1259
- half_remaining = remaining / 2
1260
- text[0, 0] = (@filler[:left] * half_remaining)[0, half_remaining]
1261
- half_remaining = remaining - half_remaining
1262
- text << (@filler[:right] * half_remaining)[0, half_remaining]
1263
- else
1264
- # Left justified
1265
- text << (@filler[:right] * remaining)[0, remaining]
1266
- end
1267
-
1268
- text
1269
- end
1270
-
1271
- # Quotes any characters that might be interpreted in +str+ to be normal
1272
- # characters.
1273
- def quote(str)
1274
- puts 'Text::Reform warning: not quoting string...' if @debug
1275
- str
1276
- end
1277
-
1278
- # Turn on internal debugging output for the duration of the
1279
- # block.
1280
- def debug
1281
- d = @debug
1282
- @debug = true
1283
- yield
1284
- @debug = d
1285
- end
1286
-
1287
- class << self
1288
- # Takes a +hyphen+ string as argument, breaks by inserting that hyphen
1289
- # into the word to be hyphenated.
1290
- def break_with(hyphen)
1291
- BreakWith.new(hyphen)
1292
- end
1293
-
1294
- # Takes a +bat+ string as argument, breaks by looking for that
1295
- # substring and breaking just after it.
1296
- def break_at(bat)
1297
- BreakAt.new(bat)
1298
- end
1299
-
1300
- # Breaks by using a 'wrap and slop' algorithm.
1301
- def break_wrap
1302
- BreakWrap.new
1303
- end
1304
-
1305
- # Hyphenates with a class that implements the API of TeX::Hyphen or
1306
- # Text::Hyphen.
1307
- def break_hyphenator(hyphenator)
1308
- BreakHyphenator.new(hyphenator)
1309
- end
1310
- end
1311
-
1312
- # Return the header to use. Header can be in many formats, refer
1313
- # yourself to the documentation.
1314
- def __header(page_num)
1315
- __header_or_footer(@header, page_num, false)
1316
- end
1317
- private :__header
1318
-
1319
- # Return the footer to use for +page_num+ page. +last+ is true if this
1320
- # is the last page.
1321
- def __footer(page_num, last)
1322
- __header_or_footer(@footer, page_num, last)
1323
- end
1324
- private :__footer
1325
-
1326
- # Return a header or footer, disambiguating of types and unchomping is
1327
- # done here.
1328
- #
1329
- #
1330
- # +element+ is the element (header or footer) to process.
1331
- # +page+ is the current page number. +last+ indicates
1332
- # whether this is the last page.
1333
- def __header_or_footer(element, page, last)
1334
- __debug("element: ", element)
1335
- if element.respond_to?(:call)
1336
- if element.arity == 1
1337
- __header_or_footer(element.call(page), page, last)
1338
- else
1339
- __header_or_footer(element.call(page, last), page, last)
1340
- end
1341
- elsif element.kind_of?(Hash)
1342
- page_width = element[:width] || @page_width
1343
- @internal_formatter = self.class.new unless @internal_formatter
1344
-
1345
- if element[:left]
1346
- format = "<" * page_width
1347
- data = element[:left]
1348
- end
1349
-
1350
- if element[:center] or element[:centre]
1351
- format = "^" * page_width
1352
- data = element[:center] || element[:centre]
1353
- end
1354
-
1355
- if element[:right]
1356
- format = ">" * page_width
1357
- data = element[:right]
1358
- end
1359
-
1360
- if format
1361
- if data.respond_to?(:call)
1362
- @internal_formatter.format(format, __header_or_footer(data.call(page), page, last))
1363
- else
1364
- @internal_formatter.format(format, data.dup)
1365
- end
1366
- else
1367
- ""
1368
- end
1369
- else
1370
- unchomp(element)
1371
- end
1372
- end
1373
- private :__header_or_footer
1374
-
1375
- # Use the page_feed attribute to get the page feed text. +page_feed+ can
1376
- # contain a block to call or a String.
1377
- def __pagefeed
1378
- if @page_feed.respond_to?(:call)
1379
- @page_feed.call(@page)
1380
- else
1381
- @page_feed
1382
- end
1383
- end
1384
- private :__pagefeed
1385
-
1386
- # Using Scanf module, scanf a string and return what has not been
1387
- # matched in addition to normal scanf return.
1388
- def scanf_remains(value, fstr, &block)
1389
- if block.nil?
1390
- unless fstr.kind_of?(Scanf::FormatString)
1391
- fstr = Scanf::FormatString.new(fstr)
1392
- end
1393
- [ fstr.match(value), fstr.string_left ]
1394
- else
1395
- value.block_scanf(fstr, &block)
1396
- end
1397
- end
1398
-
1399
- # Count occurrences of \n (lines) of all strings that are passed as
1400
- # parameter.
1401
- def count_lines(*args)
1402
- args.inject(0) do |sum, el|
1403
- sum + el.count("\n")
1404
- end
1405
- end
1406
-
1407
- # Construct a type that can be passed to #replace from last a string.
1408
- def __construct_type(str, justifiedPattern)
1409
- if str =~ /#{justifiedPattern}/x
1410
- 'J'
1411
- else
1412
- str
1413
- end
1414
- end
1415
-
1416
- # Adds a \n character to the end of the line unless it already has a
1417
- # \n at the end of the line. Returns a modified copy of +str+.
1418
- def unchomp(str)
1419
- unchomp!(str.dup)
1420
- end
1421
-
1422
- # Adds a \n character to the end of the line unless it already has a
1423
- # \n at the end of the line.
1424
- def unchomp!(str)
1425
- if str.empty? or str[-1] == ?\n
1426
- str
1427
- else
1428
- str << "\n"
1429
- end
1430
- end
1431
-
1432
- # Debug output. Message +msg+ is printed at start of line, then +obj+
1433
- # is output using +pp+.
1434
- def __debug(msg, obj = nil)
1435
- return unless @debug
1436
- require 'pp'
1437
- print msg
1438
- pp obj
1439
- end
1440
- private :__debug
1441
-
1442
- class BreakWith
1443
- def initialize hyphen
1444
- @hyphen = hyphen
1445
- @hylen = hyphen.length
1446
- end
1447
-
1448
- # Break by inserting a hyphen string.
1449
- #
1450
- # +initial_max_length+:: The maximum size of the first part of the
1451
- # word that will remain on the first line.
1452
- # +total_width+:: The total width that can be appended to this
1453
- # first line.
1454
- def break(str, initial_max_length, total_width)
1455
- if total_width <= @hylen
1456
- ret = [str[0...1], str[1..-1]]
1457
- else
1458
- ret = [str[0...(initial_max_length-@hylen)], str[(initial_max_length-@hylen)..-1]]
1459
- end
1460
-
1461
- if ret.first =~ /\A\s*\Z/
1462
- return ['', str]
1463
- else
1464
- return [ret.first + @hyphen, ret.last]
1465
- end
1466
- end
1467
- end
1468
-
1469
- class BreakAt
1470
- def initialize hyphen
1471
- @hyphen = hyphen
1472
- end
1473
-
1474
- # Break by inserting a hyphen string.
1475
- #
1476
- # +initial_max_length+:: The maximum size of the first part of the
1477
- # word that will remain on the first line.
1478
- # +total_width+:: The total width that can be appended to this
1479
- # first line.
1480
- def break(str, initial_max_length, total_width)
1481
- max = total_width - @hyphen.length
1482
- if max <= 0
1483
- ret = [str[0, 1], str[1, -1]]
1484
- elsif str =~ /(.{1,#{max}}#@hyphen)(.*)/s
1485
- ret = [ $1, $2 ]
1486
- elsif str.length > total_width
1487
- sep = initial_max_length-@hyphen.length
1488
- ret = [
1489
- str[0, sep]+@hyphen,
1490
- str[sep..-1]
1491
- ]
1492
- else
1493
- ret = [ '', str ]
1494
- end
1495
-
1496
- return '', str if ret[0] =~ /\A\s*\Z/
1497
- return ret
1498
- end
1499
- end
1500
-
1501
- class BreakWrap
1502
- def initialize
1503
- end
1504
-
1505
- # Break by wrapping and slopping to the next line.
1506
- #
1507
- # +initial_max_length+:: The maximum size of the first part of the
1508
- # word that will remain on the first line.
1509
- # +total_width+:: The total width that can be appended to this
1510
- # first line.
1511
- def break(text, initial, total)
1512
- if initial == total
1513
- text =~ /\A(\s*\S*)(.*)/
1514
- return $1, $2
1515
- else
1516
- return '', text
1517
- end
1518
- end
1519
- end
1520
-
1521
- # This word-breaker uses a class that implements the API presented by
1522
- # TeX::Hyphen and Text::Hyphen modules.
1523
- class BreakHyphenator
1524
- def initialize(hyphenator)
1525
- @hyphenator = hyphenator
1526
- end
1527
-
1528
- # Break a word using the provided hyphenation module that responds to
1529
- # #hyphenate_to.
1530
- #
1531
- # +initial_max_length+:: The maximum size of the first part of the
1532
- # word that will remain on the first line.
1533
- # +total_width+:: The total width that can be appended to this
1534
- # first line.
1535
- def break(str, initial_max_length, total_width)
1536
- res = @hyphenator.hyphenate_to(str, initial_max_length)
1537
- res.map! { |ee| ee.nil? ? "" : ee }
1538
- res
1539
- end
1540
- end
1541
- end
1
+ # Text::Reform for Ruby
2
+ #
3
+ # Copyright (c) 2004, 2014 by Kaspar Schiess
4
+
5
+ require 'scanf'
6
+ unless defined?(Text)
7
+ module Text; end
8
+ end
9
+
10
+ # = Introduction
11
+ #
12
+ # Text::Reform class is a rewrite from the perl module with the same name by
13
+ # Damian Conway (damian@conway.org). Much of this documentation has been
14
+ # copied from the original documentation and adapted to the Ruby version.
15
+ #
16
+ # The interface is subject to change, since it will undergo major
17
+ # Rubyfication.
18
+ #
19
+ # = Synopsis
20
+ # require 'text/reform'
21
+ # f = Text::Reform.new
22
+ #
23
+ # puts f.format(template, data)
24
+ #
25
+ # = Description
26
+ # == The Reform#format method
27
+ #
28
+ # Reform#format takes a series of format (or "picture") strings followed
29
+ # by replacement values, interpolates those values into each picture
30
+ # string, and returns the result.
31
+ #
32
+ # A picture string consists of sequences of the following characters:
33
+ # [<] Left-justified field indicator. A series of two or
34
+ # more sequential +<+'s specify a left-justified
35
+ # field to be filled by a subsequent value. A single
36
+ # +<+ is formatted as the literal character '<'.
37
+ # [>] Right-justified field indicator. A series of two
38
+ # or more sequential >'s specify a right-justified
39
+ # field to be filled by a subsequent value. A single
40
+ # < is formatted as the literal character '<'.
41
+ # [<<>>] Fully-justified field indicator. Field may be of
42
+ # any width, and brackets need not balance, but
43
+ # there must be at least 2 '<' and 2 '>'.
44
+ # [^] Centre-justified field indicator. A series of two
45
+ # or more sequential ^'s specify a centred field to
46
+ # be filled by a subsequent value. A single ^ is
47
+ # formatted as the literal character '<'.
48
+ # [>>.<<<<] A numerically formatted field with the specified
49
+ # number of digits to either side of the decimal
50
+ # place. See _Numerical formatting_ below.
51
+ # [[] Left-justified block field indicator. Just like a
52
+ # < field, except it repeats as required on
53
+ # subsequent lines. See below. A single [ is
54
+ # formatted as the literal character '['.
55
+ # []] Right-justified block field indicator. Just like a
56
+ # > field, except it repeats as required on
57
+ # subsequent lines. See below. A single ] is
58
+ # formatted as the literal character ']'.
59
+ # [[[]]] Fully-justified block field indicator. Just like a
60
+ # <<<>>> field, except it repeats as required on
61
+ # subsequent lines. See below. Field may be of any
62
+ # width, and brackets need not balance, but there
63
+ # must be at least 2 '[' and 2 ']'.
64
+ # [|] Centre-justified block field indicator. Just like
65
+ # a ^ field, except it repeats as required on
66
+ # subsequent lines. See below. A single | is
67
+ # formatted as the literal character '|'.
68
+ # []]].[[[[] A numerically formatted block field with the
69
+ # specified number of digits to either side of the
70
+ # decimal place. Just like a +>>>.<<<<+ field,
71
+ # except it repeats as required on subsequent lines.
72
+ # See below.
73
+ # [~] A one-character wide block field.
74
+ # [\] Literal escape of next character (e.g. +\+ is
75
+ # formatted as '~', not a one character wide block
76
+ # field).
77
+ # [Any other character] That literal character.
78
+ #
79
+ # Any substitution value which is +nil+ (either explicitly so, or because
80
+ # it is missing) is replaced by an empty string.
81
+ #
82
+ # == Controlling Reform instance options
83
+ # There are several ways to influence options set in the Reform instance:
84
+ #
85
+ # 1. At creation:
86
+ # # using a hash
87
+ # r1 = Text::Reform.new(:squeeze => true)
88
+ #
89
+ # # using a block
90
+ # r2 = Text::Reform.new do |rf|
91
+ # rf.squeeze = true
92
+ # rf.fill = true
93
+ # end
94
+ #
95
+ # 2. Using accessors:
96
+ # r = Text::Reform.new
97
+ # r.squeeze = true
98
+ # r.fill = true
99
+ #
100
+ # The Perl way of interleaving option changes with picture strings and
101
+ # data is currently *NOT* supported.
102
+ #
103
+ # == Controlling line filling
104
+ # #squeeze replaces sequences of spaces or tabs to be replaced with a
105
+ # single space; #fill removes newlines from the input. To minimize all
106
+ # whitespace, you need to specify both options. Hence:
107
+ #
108
+ # format = "EG> [[[[[[[[[[[[[[[[[[[[["
109
+ # data = "h e\t l lo\nworld\t\t\t\t\t"
110
+ # r = Text::Reform.new
111
+ # r.squeeze = false # default, implied
112
+ # r.fill = false # default, implied
113
+ # puts r.format(format, data)
114
+ # # all whitespace preserved:
115
+ # #
116
+ # # EG> h e l lo
117
+ # # EG> world
118
+ #
119
+ # r.squeeze = true
120
+ # r.fill = false # default, implied
121
+ # puts r.format(format, data)
122
+ # # only newlines preserved
123
+ # #
124
+ # # EG> h e l lo
125
+ # # EG> world
126
+ #
127
+ # r.squeeze = false # default, implied
128
+ # r.fill = true
129
+ # puts r.format(format, data)
130
+ # # only spaces/tabs preserved:
131
+ # #
132
+ # # EG> h e l lo world
133
+ #
134
+ # r.fill = true
135
+ # r.squeeze = true
136
+ # puts r.format(format, data)
137
+ # # no whitespace preserved:
138
+ # #
139
+ # # EG> h e l lo world
140
+ #
141
+ # Whether or not filling or squeezing is in effect, #format can also be
142
+ # directed to trim any extra whitespace from the end of each line it
143
+ # formats, using the #trim option. If this option is specified with a
144
+ # +true+ value, every line returned by #format will automatically have the
145
+ # substitution +.gsub!(/[ \t]+/, '')+ applied to it.
146
+ #
147
+ # r.format("[[[[[[[[[[[", 'short').length # => 11
148
+ # r.trim = true
149
+ # r.format("[[[[[[[[[[[", 'short').length # => 6
150
+ #
151
+ # It is also possible to control the character used to fill lines that are
152
+ # too short, using the #filler option. If this option is specified the
153
+ # value of the #filler flag is used as the fill string, rather than the
154
+ # default +" "+.
155
+ #
156
+ # For example:
157
+ # r.filler = '*'
158
+ # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$123.4')
159
+ # prints:
160
+ # Pay bearer: *******$123.4*******
161
+ #
162
+ # If the filler string is longer than one character, it is truncated to
163
+ # the appropriate length. So:
164
+ # r.filler = '-->'
165
+ # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$123.4')
166
+ # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$13.4')
167
+ # print r.format("Pay bearer: ^^^^^^^^^^^^^^^^^^^^", '$1.4')
168
+ # prints:
169
+ # Pay bearer: -->-->-$123.4-->-->-
170
+ # Pay bearer: -->-->--$13.4-->-->-
171
+ # Pay bearer: -->-->--$1.4-->-->--
172
+ #
173
+ # If the value of the #filler option is a hash, then its +:left+ and
174
+ # +:right+ entries specify separate filler strings for each side of an
175
+ # interpolated value.
176
+ #
177
+ # == Options
178
+ # The Perl variant supports option switching during processing of the
179
+ # arguments of a single call to #format. This has been removed while
180
+ # porting to Ruby, since I believe that this does not add to clarity
181
+ # of code. So you have to change options explicitly.
182
+ #
183
+ # == Data argument types and handling
184
+ # The +data+ part of the call to format can be either in String form, the
185
+ # items being newline separated, or in Array form. The array form can
186
+ # contain any kind of type you want, as long as it supports #to_s.
187
+ #
188
+ # So all of the following examples return the same result:
189
+ # # String form
190
+ # r.format("]]]].[[", "1234\n123")
191
+ # # Array form
192
+ # r.format("]]]].[[", [ 1234, 123 ])
193
+ # # Array with another type
194
+ # r.format("]]]].[[", [ 1234.0, 123.0 ])
195
+ #
196
+ # == Multi-line format specifiers and interleaving
197
+ # By default, if a format specifier contains two or more lines (i.e. one
198
+ # or more newline characters), the entire format specifier is repeatedly
199
+ # filled as a unit, until all block fields have consumed their
200
+ # corresponding arguments. For example, to build a simple look-up table:
201
+ # values = (1..12).to_a
202
+ # squares = values.map { |el| sprintf "%.6g", el**2 }
203
+ # roots = values.map { |el| sprintf "%.6g", Math.sqrt(el) }
204
+ # logs = values.map { |el| sprintf "%.6g", Math.log(el) }
205
+ # inverses = values.map { |el| sprintf "%.6g", 1/el }
206
+ #
207
+ # puts reform.format(
208
+ # " N N**2 sqrt(N) log(N) 1/N",
209
+ # "=====================================================",
210
+ # "| [[ | [[[ | [[[[[[[[[[ | [[[[[[[[[ | [[[[[[[[[ |" +
211
+ # "-----------------------------------------------------",
212
+ # values, squares, roots, logs, inverses
213
+ # )
214
+ #
215
+ # The multiline format specifier:
216
+ # "| [[ | [[[ | [[[[[[[[[[ | [[[[[[[[[ | [[[[[[[[[ |" +
217
+ # "-----------------------------------------------------"
218
+ #
219
+ # is treated as a single logical line. So #format alternately fills the
220
+ # first physical line (interpolating one value from each of the arrays)
221
+ # and the second physical line (which puts a line of dashes between each
222
+ # row of the table) producing:
223
+ # N N**2 sqrt(N) log(N) 1/N
224
+ # =====================================================
225
+ # | 1 | 1 | 1 | 0 | 1 |
226
+ # -----------------------------------------------------
227
+ # | 2 | 4 | 1.41421 | 0.693147 | 0.5 |
228
+ # -----------------------------------------------------
229
+ # | 3 | 9 | 1.73205 | 1.09861 | 0.333333 |
230
+ # -----------------------------------------------------
231
+ # | 4 | 16 | 2 | 1.38629 | 0.25 |
232
+ # -----------------------------------------------------
233
+ # | 5 | 25 | 2.23607 | 1.60944 | 0.2 |
234
+ # -----------------------------------------------------
235
+ # | 6 | 36 | 2.44949 | 1.79176 | 0.166667 |
236
+ # -----------------------------------------------------
237
+ # | 7 | 49 | 2.64575 | 1.94591 | 0.142857 |
238
+ # -----------------------------------------------------
239
+ # | 8 | 64 | 2.82843 | 2.07944 | 0.125 |
240
+ # -----------------------------------------------------
241
+ # | 9 | 81 | 3 | 2.19722 | 0.111111 |
242
+ # -----------------------------------------------------
243
+ # | 10 | 100 | 3.16228 | 2.30259 | 0.1 |
244
+ # -----------------------------------------------------
245
+ # | 11 | 121 | 3.31662 | 2.3979 | 0.0909091 |
246
+ # -----------------------------------------------------
247
+ # | 12 | 144 | 3.4641 | 2.48491 | 0.0833333 |
248
+ # -----------------------------------------------------
249
+ #
250
+ # This implies that formats and the variables from which they're filled
251
+ # need to be interleaved. That is, a multi-line specification like this:
252
+ # puts r.format(
253
+ # "Passed: ##
254
+ # [[[[[[[[[[[[[[[ # single format specification
255
+ # Failed: # (needs two sets of data)
256
+ # [[[[[[[[[[[[[[[", ##
257
+ # passes, fails) ## data for previous format
258
+ # would print:
259
+ # Passed:
260
+ # <pass 1>
261
+ # Failed:
262
+ # <fail 1>
263
+ # Passed:
264
+ # <pass 2>
265
+ # Failed:
266
+ # <fail 2>
267
+ # Passed:
268
+ # <pass 3>
269
+ # Failed:
270
+ # <fail 3>
271
+ #
272
+ # because the four-line format specifier is treated as a single unit, to
273
+ # be repeatedly filled until all the data in +passes+ and +fails+ has been
274
+ # consumed.
275
+ #
276
+ # Unlike the table example, where this unit filling correctly put a line
277
+ # of dashes between lines of data, in this case the alternation of passes
278
+ # and fails is probably /not/ the desired effect.
279
+ #
280
+ # Judging by the labels, it is far more likely that the user wanted:
281
+ # Passed:
282
+ # <pass 1>
283
+ # <pass 2>
284
+ # <pass 3>
285
+ # Failed:
286
+ # <fail 4>
287
+ # <fail 5>
288
+ # <fail 6>
289
+ #
290
+ # To achieve that, either explicitly interleave the formats and their data
291
+ # sources:
292
+ # puts r.format(
293
+ # "Passed:", ## single format (no data required)
294
+ # " [[[[[[[[[[[[[[[", ## single format (needs one set of data)
295
+ # passes, ## data for previous format
296
+ # "Failed:", ## single format (no data required)
297
+ # " [[[[[[[[[[[[[[[", ## single format (needs one set of data)
298
+ # fails) ## data for previous format
299
+ # or instruct #format to do it for you automagically, by setting the
300
+ # 'interleave' flag +true+:
301
+ #
302
+ # r.interleave = true
303
+ # puts r.format(
304
+ # "Passed: ##
305
+ # [[[[[[[[[[[[[[[ # single format
306
+ # Failed: # (needs two sets of data)
307
+ # [[[[[[[[[[[[[[[", ##
308
+ # ## data to be automagically interleaved
309
+ # passes, fails) # as necessary between lines of previous
310
+ # ## format
311
+ #
312
+ # == How #format hyphenates
313
+ # Any line with a block field repeats on subsequent lines until all block
314
+ # fields on that line have consumed all their data. Non-block fields on
315
+ # these lines are replaced by the appropriate number of spaces.
316
+ #
317
+ # Words are wrapped whole, unless they will not fit into the field at all,
318
+ # in which case they are broken and (by default) hyphenated. Simple
319
+ # hyphenation is used (i.e. break at the +N-1+th character and insert a
320
+ # '-'), unless a suitable alternative subroutine is specified instead.
321
+ #
322
+ # Words will not be broken if the break would leave less than 2 characters
323
+ # on the current line. This minimum can be varied by setting the
324
+ # +min_break+ option to a numeric value indicating the minumum total broken
325
+ # characters (including hyphens) required on the current line. Note that,
326
+ # for very narrow fields, words will still be broken (but
327
+ # __unhyphenated__). For example:
328
+ #
329
+ # puts r.format('~', 'split')
330
+ #
331
+ # would print:
332
+ #
333
+ # s
334
+ # p
335
+ # l
336
+ # i
337
+ # t
338
+ #
339
+ # whilst:
340
+ #
341
+ # r.min_break= 1
342
+ # puts r.format('~', 'split')
343
+ #
344
+ # would print:
345
+ #
346
+ # s-
347
+ # p-
348
+ # l-
349
+ # i-
350
+ # t
351
+ #
352
+ # Alternative breaking strategies can be specified using the "break"
353
+ # option in a configuration hash. For example:
354
+ #
355
+ # r.break = MyBreaker.new
356
+ # r.format(fmt, data)
357
+ #
358
+ # #format expects a user-defined line-breaking strategy to listen to the
359
+ # method #break that takes three arguments (the string to be broken, the
360
+ # maximum permissible length of the initial section, and the total width
361
+ # of the field being filled). #break must return a list of two strings:
362
+ # the initial (broken) section of the word, and the remainder of the
363
+ # string respectivly).
364
+ #
365
+ # For example:
366
+ # class MyBreaker
367
+ # def break(str, initial, total)
368
+ # [ str[0, initial-1].'~'], str[initial-1..-1] ]
369
+ # end
370
+ # end
371
+ #
372
+ # r.break = MyBreaker.new
373
+ #
374
+ # makes '~' the hyphenation character, whilst:
375
+ # class WrapAndSlop
376
+ # def break(str, initial, total)
377
+ # if (initial == total)
378
+ # str =~ /\A(\s*\S*)(.*)/
379
+ # [ $1, $2 ]
380
+ # else
381
+ # [ '', str ]
382
+ # end
383
+ # end
384
+ # end
385
+ #
386
+ # r.break = WrapAndSlop.new
387
+ #
388
+ # wraps excessively long words to the next line and "slops" them over the
389
+ # right margin if necessary.
390
+ #
391
+ # The Text::Reform class provides three functions to simplify the use of
392
+ # variant hyphenation schemes. Text::Reform::break_wrap returns an
393
+ # instance implementing the "wrap-and-slop" algorithm shown in the last
394
+ # example, which could therefore be rewritten:
395
+ #
396
+ # r.break = Text::Reform.break_wrap
397
+ #
398
+ # Text::Reform::break_with takes a single string argument and returns an
399
+ # instance of a class which hyphenates by cutting off the text at the
400
+ # right margin and appending the string argument. Hence the first of the
401
+ # two examples could be rewritten:
402
+ #
403
+ # r.break = Text::Reform.break_with('~')
404
+ #
405
+ # The method Text::Reform::break_at takes a single string argument and
406
+ # returns a reference to a sub which hyphenates by breaking immediately
407
+ # after that string. For example:
408
+ #
409
+ # r.break = Text::Reform.break_at('-')
410
+ # r.format("[[[[[[[[[[[[[[", "The Newton-Raphson methodology")
411
+ #
412
+ # returns:
413
+ # "The Newton-
414
+ # Raphson
415
+ # methodology"
416
+ #
417
+ # Note that this differs from the behaviour of Text::Reform::break_with,
418
+ # which would be:
419
+ #
420
+ # r.break = Text::Reform.break_width('-')
421
+ # r.format("[[[[[[[[[[[[[[", "The Newton-Raphson methodology")
422
+ #
423
+ # returns:
424
+ # "The Newton-R-
425
+ # aphson metho-
426
+ # dology"
427
+ #
428
+ # Choosing the correct breaking strategy depends on your kind of data.
429
+ #
430
+ # The method Text::Reform::break_hyphen returns an instance of a class
431
+ # which hyphenates using a Ruby hyphenator. The hyphenator must be
432
+ # provided to the method. At the time of release, there are two
433
+ # implementations of hyphenators available: TeX::Hyphen by Martin DeMello
434
+ # and Austin Ziegler (a Ruby port of Jan Pazdziora's TeX::Hyphen module);
435
+ # and Text::Hyphen by Austin Ziegler (a significant recoding of
436
+ # TeX::Hyphen to better support non-English languages).
437
+ #
438
+ # For example:
439
+ # r.break = Text::Reform.break_hyphen
440
+ #
441
+ # Note that in the previous example the calls to .break_at, .break_wrap
442
+ # and .break_hyphen produce instances of the corresponding strategy class.
443
+ #
444
+ # == The algorithm #format uses is:
445
+ #
446
+ # 1. If interleaving is specified, split the first string in the
447
+ # argument list into individual format lines and add a
448
+ # terminating newline (unless one is already present).
449
+ # therwise, treat the entire string as a single "line" (like
450
+ # /s does in regexes)
451
+ #
452
+ # 2. For each format line...
453
+ #
454
+ # 1. determine the number of fields and shift
455
+ # that many values off the argument list and
456
+ # into the filling list. If insufficient
457
+ # arguments are available, generate as many
458
+ # empty strings as are required.
459
+ #
460
+ # 2. generate a text line by filling each field
461
+ # in the format line with the initial contents
462
+ # of the corresponding arg in the filling list
463
+ # (and remove those initial contents from the arg).
464
+ #
465
+ # 3. replace any <,>, or ^ fields by an equivalent
466
+ # number of spaces. Splice out the corresponding
467
+ # args from the filling list.
468
+ #
469
+ # 4. Repeat from step 2.2 until all args in the
470
+ # filling list are empty.
471
+ #
472
+ # 3. concatenate the text lines generated in step 2
473
+ #
474
+ # Note that in difference to the Perl version of Text::Reform,
475
+ # this version does not currently loop over several format strings
476
+ # in one function call.
477
+ #
478
+ #
479
+ # == Reform#format examples
480
+ #
481
+ # As an example of the use of #format, the following:
482
+ #
483
+ # count = 1
484
+ # text = "A big long piece of text to be formatted exquisitely"
485
+ # output = ''
486
+ # output << r.format(" |||| <<<<<<<<<< ", count, text)
487
+ # output << r.format(" ---------------- ",
488
+ # " ^^^^ ]]]]]]]]]]| ", count+11, text)
489
+ #
490
+ # results in +output+:
491
+ # 1 A big lon-
492
+ # ----------------
493
+ # 12 g piece|
494
+ # of text|
495
+ # to be for-|
496
+ # matted ex-|
497
+ # quisitely|
498
+ #
499
+ # Note that block fields in a multi-line format string,
500
+ # cause the entire multi-line format to be repeated as
501
+ # often as necessary.
502
+ #
503
+ # Unlike traditional Perl #format arguments, picture strings and
504
+ # arguments cannot be interleaved in Ruby version. This is partly
505
+ # by intention to see if the feature is a feature or if it
506
+ # can be disposed with. Another example:
507
+ #
508
+ # report = ''
509
+ # report << r.format(
510
+ # 'Name Rank Serial Number',
511
+ # '==== ==== =============',
512
+ # '<<<<<<<<<<<<< ^^^^ <<<<<<<<<<<<<',
513
+ # name, rank, serial_number
514
+ # )
515
+ #
516
+ # results in:
517
+ #
518
+ # Name Rank Serial Number
519
+ # ==== ==== =============
520
+ # John Doe high 314159
521
+ #
522
+ # == Numerical formatting
523
+ #
524
+ # The ">>>.<<<" and "]]].[[[" field specifiers may be used to format
525
+ # numeric values about a fixed decimal place marker. For example:
526
+ #
527
+ # puts r.format('(]]]]].[[)', %w{
528
+ # 1
529
+ # 1.0
530
+ # 1.001
531
+ # 1.009
532
+ # 123.456
533
+ # 1234567
534
+ # one two
535
+ # })
536
+ #
537
+ # would print:
538
+ #
539
+ # ( 1.0)
540
+ # ( 1.0)
541
+ # ( 1.00)
542
+ # ( 1.01)
543
+ # ( 123.46)
544
+ # (#####.##)
545
+ # (?????.??)
546
+ # (?????.??)
547
+ #
548
+ # Fractions are rounded to the specified number of places after the
549
+ # decimal, but only significant digits are shown. That's why, in the
550
+ # above example, 1 and 1.0 are formatted as "1.0", whilst 1.001 is
551
+ # formatted as "1.00".
552
+ #
553
+ # You can specify that the maximal number of decimal places always be used
554
+ # by giving the configuration option 'numeric' the value NUMBERS_ALL_PLACES.
555
+ # For example:
556
+ #
557
+ # r.numeric = Text::Reform::NUMBERS_ALL_PLACES
558
+ # puts r.format('(]]]]].[[)', <<EONUMS)
559
+ # 1
560
+ # 1.0
561
+ # EONUMS
562
+ #
563
+ # would print:
564
+ #
565
+ # ( 1.00)
566
+ # ( 1.00)
567
+ #
568
+ # Note that although decimal digits are rounded to fit the specified width, the
569
+ # integral part of a number is never modified. If there are not enough places
570
+ # before the decimal place to represent the number, the entire number is
571
+ # replaced with hashes.
572
+ #
573
+ # If a non-numeric sequence is passed as data for a numeric field, it is
574
+ # formatted as a series of question marks. This querulous behaviour can be
575
+ # changed by giving the configuration option 'numeric' a value that
576
+ # matches /\bSkipNaN\b/i in which case, any invalid numeric data is simply
577
+ # ignored. For example:
578
+ #
579
+ #
580
+ # r.numeric = Text::Reform::NUMBERS_SKIP_NAN
581
+ # puts r.format('(]]]]].[[)', %w{
582
+ # 1
583
+ # two three
584
+ # 4
585
+ # })
586
+ #
587
+ #
588
+ # would print:
589
+ #
590
+ # ( 1.0)
591
+ # ( 4.0)
592
+ #
593
+ # == Filling block fields with lists of values
594
+ #
595
+ # If an argument contains an array, then #format
596
+ # automatically joins the elements of the array into a single string, separating
597
+ # each element with a newline character. As a result, a call like this:
598
+ #
599
+ # svalues = %w{ 1 10 100 1000 }
600
+ # nvalues = [1, 10, 100, 1000]
601
+ # puts r.format(
602
+ # "(]]]].[[)",
603
+ # svalues # you could also use nvalues here.
604
+ # )
605
+ #
606
+ # will print out
607
+ #
608
+ # ( 1.00)
609
+ # ( 10.00)
610
+ # (100.00)
611
+ # (1000.00)
612
+ #
613
+ # as might be expected.
614
+ #
615
+ # Note: While String arguments are consumed during formatting process
616
+ # and will be empty at the end of formatting, array arguments are not.
617
+ # So svalues (nvalues) still contains [1,10,100,1000] after the call
618
+ # to #format.
619
+ #
620
+ # == Headers, footers, and pages
621
+ #
622
+ # The #format method can also insert headers, footers, and page-feeds
623
+ # as it formats. These features are controlled by the "header", "footer",
624
+ # "page_feed", "page_len", and "page_num" options.
625
+ #
626
+ # If the +page_num+ option is set to an Integer value, page numbering
627
+ # will start at that value.
628
+ #
629
+ # The +page_len+ option specifies the total number of lines in a page (including
630
+ # headers, footers, and page-feeds).
631
+ #
632
+ # The +page_width+ option specifies the total number of columns in a page.
633
+ #
634
+ # If the +header+ option is specified with a string value, that string is
635
+ # used as the header of every page generated. If it is specified as a block,
636
+ # that block is called at the start of every page and
637
+ # its return value used as the header string. When called, the block is
638
+ # passed the current page number.
639
+ #
640
+ # Likewise, if the +footer+ option is specified with a string value, that
641
+ # string is used as the footer of every page generated. If it is specified
642
+ # as a block, that block is called at the *start*
643
+ # of every page and its return value used as the footer string. When called,
644
+ # the footer block is passed the current page number.
645
+ #
646
+ # Both the header and footer options can also be specified as hash references.
647
+ # In this case the hash entries for keys +left+, +centre+ (or +center+), and
648
+ # +right+ specify what is to appear on the left, centre, and right of the
649
+ # header/footer. The entry for the key +width+ specifies how wide the
650
+ # footer is to be. If the +width+ key is omitted, the +page_width+ configuration
651
+ # option (which defaults to 72 characters) is used.
652
+ #
653
+ # The +:left+, +:centre+, and +:right+ values may be literal
654
+ # strings, or blocks (just as a normal header/footer specification may
655
+ # be.) See the second example, below.
656
+ #
657
+ # Another alternative for header and footer options is to specify them as a
658
+ # block that returns a hash reference. The subroutine is called for each
659
+ # page, then the resulting hash is treated like the hashes described in the
660
+ # preceding paragraph. See the third example, below.
661
+ #
662
+ # The +page_feed+ option acts in exactly the same way, to produce a
663
+ # page_feed which is appended after the footer. But note that the page_feed
664
+ # is not counted as part of the page length.
665
+ #
666
+ # All three of these page components are recomputed at the *start of each
667
+ # new page*, before the page contents are formatted (recomputing the header
668
+ # and footer first makes it possible to determine how many lines of data to
669
+ # format so as to adhere to the specified page length).
670
+ #
671
+ # When the call to #format is complete and the data has been fully formatted,
672
+ # the footer subroutine is called one last time, with an extra argument of +true+.
673
+ # The string returned by this final call is used as the final footer.
674
+ #
675
+ # So for example, a 60-line per page report, starting at page 7,
676
+ # with appropriate headers and footers might be set up like so:
677
+ #
678
+ # small = Text::Reform.new
679
+ # r.header = lambda do |page| "Page #{page}\n\n" end
680
+ # r.footer = lambda do |page, last|
681
+ # if last
682
+ # ''
683
+ # else
684
+ # ('-'*50 + "\n" + small.format('>'*50, "...#{page+1}"))
685
+ # end
686
+ # end
687
+ # r.page_feed = "\n\n"
688
+ # r.page_len = 60
689
+ # r.page_num = 7
690
+ #
691
+ # r.format(template, data)
692
+ #
693
+ # Note that you can't reuse the +r+ instance of Text::Reform inside
694
+ # the footer, it will end up calling itself recursivly until stack
695
+ # exhaustion.
696
+ #
697
+ # Alternatively, to set up headers and footers such that the running
698
+ # head is right justified in the header and the page number is centred
699
+ # in the footer:
700
+ #
701
+ # r.header = { :right => 'Running head' }
702
+ # r.footer = { :centre => lambda do |page| "page #{page}" end }
703
+ # r.page_len = 60
704
+ #
705
+ # r.format(template, data)
706
+ #
707
+ # The footer in the previous example could also have been specified the other
708
+ # way around, as a block that returns a hash (rather than a hash containing
709
+ # a block):
710
+ #
711
+ # r.header = { :right => 'Running head' }
712
+ # r.footer = lambda do |page| { :center => "page #{page}" } end
713
+ #
714
+ #
715
+ # = AUTHOR
716
+ #
717
+ # Original Perl library and documentation:
718
+ # Damian Conway (damian at conway dot org)
719
+ #
720
+ # Translating everything to Ruby (and leaving a lot of stuff out):
721
+ # Kaspar Schiess (eule at space dot ch)
722
+ #
723
+ # = BUGS
724
+ #
725
+ # There are undoubtedly serious bugs lurking somewhere in code this funky :-)
726
+ # Bug reports and other feedback are most welcome.
727
+ #
728
+ # = COPYRIGHT
729
+ #
730
+ # Copyright (c) 2005, Kaspar Schiess. All Rights Reserved.
731
+ # This module is free software. It may be used, redistributed
732
+ # and/or modified under the terms of the Ruby License
733
+ # (see http://www.ruby-lang.org/en/LICENSE.txt)
734
+ class Text::Reform
735
+ VERSION = "0.3.0"
736
+
737
+ # various regexp parts for matching patterns.
738
+ BSPECIALS = %w{ [ | ] }
739
+ LSPECIALS = %w{ < ^ > }
740
+ LJUSTIFIED = "[<]{2,} [>]{2,}"
741
+ BJUSTIFIED = "[\\[]{2,} [\\]]{2,}"
742
+ BSINGLE = "~+"
743
+ SPECIALS = [BSPECIALS, LSPECIALS].flatten.map { |spec| Regexp.escape(spec)+"{2,}" }
744
+ FIXED_FIELDPAT = [LJUSTIFIED, BJUSTIFIED, BSINGLE, SPECIALS ].flatten.join('|')
745
+
746
+ DECIMAL = '.' # TODO: Make this locale dependent
747
+ # Matches one or more > followed by . followed by one or more <
748
+ LNUMERICAL = "[>]+ (?:#{Regexp.escape(DECIMAL)}[<]{1,})"
749
+ # Matches one or more ] followed by . followed by one or more [
750
+ BNUMERICAL = "[\\]]+ (?: #{Regexp.escape(DECIMAL)} [\\[]{1,})"
751
+
752
+ FIELDPAT = [LNUMERICAL, BNUMERICAL, FIXED_FIELDPAT].join('|')
753
+
754
+ LFIELDMARK = [LNUMERICAL, LJUSTIFIED, LSPECIALS.map { |l| Regexp.escape(l) + "{2}" } ].flatten.join('|')
755
+ BFIELDMARK = [BNUMERICAL, BJUSTIFIED, BSINGLE, BSPECIALS.map { |l| Regexp.escape(l) + "{2}" } ].flatten.join('|')
756
+
757
+ FIELDMARK = [LNUMERICAL, BNUMERICAL, BSINGLE, LJUSTIFIED, BJUSTIFIED, LFIELDMARK, BFIELDMARK].flatten.join('|')
758
+
759
+ # For use with #header, #footer, and #page_feed; this will clear the
760
+ # header, footer, or page feed block result to be an empty block.
761
+ CLEAR_BLOCK = lambda { |*args| "" }
762
+
763
+ # Proc returning page header. This is called before the page actually
764
+ # gets formatted to permit calculation of page length.
765
+ #
766
+ # *Default*:: +CLEAR_BLOCK+
767
+ attr_accessor :header
768
+
769
+ # Proc returning the page footer. This gets called before the
770
+ # page gets formatted to permit calculation of page length.
771
+ #
772
+ # *Default*:: +CLEAR_BLOCK+
773
+ attr_accessor :footer
774
+
775
+ # Proc to be called for page feed text. This is also called at
776
+ # the start of each page, but does not count towards page length.
777
+ #
778
+ # *Default*:: +CLEAR_BLOCK+
779
+ attr_accessor :page_feed
780
+
781
+ # Specifies the total number of lines in a page (including headers,
782
+ # footers, and page-feeds).
783
+ #
784
+ # *Default*:: +nil+
785
+ attr_accessor :page_len
786
+
787
+ # Where to start page numbering.
788
+ #
789
+ # *Default*:: +nil+
790
+ attr_accessor :page_num
791
+
792
+ # Specifies the total number of columns in a page.
793
+ #
794
+ # *Default*:: 72
795
+ attr_accessor :page_width
796
+
797
+ # Break class instance that is used to break words in hyphenation. This
798
+ # class must have a #break method accepting the three arguments +str+,
799
+ # +initial_max_length+ and +maxLength+.
800
+ #
801
+ # You can directly call the break_* methods to produce such a class
802
+ # instance for you; Available methods are #break_width, #break_at,
803
+ # #break_wrap, #break_hyphenator.
804
+ #
805
+ # *Default*:: Text::Hyphen::break_with('-')
806
+ attr_accessor :break
807
+
808
+ # Specifies the minimal number of characters that must be left on a
809
+ # line. This prevents breaking of words below its value.
810
+ #
811
+ # *Default*:: 2
812
+ attr_accessor :min_break
813
+
814
+ # If +true+, causes any sequence of spaces and/or tabs (but not
815
+ # newlines) in an interpolated string to be replaced with a single
816
+ # space.
817
+ #
818
+ # *Default*:: +false+
819
+ attr_accessor :squeeze
820
+
821
+ # If +true+, causes newlines to be removed from the input. If you want
822
+ # to squeeze all whitespace, set #fill and #squeeze to true.
823
+ #
824
+ # *Default*:: +false+
825
+ attr_accessor :fill
826
+
827
+ # Controls character that is used to fill lines that are too short.
828
+ # If this attribute has a hash value, the symbols :left and :right
829
+ # store the filler character to use on the left and the right,
830
+ # respectivly.
831
+ #
832
+ # *Default*:: +' '+ on both sides
833
+ attr_accessor :filler
834
+ undef :filler=
835
+ def filler=(value) #:nodoc:
836
+ if value.kind_of?(Hash)
837
+ unless value[:left] and value[:right]
838
+ raise ArgumentError, "If #filler is provided as a Hash, it must contain the keys :left and :right"
839
+ else
840
+ @filler = value
841
+ end
842
+ else
843
+ @filler = { :left => value, :right => value }
844
+ end
845
+ end
846
+
847
+ # This implies that formats and the variables from which they're filled
848
+ # need to be interleaved. That is, a multi-line specification like this:
849
+ #
850
+ # print format(
851
+ # "Passed: ##
852
+ # [[[[[[[[[[[[[[[ # single format specification
853
+ # Failed: # (needs two sets of data)
854
+ # [[[[[[[[[[[[[[[", ##
855
+ #
856
+ # fails, passes) ## two arrays, data for previous format
857
+ #
858
+ # would print:
859
+ #
860
+ # Passed:
861
+ # <pass 1>
862
+ # Failed:
863
+ # <fail 1>
864
+ # Passed:
865
+ # <pass 2>
866
+ # Failed:
867
+ # <fail 2>
868
+ # Passed:
869
+ # <pass 3>
870
+ # Failed:
871
+ # <fail 3>
872
+ #
873
+ # because the four-line format specifier is treated as a single unit, to
874
+ # be repeatedly filled until all the data in +passes+ and +fails+ has
875
+ # been consumed.
876
+ #
877
+ # *Default*:: false
878
+ attr_accessor :interleave
879
+
880
+ # Numbers are printed, leaving off unnecessary decimal places. Non-
881
+ # numeric data is printed as a series of question marks. This is the
882
+ # default for formatting numbers.
883
+ NUMBERS_NORMAL = 0
884
+ # Numbers are printed, retaining all decimal places. Non-numeric data is
885
+ # printed as a series of question marks.
886
+ #
887
+ # [[[[[.]] # format
888
+ # 1.0 -> 1.00
889
+ # 1 -> 1.00
890
+ NUMBERS_ALL_PLACES = 1
891
+ # Numbers are printed as ffor +NUMBERS_NORMAL+, but NaN ("not a number")
892
+ # values are skipped.
893
+ NUMBERS_SKIP_NAN = 2
894
+ # Numbers are printed as for +NUMBERS_ALL_PLACES+, but NaN values are
895
+ # skipped.
896
+ NUMBERS_ALL_AND_SKIP = NUMBERS_ALL_PLACES | NUMBERS_SKIP_NAN
897
+
898
+ # Specifies handling method for numerical data. Allowed values include:
899
+ # * +NUMBERS_NORMAL+
900
+ # * +NUMBERS_ALL_PLACES+
901
+ # * +NUMBERS_SKIP_NAN+
902
+ # * +NUMBERS_ALL_AND_SKIP+
903
+ #
904
+ # *Default*:: NUMBERS_NORMAL
905
+ attr_accessor :numeric
906
+
907
+ # Controls trimming of whitespace at end of lines.
908
+ #
909
+ # *Default*:: +true+
910
+ attr_accessor :trim
911
+
912
+ # Create a Text::Reform object. Accepts an optional hash of
913
+ # construction option (this will change to named parameters in Ruby
914
+ # 2.0). After the initial object is constructed (with either the
915
+ # provided or default values), the object will be yielded (as +self+) to
916
+ # an optional block for further construction and operation.
917
+
918
+ def initialize(options = {}) #:yields self:
919
+ @debug = options[:debug] || false
920
+ @header = options[:header] || CLEAR_BLOCK
921
+ @footer = options[:footer] || CLEAR_BLOCK
922
+ @page_feed = options[:page_feed] || CLEAR_BLOCK
923
+ @page_len = options[:page_len] || nil
924
+ @page_num = options[:page_num] || nil
925
+ @page_width = options[:page_width] || 72
926
+ @break = options[:break] || Text::Reform.break_with('-')
927
+ @min_break = options[:min_break] || 2
928
+ @squeeze = options[:squeeze] || false
929
+ @fill = options[:fill] || false
930
+ @filler = options[:filler] || { :left => ' ', :right => ' ' }
931
+ @interleave = options[:interleave] || false
932
+ @numeric = options[:numeric] || 0
933
+ @trim = options[:trim] || false
934
+
935
+ yield self if block_given?
936
+ end
937
+
938
+ # Format data according to +format+.
939
+ def format(*args)
940
+ @page_num ||= 1
941
+
942
+ __debug("Acquiring header and footer: ", @page_num)
943
+ header = __header(@page_num)
944
+ footer = __footer(@page_num, false)
945
+
946
+ previous_footer = footer
947
+
948
+ line_count = count_lines(header, footer)
949
+ hf_count = line_count
950
+
951
+ text = header
952
+ format_stack = []
953
+
954
+ while (args and not args.empty?) or (not format_stack.empty?)
955
+ __debug("Arguments: ", args)
956
+ __debug("Formats left: ", format_stack)
957
+
958
+ if format_stack.empty?
959
+ if @interleave
960
+ # split format in its parts and recombine line by line
961
+ format_stack += args.shift.split(%r{\n}o).collect { |fmtline| fmtline << "\n" }
962
+ else
963
+ format_stack << args.shift
964
+ end
965
+ end
966
+
967
+ format = format_stack.shift
968
+
969
+ parts = format.split(%r{
970
+ ( # Capture
971
+ \n | # newline... OR
972
+ (?:\\.)+ | # one or more escapes... OR
973
+ #{FIELDPAT} | # patterns
974
+ )}ox)
975
+ parts << "\n" unless parts[-1] == "\n"
976
+ __debug("Parts: ", parts)
977
+
978
+ # Count all fields (inject 0, increment when field) and prepare
979
+ # data.
980
+ field_count = parts.inject(0) do |count, el|
981
+ if (el =~ /#{LFIELDMARK}/ox or el =~ /#{FIELDMARK}/ox)
982
+ count + 1
983
+ else
984
+ count
985
+ end
986
+ end
987
+
988
+ if field_count.nonzero?
989
+ data = args.first(field_count).collect do |el|
990
+ if el.kind_of?(Array)
991
+ el.join("\n")
992
+ else
993
+ el.to_s
994
+ end
995
+ end
996
+ # shift all arguments that we have just consumed
997
+ args = args[field_count..-1]
998
+ # Is argument count correct?
999
+ data += [''] * (field_count - data.length) unless data.length == field_count
1000
+ else
1001
+ data = [[]] # one line of data, contains nothing
1002
+ end
1003
+
1004
+ first_line = true
1005
+ data_left = true
1006
+ while data_left
1007
+ idx = 0
1008
+ data_left = false
1009
+
1010
+ parts.each do |part|
1011
+ # Is part an escaped format literal ?
1012
+ if part =~ /\A (?:\\.)+/ox
1013
+ __debug("esc literal: ", part)
1014
+ text << part.gsub(/\\(.)/, "\1")
1015
+ # Is part a once field mark ?
1016
+ elsif part =~ /(#{LFIELDMARK})/ox
1017
+ if first_line
1018
+ type = __construct_type($1, LJUSTIFIED)
1019
+
1020
+ __debug("once field: ", part)
1021
+ __debug("data is: ", data[idx])
1022
+ text << replace(type, part.length, data[idx])
1023
+ __debug("data now: ", data[idx])
1024
+ else
1025
+ text << (@filler[:left] * part.length)[0, part.length]
1026
+ __debug("missing once field: ", part)
1027
+ end
1028
+ idx += 1
1029
+ # Is part a multi field mark ?
1030
+ elsif part =~ /(#{FIELDMARK})/ox and part[0, 2] != '~~'
1031
+ type = __construct_type($1, BJUSTIFIED)
1032
+
1033
+ __debug("multi field: ", part)
1034
+ __debug("data is: ", data[idx])
1035
+ text << replace(type, part.length, data[idx])
1036
+ __debug("text is: ", text)
1037
+ __debug("data now: ", data[idx])
1038
+ data_left = true if data[idx].strip.length > 0
1039
+ idx += 1
1040
+ # Part is a literal.
1041
+ else
1042
+ __debug("literal: ", part)
1043
+ text << part.gsub(/\0(\0*)/, '\1') # XXX: What is this gsub for ?
1044
+
1045
+ # New line ?
1046
+ if part == "\n"
1047
+ line_count += 1
1048
+ if @page_len && line_count >= @page_len
1049
+ __debug("\tejecting page: #@page_num")
1050
+
1051
+ @page_num += 1
1052
+ page_feed = __pagefeed
1053
+ header = __header(@page_num)
1054
+
1055
+ text << footer + page_feed + header
1056
+ previous_footer = footer
1057
+
1058
+ footer = __footer(@page_num, false)
1059
+
1060
+ line_count = hf_count = (header.count("\n") + footer.count("\n"))
1061
+
1062
+ header = page_feed + header
1063
+ end
1064
+ end
1065
+ end # multiway if on part
1066
+ end # parts.each
1067
+
1068
+ __debug("Accumulated: ", text)
1069
+
1070
+ first_line = false
1071
+ end
1072
+ end # while args or formats left
1073
+
1074
+ # Adjust final page header or footer as required
1075
+ if hf_count > 0 and line_count == hf_count
1076
+ # there is a header that we don't need
1077
+ text.sub!(/#{Regexp.escape(header)}\Z/, '')
1078
+ elsif line_count > 0 and @page_len and @page_len > 0
1079
+ # missing footer:
1080
+ text << "\n" * (@page_len - line_count) + footer
1081
+ previous_footer = footer
1082
+ end
1083
+
1084
+ # Replace last footer
1085
+ if previous_footer and not previous_footer.empty?
1086
+ lastFooter = __footer(@page_num, true)
1087
+ footerDiff = lastFooter.count("\n") - previous_footer.count("\n")
1088
+
1089
+ # Enough space to squeeze the longer final footer in ?
1090
+ if footerDiff > 0 && text =~ /(#{'^[^\S\n]*\n' * footerDiff}#{Regexp.escape(previous_footer)})\Z/
1091
+ previous_footer = $1
1092
+ footerDiff = 0
1093
+ end
1094
+
1095
+ # If not, create an empty page for it.
1096
+ if footerDiff > 0
1097
+ @page_num += 1
1098
+ lastHeader = __header(@page_num)
1099
+ lastFooter = __footer(@page_num, true)
1100
+
1101
+ text << lastHeader
1102
+ text << "\n" * (@page_len - lastHeader.count("\n") - lastFooter.count("\n"))
1103
+ text << lastFooter
1104
+ else
1105
+ lastFooter = "\n" * (-footerDiff) + lastFooter
1106
+ text[-(previous_footer.length), text.length] = lastFooter
1107
+ end
1108
+ end
1109
+
1110
+ # Trim text
1111
+ text.gsub!(/[ ]+$/m, '') if @trim
1112
+ text
1113
+ end
1114
+
1115
+ # Replaces a placeholder with the text given. The +format+ string gives
1116
+ # the type of the replace match: When exactly two chars, this indicates
1117
+ # a text replace field, when longer, this is a numeric field.
1118
+ def replace(format, length, value)
1119
+ text = ''
1120
+ remaining = length
1121
+ filled = 0
1122
+
1123
+ __debug("value is: ", value)
1124
+
1125
+ if @fill
1126
+ value.sub!(/\A\s*/m, '')
1127
+ else
1128
+ value.sub!(/\A[ \t]*/, '')
1129
+ end
1130
+
1131
+ if value and format.length > 2
1132
+ # find length of numerical fields
1133
+ if format =~ /([\]>]+)#{Regexp.escape(DECIMAL)}([\[<]+)/
1134
+ ilen, dlen = $1.length, $2.length
1135
+ end
1136
+
1137
+ # Try to extract a numeric value from +value+
1138
+ done = false
1139
+ while not done
1140
+ num, extra = scanf_remains(value, "%f")
1141
+ __debug "Number split into: ", [num, extra]
1142
+ done = true
1143
+
1144
+ if extra.length == value.length
1145
+ value.sub!(/\s*\S*/, '') # skip offending non number value
1146
+ if (@numeric & NUMBERS_SKIP_NAN) > 0 && value =~ /\S/
1147
+ __debug("Not a Number, retrying ", value)
1148
+ done = false
1149
+ else
1150
+ text = '?' * ilen + DECIMAL + '?' * dlen
1151
+ return text
1152
+ end
1153
+ end
1154
+ end
1155
+
1156
+ num = num.first if num.kind_of?(Array)
1157
+ __debug("Finally number is: ", num)
1158
+
1159
+ formatted = "%#{format.length}.#{dlen}f" % num
1160
+
1161
+ if formatted.length > format.length
1162
+ text = '#' * ilen + DECIMAL + '#' * dlen
1163
+ else
1164
+ text = formatted
1165
+ end
1166
+
1167
+ __debug("Formatted number is: ", text)
1168
+
1169
+ # Only output significant digits. Unless not all places were
1170
+ # explicitly requested or the number has more digits than we just
1171
+ # output replace trailing zeros with spaces.
1172
+ unless (@numeric & NUMBERS_ALL_PLACES > 0) or num.to_s =~ /#{Regexp.escape(DECIMAL)}\d\d{#{dlen},}$/
1173
+ text.sub!(/(#{Regexp.escape(DECIMAL)}\d+?)(0+)$/) do |mv|
1174
+ $1 + ' ' * $2.length
1175
+ end
1176
+ end
1177
+
1178
+ value.replace(extra)
1179
+ remaining = 0
1180
+ else
1181
+ while !((value =~ /\S/o).nil?)
1182
+ # Only whitespace remaining ?
1183
+ if ! @fill && value.sub!(/\A[ \t]*\n/, '')
1184
+ filled = 2
1185
+ break
1186
+ end
1187
+ break unless value =~ /\A(\s*)(\S+)(.*)\z/om;
1188
+
1189
+ ws, word, extra = $1, $2, $3
1190
+
1191
+ # Replace all newlines by spaces when fill was specified.
1192
+ nonnl = (ws =~ /[^\n]/o)
1193
+ if @fill
1194
+ ws.gsub!(/\n/) do |match|
1195
+ nonnl ? '' : ' '
1196
+ end
1197
+ end
1198
+
1199
+ # Replace all whitespace by one space if squeeze was specified.
1200
+ lead = @squeeze ? (ws.length > 0 ? ' ' : '') : ws
1201
+ match = lead + word
1202
+
1203
+ __debug("Extracted: ", match)
1204
+ break if text and match =~ /\n/o
1205
+
1206
+ if match.length <= remaining
1207
+ __debug("Accepted: ", match)
1208
+ text << match
1209
+ remaining -= match.length
1210
+ value.replace(extra)
1211
+ else
1212
+ __debug("Need to break: ", match)
1213
+ if (remaining - lead.length) >= @min_break
1214
+ __debug("Trying to break: ", match)
1215
+ broken, left = @break.break(match, remaining, length)
1216
+ text << broken
1217
+ __debug("Broke as: ", [broken, left])
1218
+ value.replace left + extra
1219
+
1220
+ # Adjust remaining chars, but allow for underflow.
1221
+ t = remaining-broken.length
1222
+ if t < 0
1223
+ remaining = 0
1224
+ else
1225
+ remaining = t
1226
+ end
1227
+ end
1228
+ break
1229
+ end
1230
+
1231
+ filled = 1
1232
+ end
1233
+ end
1234
+
1235
+ if filled.zero? and remaining > 0 and value =~ /\S/ and text.empty?
1236
+ value.sub!(/^\s*(.{1,#{remaining}})/, '')
1237
+ text = $1
1238
+ remaining -= text.length
1239
+ end
1240
+
1241
+ # Justify format?
1242
+ if text =~ / /o and format == 'J' and value =~ /\S/o and filled != 2
1243
+ # Fully justified
1244
+ text.reverse!
1245
+ text.gsub!(/( +)/o) do |mv|
1246
+ remaining -= 1
1247
+ if remaining > 0
1248
+ " #{$1}"
1249
+ else
1250
+ $1
1251
+ end
1252
+ end while remaining > 0
1253
+ text.reverse!
1254
+ elsif format =~ /\>|\]/o
1255
+ # Right justified
1256
+ text[0, 0] = (@filler[:left] * remaining)[0, remaining] if remaining > 0
1257
+ elsif format =~ /\^|\|/o
1258
+ # Center justified
1259
+ half_remaining = remaining / 2
1260
+ text[0, 0] = (@filler[:left] * half_remaining)[0, half_remaining]
1261
+ half_remaining = remaining - half_remaining
1262
+ text << (@filler[:right] * half_remaining)[0, half_remaining]
1263
+ else
1264
+ # Left justified
1265
+ text << (@filler[:right] * remaining)[0, remaining]
1266
+ end
1267
+
1268
+ text
1269
+ end
1270
+
1271
+ # Quotes any characters that might be interpreted in +str+ to be normal
1272
+ # characters.
1273
+ def quote(str)
1274
+ puts 'Text::Reform warning: not quoting string...' if @debug
1275
+ str
1276
+ end
1277
+
1278
+ # Turn on internal debugging output for the duration of the
1279
+ # block.
1280
+ def debug
1281
+ d = @debug
1282
+ @debug = true
1283
+ yield
1284
+ @debug = d
1285
+ end
1286
+
1287
+ class << self
1288
+ # Takes a +hyphen+ string as argument, breaks by inserting that hyphen
1289
+ # into the word to be hyphenated.
1290
+ def break_with(hyphen)
1291
+ BreakWith.new(hyphen)
1292
+ end
1293
+
1294
+ # Takes a +bat+ string as argument, breaks by looking for that
1295
+ # substring and breaking just after it.
1296
+ def break_at(bat)
1297
+ BreakAt.new(bat)
1298
+ end
1299
+
1300
+ # Breaks by using a 'wrap and slop' algorithm.
1301
+ def break_wrap
1302
+ BreakWrap.new
1303
+ end
1304
+
1305
+ # Hyphenates with a class that implements the API of TeX::Hyphen or
1306
+ # Text::Hyphen.
1307
+ def break_hyphenator(hyphenator)
1308
+ BreakHyphenator.new(hyphenator)
1309
+ end
1310
+ end
1311
+
1312
+ # Return the header to use. Header can be in many formats, refer
1313
+ # yourself to the documentation.
1314
+ def __header(page_num)
1315
+ __header_or_footer(@header, page_num, false)
1316
+ end
1317
+ private :__header
1318
+
1319
+ # Return the footer to use for +page_num+ page. +last+ is true if this
1320
+ # is the last page.
1321
+ def __footer(page_num, last)
1322
+ __header_or_footer(@footer, page_num, last)
1323
+ end
1324
+ private :__footer
1325
+
1326
+ # Return a header or footer, disambiguating of types and unchomping is
1327
+ # done here.
1328
+ #
1329
+ #
1330
+ # +element+ is the element (header or footer) to process.
1331
+ # +page+ is the current page number. +last+ indicates
1332
+ # whether this is the last page.
1333
+ def __header_or_footer(element, page, last)
1334
+ __debug("element: ", element)
1335
+ if element.respond_to?(:call)
1336
+ if element.arity == 1
1337
+ __header_or_footer(element.call(page), page, last)
1338
+ else
1339
+ __header_or_footer(element.call(page, last), page, last)
1340
+ end
1341
+ elsif element.kind_of?(Hash)
1342
+ page_width = element[:width] || @page_width
1343
+ @internal_formatter||= self.class.new
1344
+
1345
+ if element[:left]
1346
+ format = "<" * page_width
1347
+ data = element[:left]
1348
+ end
1349
+
1350
+ if element[:center] or element[:centre]
1351
+ format = "^" * page_width
1352
+ data = element[:center] || element[:centre]
1353
+ end
1354
+
1355
+ if element[:right]
1356
+ format = ">" * page_width
1357
+ data = element[:right]
1358
+ end
1359
+
1360
+ if format
1361
+ if data.respond_to?(:call)
1362
+ @internal_formatter.format(format, __header_or_footer(data.call(page), page, last))
1363
+ else
1364
+ @internal_formatter.format(format, data.dup)
1365
+ end
1366
+ else
1367
+ ""
1368
+ end
1369
+ else
1370
+ unchomp(element)
1371
+ end
1372
+ end
1373
+ private :__header_or_footer
1374
+
1375
+ # Use the page_feed attribute to get the page feed text. +page_feed+ can
1376
+ # contain a block to call or a String.
1377
+ def __pagefeed
1378
+ if @page_feed.respond_to?(:call)
1379
+ @page_feed.call(@page)
1380
+ else
1381
+ @page_feed
1382
+ end
1383
+ end
1384
+ private :__pagefeed
1385
+
1386
+ # Using Scanf module, scanf a string and return what has not been
1387
+ # matched in addition to normal scanf return.
1388
+ def scanf_remains(value, fstr, &block)
1389
+ if block.nil?
1390
+ unless fstr.kind_of?(Scanf::FormatString)
1391
+ fstr = Scanf::FormatString.new(fstr)
1392
+ end
1393
+ [ fstr.match(value), fstr.string_left ]
1394
+ else
1395
+ value.block_scanf(fstr, &block)
1396
+ end
1397
+ end
1398
+
1399
+ # Count occurrences of \n (lines) of all strings that are passed as
1400
+ # parameter.
1401
+ def count_lines(*args)
1402
+ args.inject(0) do |sum, el|
1403
+ sum + el.count("\n")
1404
+ end
1405
+ end
1406
+
1407
+ # Construct a type that can be passed to #replace from last a string.
1408
+ def __construct_type(str, justifiedPattern)
1409
+ if str =~ /#{justifiedPattern}/x
1410
+ 'J'
1411
+ else
1412
+ str
1413
+ end
1414
+ end
1415
+
1416
+ # Adds a \n character to the end of the line unless it already has a
1417
+ # \n at the end of the line. Returns a modified copy of +str+.
1418
+ def unchomp(str)
1419
+ unchomp!(str.dup)
1420
+ end
1421
+
1422
+ # Adds a \n character to the end of the line unless it already has a
1423
+ # \n at the end of the line.
1424
+ def unchomp!(str)
1425
+ if str.empty? or str[-1] == ?\n
1426
+ str
1427
+ else
1428
+ str << "\n"
1429
+ end
1430
+ end
1431
+
1432
+ # Debug output. Message +msg+ is printed at start of line, then +obj+
1433
+ # is output using +pp+.
1434
+ def __debug(msg, obj = nil)
1435
+ return unless @debug
1436
+ require 'pp'
1437
+ print msg
1438
+ pp obj
1439
+ end
1440
+ private :__debug
1441
+
1442
+ class BreakWith
1443
+ def initialize hyphen
1444
+ @hyphen = hyphen
1445
+ @hylen = hyphen.length
1446
+ end
1447
+
1448
+ # Break by inserting a hyphen string.
1449
+ #
1450
+ # +initial_max_length+:: The maximum size of the first part of the
1451
+ # word that will remain on the first line.
1452
+ # +total_width+:: The total width that can be appended to this
1453
+ # first line.
1454
+ def break(str, initial_max_length, total_width)
1455
+ if total_width <= @hylen
1456
+ ret = [str[0...1], str[1..-1]]
1457
+ else
1458
+ ret = [str[0...(initial_max_length-@hylen)], str[(initial_max_length-@hylen)..-1]]
1459
+ end
1460
+
1461
+ if ret.first =~ /\A\s*\Z/
1462
+ return ['', str]
1463
+ else
1464
+ return [ret.first + @hyphen, ret.last]
1465
+ end
1466
+ end
1467
+ end
1468
+
1469
+ class BreakAt
1470
+ def initialize hyphen
1471
+ @hyphen = hyphen
1472
+ end
1473
+
1474
+ # Break by inserting a hyphen string.
1475
+ #
1476
+ # +initial_max_length+:: The maximum size of the first part of the
1477
+ # word that will remain on the first line.
1478
+ # +total_width+:: The total width that can be appended to this
1479
+ # first line.
1480
+ def break(str, initial_max_length, total_width)
1481
+ max = total_width - @hyphen.length
1482
+ if max <= 0
1483
+ ret = [str[0, 1], str[1, -1]]
1484
+ elsif str =~ /(.{1,#{max}}#@hyphen)(.*)/s
1485
+ ret = [ $1, $2 ]
1486
+ elsif str.length > total_width
1487
+ sep = initial_max_length-@hyphen.length
1488
+ ret = [
1489
+ str[0, sep]+@hyphen,
1490
+ str[sep..-1]
1491
+ ]
1492
+ else
1493
+ ret = [ '', str ]
1494
+ end
1495
+
1496
+ return '', str if ret[0] =~ /\A\s*\Z/
1497
+ return ret
1498
+ end
1499
+ end
1500
+
1501
+ class BreakWrap
1502
+ def initialize
1503
+ end
1504
+
1505
+ # Break by wrapping and slopping to the next line.
1506
+ #
1507
+ # +initial_max_length+:: The maximum size of the first part of the
1508
+ # word that will remain on the first line.
1509
+ # +total_width+:: The total width that can be appended to this
1510
+ # first line.
1511
+ def break(text, initial, total)
1512
+ if initial == total
1513
+ text =~ /\A(\s*\S*)(.*)/
1514
+ return $1, $2
1515
+ else
1516
+ return '', text
1517
+ end
1518
+ end
1519
+ end
1520
+
1521
+ # This word-breaker uses a class that implements the API presented by
1522
+ # TeX::Hyphen and Text::Hyphen modules.
1523
+ class BreakHyphenator
1524
+ def initialize(hyphenator)
1525
+ @hyphenator = hyphenator
1526
+ end
1527
+
1528
+ # Break a word using the provided hyphenation module that responds to
1529
+ # #hyphenate_to.
1530
+ #
1531
+ # +initial_max_length+:: The maximum size of the first part of the
1532
+ # word that will remain on the first line.
1533
+ # +total_width+:: The total width that can be appended to this
1534
+ # first line.
1535
+ def break(str, initial_max_length, total_width)
1536
+ res = @hyphenator.hyphenate_to(str, initial_max_length)
1537
+ res.map! { |ee| ee.nil? ? "" : ee }
1538
+ res
1539
+ end
1540
+ end
1541
+ end