text-reform 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/HACKING +5 -0
- data/History.txt +14 -0
- data/LICENSE +23 -0
- data/Manifest.txt +11 -0
- data/README +49 -36
- data/lib/text/reform.rb +1541 -1541
- data/{tests/tc_text_reform.rb → test/test_text_reform.rb} +448 -451
- metadata +58 -53
- data/Changelog +0 -5
- data/Install +0 -4
- data/Rakefile +0 -116
- data/TODO +0 -5
- data/tests/testall.rb +0 -20
checksums.yaml
ADDED
@@ -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
data/HACKING
ADDED
data/History.txt
ADDED
@@ -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.
|
data/Manifest.txt
ADDED
data/README
CHANGED
@@ -1,36 +1,49 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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.
|
data/lib/text/reform.rb
CHANGED
@@ -1,1541 +1,1541 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
#
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
attr_accessor :
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
@
|
927
|
-
@
|
928
|
-
@
|
929
|
-
@
|
930
|
-
@
|
931
|
-
@
|
932
|
-
@
|
933
|
-
@
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
@
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
__debug("
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
text
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
text =
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
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 |
|
1174
|
-
|
1175
|
-
end
|
1176
|
-
end
|
1177
|
-
|
1178
|
-
value.replace(extra)
|
1179
|
-
remaining = 0
|
1180
|
-
else
|
1181
|
-
while
|
1182
|
-
|
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
|
-
|
1192
|
-
nonnl = (ws =~ /[^\n]/o)
|
1193
|
-
if @fill
|
1194
|
-
ws.gsub!(/\n/) do |match|
|
1195
|
-
nonnl ? '' : ' '
|
1196
|
-
end
|
1197
|
-
end
|
1198
|
-
|
1199
|
-
|
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
|
-
|
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
|
-
|
1238
|
-
remaining -= text.length
|
1239
|
-
end
|
1240
|
-
|
1241
|
-
|
1242
|
-
if text =~ / /o and format == 'J' and value =~ /\S/o and filled != 2
|
1243
|
-
|
1244
|
-
text.reverse!
|
1245
|
-
text.gsub!(/( +)/o) do |
|
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
|
-
|
1256
|
-
text[0, 0] = (@filler[:left] * remaining)[0, remaining] if remaining > 0
|
1257
|
-
elsif format =~ /\^|\|/o
|
1258
|
-
|
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
|
-
|
1265
|
-
text << (@filler[:right] * remaining)[0, remaining]
|
1266
|
-
end
|
1267
|
-
|
1268
|
-
text
|
1269
|
-
end
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
def quote(str)
|
1274
|
-
puts 'Text::Reform warning: not quoting string...' if @debug
|
1275
|
-
str
|
1276
|
-
end
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
def debug
|
1281
|
-
d = @debug
|
1282
|
-
@debug = true
|
1283
|
-
yield
|
1284
|
-
@debug = d
|
1285
|
-
end
|
1286
|
-
|
1287
|
-
class << self
|
1288
|
-
|
1289
|
-
|
1290
|
-
def break_with(hyphen)
|
1291
|
-
BreakWith.new(hyphen)
|
1292
|
-
end
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
def break_at(bat)
|
1297
|
-
BreakAt.new(bat)
|
1298
|
-
end
|
1299
|
-
|
1300
|
-
|
1301
|
-
def break_wrap
|
1302
|
-
BreakWrap.new
|
1303
|
-
end
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
def break_hyphenator(hyphenator)
|
1308
|
-
BreakHyphenator.new(hyphenator)
|
1309
|
-
end
|
1310
|
-
end
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
def __header(page_num)
|
1315
|
-
__header_or_footer(@header, page_num, false)
|
1316
|
-
end
|
1317
|
-
private :__header
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
def __footer(page_num, last)
|
1322
|
-
__header_or_footer(@footer, page_num, last)
|
1323
|
-
end
|
1324
|
-
private :__footer
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
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
|
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
|
-
|
1376
|
-
|
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
|
-
|
1387
|
-
|
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
|
-
|
1400
|
-
|
1401
|
-
def count_lines(*args)
|
1402
|
-
args.inject(0) do |sum, el|
|
1403
|
-
sum + el.count("\n")
|
1404
|
-
end
|
1405
|
-
end
|
1406
|
-
|
1407
|
-
|
1408
|
-
def __construct_type(str, justifiedPattern)
|
1409
|
-
if str =~ /#{justifiedPattern}/x
|
1410
|
-
'J'
|
1411
|
-
else
|
1412
|
-
str
|
1413
|
-
end
|
1414
|
-
end
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
def unchomp(str)
|
1419
|
-
unchomp!(str.dup)
|
1420
|
-
end
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
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
|
-
|
1433
|
-
|
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
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
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
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
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
|
-
|
1498
|
-
end
|
1499
|
-
end
|
1500
|
-
|
1501
|
-
class BreakWrap
|
1502
|
-
def initialize
|
1503
|
-
end
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
def break(text, initial, total)
|
1512
|
-
if initial == total
|
1513
|
-
text =~ /\A(\s*\S*)(.*)/
|
1514
|
-
|
1515
|
-
else
|
1516
|
-
return '', text
|
1517
|
-
end
|
1518
|
-
end
|
1519
|
-
end
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
class BreakHyphenator
|
1524
|
-
def initialize(hyphenator)
|
1525
|
-
@hyphenator = hyphenator
|
1526
|
-
end
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
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
|