Subversion Repositories OpenARM Single-board Computer

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
324 agaran 1
# Text::Aligner - Align text in columns
2
package Text::Aligner;
3
use strict;
4
 
5
use warnings;
6
 
7
BEGIN    {
8
    use Exporter ();
9
    use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
10
    $VERSION     = 0.03;
11
    @ISA         = qw (Exporter);
12
    #Give a hoot don't pollute, do not export more than needed by default
13
    @EXPORT      = qw ();
14
    @EXPORT_OK   = qw ( align);
15
    %EXPORT_TAGS = ();
16
}
17
 
18
# this is a non-method, and currently the only user interface
19
sub align ($@) {
20
    my $ali = Text::Aligner->new( shift);
21
    $ali->alloc( map ref eq 'SCALAR' ? $$_ : $_, @_);
22
    if ( defined wantarray ) {
23
        my @just = map $ali->justify( ref eq 'SCALAR' ? $$_ : $_), @_;
24
        return @just if wantarray;
25
        return join "\n", @just, '';
26
    } else {
27
        for ( @_ ) {
28
            $_ = $ali->justify( $_) for ref eq 'SCALAR' ? $$_ : $_; # one-shot
29
        }
30
    }
31
}
32
 
33
### class Text::Aligner
34
 
35
sub _new { # internal creator
36
    my $class = shift;
37
    my ( $width, $pos) = @_; # both method-or-coderef (this is very general)
38
    bless {
39
        width => $width,
40
        pos => $pos,
41
        left => Text::Aligner::MaxKeeper->new,
42
        right => Text::Aligner::MaxKeeper->new,
43
    }, $class;
44
}
45
 
46
# create an aligner
47
sub new {
48
    my ( $class, $spec) = @_;
49
    $spec ||= 0; # left alignment is default
50
    my $al;
51
    if ( !ref( $spec) and $spec =~ s/^auto/num/ ) {
52
        $al = Text::Aligner::Auto->_new( $spec);
53
    } else {
54
        $al = $class->_new( _compile_alispec( $spec));
55
    }
56
    $al;
57
}
58
 
59
# return left and right field widths for an object
60
sub _measure0 {
61
    my $al = shift;
62
    my $obj = shift;
63
    $obj = '' unless defined $obj;
64
    my ( $w, $p);
65
    if ( ref $obj ) {
66
        ( $w, $p) = ( $obj->$al->{ width}->(), $obj->$al->{ pos}->() );
67
    } else {
68
        ( $w, $p) = ( $al->{ width}->( $obj), $al->{ pos}->( $obj) );
69
    }
70
    $_ ||= 0 for $w, $p;
71
    ( $p, $w - $p);
72
}
73
 
74
# return left and right field widths for an object
75
sub _measure {
76
    my $al = shift;
77
    my $obj = shift;
78
    $obj = '' unless defined $obj;
79
    my ( $wmeth, $pmeth) = @{ $al}{ qw( width pos)};
80
    my $w = ref $wmeth ? $wmeth->( $obj) : $obj->$wmeth;
81
    my $p = ref $pmeth ? $pmeth->( $obj) : $obj->$pmeth;
82
    $_ ||= 0 for $w, $p;
83
    ( $p, $w - $p);
84
}
85
 
86
# return left and rigth maxima, or nothing if the aligner is empty
87
sub _status {
88
    my @lr = ( $_[ 0]->{ left}->max, $_[ 0]->{ right}->max);
89
    # $l and $r should be both defined or undefined, unless the
90
    # MaxKeeper memory is corrupted by forgetting unremembered things.
91
    return unless defined( $lr[ 0]) and defined( $lr[ 1]);
92
    @lr;
93
}
94
 
95
# remember alignment requirements
96
sub alloc {
97
    my $al = shift;
98
    for ( @_ ) {
99
#       $_ ||= ''; print "allocing '$_'\n";
100
        my ( $l, $r) = $al->_measure( $_);
101
        $al->{ left}->remember( $l); # space needed left of pos
102
        $al->{ right}->remember( $r); # ...and right of pos
103
    }
104
    $al;
105
}
106
 
107
# release alignment requirement.  it disturbs an aligner deeply to forget
108
# things it hasn't remembered.  the effects may be delayed.
109
sub _forget {
110
    my $al = shift;
111
    for ( map defined() ? $_ : '', @_ ) {
112
#       print "forgetting '$_'\n";
113
        my ( $l, $r) = $al->_measure( $_);
114
        $al->{ left}->forget( $l);
115
        $al->{ right}->forget( $r);
116
    }
117
    $al;
118
}
119
 
120
# justify a string.  a string is aligned within the aligner's field, and
121
# filled with blanks or cut to size, as appropriate.  a string that has
122
# been allocated will never be trimmed (that is the point of allocation).
123
# if the aligner is empty it returns the string unaltered.
124
sub justify {
125
    my $al = shift;
126
    my $str  = shift;
127
#   print "justifying '$str'\n";
128
    $str .= ''; # stringify (objects, numbers, undef)
129
    my ( $l_pad, $r_pad) = $al->_padding( $str);
130
    substr( $str, 0, -$l_pad) = '' if $l_pad < 0; # trim if negative
131
    substr( $str, $r_pad) = '' if $r_pad < 0; # ... both ends
132
    join $str, ' ' x $l_pad, ' ' x $r_pad; # pad if positive
133
}
134
 
135
# return two numbers that indicate how many blanks are needed on each side
136
# of a string to justify it.  Negative values mean trim that many characters.
137
# an empty aligner returns ( 0, 0), so doesn't change anything.
138
sub _padding {
139
    my $al = shift;
140
    my $str = shift;
141
    my ( $this_l, $this_r) = $al->_measure( $str);
142
    my ( $l_pad, $r_pad) = ( 0, 0);
143
    if ( $al->_status ) {
144
        ( $l_pad, $r_pad) = $al->_status;
145
        $l_pad -= $this_l;
146
        $r_pad -= $this_r;
147
    }
148
    ( $l_pad, $r_pad);
149
}
150
 
151
# _compile_alispec() returns positioners according to specification.  In
152
# effect, it is is the interpreter for alignment specifications.
153
 
154
sub _compile_alispec { # it's a dirty job...
155
    my $width = sub { length shift }; # this is always so for string aligners
156
    my $pos; # the positioner we actually compile
157
    local $_ = shift || ''; # alignment specification
158
    if ( ref() eq 'Regexp' ) {
159
        my $regex = $_; # lexical copy!
160
        $pos = sub {
161
            local $_ = shift;
162
            m/$regex/ ? $-[ 0] : length; # assume match after string
163
        };
164
    } else {
165
        s/^left/0/;
166
        s/^center/0.5/;
167
        s/^right/1/;
168
        if ( _is_number( $_) ) {
169
            my $proportion = $_; # use lexical copy
170
            $pos = sub { int( $proportion*length shift) };
171
        } elsif ( $_ =~ /^(?:num|point)(?:\((.*))?/ ) {
172
            my $point = defined $1 ? $1 : '';
173
            $point =~ s/\)$//; # ignore trailing paren, if present
174
            length $point or $point = '.';
175
            $pos = sub { index( shift() . $point, $point) }
176
        } else {
177
            $pos = sub { 0 };
178
        }
179
    }
180
    ( $width, $pos);
181
}
182
 
183
# decide if a string is a number. (see perlfaq4).  This needs to become
184
# more flexible for auto-alignment
185
sub _is_number { defined( $_[ 0]) and $_[ 0] =~ /^-?\d+\.?\d*$/ }
186
 
187
package Text::Aligner::Auto;
188
# Combined numeric and left alignment.  Numbers are aligned numerically,
189
# other strings are left-aligned.  The resulting columns are interleaved
190
# flush left and filled on the right if necessary.
191
 
192
sub _new { # only called by Text::Aligner->new()
193
    my $class = shift;
194
    my $numspec = shift; # currently ignored
195
    bless {
196
        num => Text::Aligner->new( 'num'),    # align numbers among themselves
197
        other => Text::Aligner->new,          # left-align anything else
198
    }, $class;
199
}
200
 
201
sub alloc {
202
    my $aa = shift;
203
    my @num = grep _is_number( $_), @_;
204
    my @other = grep !_is_number( $_), @_;
205
    $aa->{ num}->alloc( @num);
206
    $aa->{ other}->alloc( @other);
207
    $aa;
208
}
209
 
210
sub _forget {
211
    my $aa = shift;
212
    $aa->{ num}->_forget( grep _is_number( $_), @_);
213
    $aa->{ other}->_forget( grep !_is_number( $_), @_);
214
    $aa;
215
}
216
 
217
# justify as required
218
sub justify {
219
    my ( $aa, $str) = @_;
220
    # align according to type
221
    $str = $aa->{ _is_number( $str) ? 'num' : 'other'}->justify( $str);
222
    my $combi = Text::Aligner->new; # left-justify pre-aligned string
223
    # initialise to size of partial aligners.  (don't initialise from
224
    # empty aligner)
225
    $combi->alloc( $aa->{ num}->justify( '')) if $aa->{ num}->_status;
226
    $combi->alloc( $aa->{ other}->justify( '')) if $aa->{ other}->_status;
227
    $combi->justify( $str);
228
}
229
 
230
# for convenience
231
BEGIN { # import _is_number()
232
    *_is_number = \ &Text::Aligner::_is_number;
233
}
234
 
235
package Text::Aligner::MaxKeeper;
236
# Keep the maximum of a dynamic set of numbers.  Optimized for the case of
237
# a relatively small range of numbers that may occur repeatedly.
238
 
239
sub new {
240
    bless {
241
        max => undef,
242
        seen => {},
243
    }, shift;
244
}
245
 
246
sub max { $_[ 0]->{ max} }
247
 
248
sub remember {
249
    my ( $mk, $val) = @_;
250
    _to_max( $mk->{ max}, $val);
251
    $mk->{ seen}->{ $val}++;
252
    $mk;
253
}
254
 
255
sub forget {
256
    my ( $mk, $val) = @_;
257
    if ( exists $mk->{ seen}->{ $val} ) {
258
        my $seen = $mk->{ seen};
259
        unless ( --$seen->{ $val} ) {
260
            delete $seen->{ $val};
261
            if ( $mk->{ max} == $val ) {
262
                # lost the maximum, recalculate
263
                undef $mk->{ max};
264
                _to_max( $mk->{ max}, keys %$seen);
265
            }
266
        }
267
    }
268
    $mk;
269
}
270
 
271
sub _to_max {
272
    my $var = \ shift;
273
    defined $_ and ( not defined $$var or $$var < $_) and $$var = $_ for @_;
274
    $$var;
275
}
276
 
277
########################################### main pod documentation begin ##
278
 
279
=head1 NAME
280
 
281
Text::Aligner
282
 
283
=head1 SYNOPSIS
284
 
285
  use Text::Aligner qw( align);
286
 
287
  # Print the words "just a test!" right-justified each on a line:
288
 
289
  my @lines = align( 'right', qw( just a test!);
290
  print "$_\n" for @lines;
291
 
292
=head1 DESCRIPTION
293
 
294
Text::Aligner exports a single function, align(), which is
295
used to justify strings to various alignment styles.  The
296
alignment specification is the first argument, followed by
297
any number of scalars which are subject to alignment.
298
 
299
The operation depends on context.  In list context, a list of
300
the justified scalars is returned.  In scalar context, the
301
justified arguments are joined into a single string with newlines
302
appended.  The original arguments remain unchanged.  In void
303
context, in-place justification is attempted.  In this case, all
304
arguments must be lvalues.
305
 
306
Align() also does one level of scalar dereferencing.  That is,
307
whenever one of the arguments is a scalar reference, the scalar
308
pointed to is aligned instead.  Other references are simply stringified.
309
An undefined argument is interpreted as an empty string without
310
complaint.
311
 
312
=head1 ALIGNMENT
313
 
314
The first argument of the align() function is an alignment style, a
315
single scalar.
316
 
317
It can be one of the strings "left", "right", "center", "num", "point",
318
or "auto", or a regular expression (qr/.../), or a coderef.
319
 
320
A default style of "left" is assumed for every other value, including
321
"" and undef.
322
 
323
"left", "right" and "center" have the obvious meanings.  These can
324
also be given as numbers 0, 1, and 0.5 respectively. (Other numbers
325
are also possible, but probably not very useful).
326
 
327
"num", and its synonym "point", specify that the decimal points be
328
aligned (assumed on the right, unless present).  Arbitrary (non-numeric)
329
strings are also aligned in this manner, so they end up one column left
330
of the (possibly assumed) decimal point, flush right with any integers.
331
For the occasional string like "inf", or "-" for missing values, this
332
may be the right place.  A string-only column ends up right-aligned
333
(unless there are points present).
334
 
335
The "auto" style seperates numeric strings (that are composed of
336
"-", ".", and digits in the usual manner) and aligns them numerically.
337
Other strings are left aligned with the number that sticks out
338
farthest to the left.  This gives left alignment for string-only
339
columns and numeric alignment for columns of numbers.  In mixed
340
columns, strings are reasonably placed to serve as column headings
341
or intermediate titles.
342
 
343
With "num" (and "point") it is possible to specify another character
344
for the decimal point in the form "num(,)".  In fact, you can specify
345
any string after a leading "(", and the closing ")" is optional.
346
"point(=>)" could be used to align certain pieces of Perl code.  This
347
option is currently not available with "auto" alignment (because
348
recognition of numbers is Anglo-centric).
349
 
350
If a regular expression is specified, the points are aligned where
351
the first match of the regex starts.  A match is assumed immediately
352
after the string if it doesn't match.
353
 
354
A regular expression is a powerful way of alignment specification.  It
355
can replace most others easily, except center alignment and, of course,
356
the double action of "auto".
357
 
358
=head1 POSITIONERS
359
 
360
For entirely self-defined forms of alignment, a coderef, also known
361
as a positioner, can be given instead of an alignment style.  This
362
code will be called once or more times with the string to be aligned
363
as its argument.  It must return two numbers, a width and a position,
364
that describe how to align a string with other strings.
365
 
366
The width should normally be the length of the string.  The position
367
defines a point relative to the beginning of the string, which is
368
aligned with the positions given for other strings.
369
 
370
A zero position for all strings results in left alignment, positioning
371
to the end of the string results in right alignment, and returning
372
half the length gives center alignment.  "num" alignment is realized
373
by marking the position of the decimal point.
374
 
375
Note that the position you return is a relative measure.  Adding a
376
constant value to all positions results in no change in alignment.
377
It doesn't have to point inside the string (as in right alignment,
378
where it points one character past the end of the string).
379
 
380
The first return value of a positioner should almost always be the
381
length of the given string.  It may be useful to ly about the string
382
length if the string contains escape sequences that occupy no place
383
on screen.
384
 
385
=head1 USAGE
386
 
387
  use Text::Aligner qw( align);
388
 
389
  align( $style, $str, ...);
390
 
391
  $style must be given and must be an alignment specification.
392
  Any number of scalars can follow.  An argument that contains a
393
  scalar reference is dereferenced before it is used.  In scalar
394
  and list context, the aligned strings are returned.  In void
395
  context, the values are aligned in place and must be lvalues.
396
 
397
=head1 BUGS
398
 
399
  None known as of realease, but...
400
 
401
=head1 AUTHOR
402
 
403
    Anno Siegel
404
    CPAN ID: ANNO
405
    siegel@zrz.tu-berlin.de
406
    http://www.tu-berlin.de/~siegel
407
 
408
=head1 COPYRIGHT
409
 
410
Copyright (c) 2002 Anno Siegel. All rights reserved.
411
This program is free software; you can redistribute
412
it and/or modify it under the same terms as Perl itself.
413
 
414
The full text of the license can be found in the
415
LICENSE file included with this module.
416
 
417
=head1 SEE ALSO
418
 
419
perl(1)
420
 
421
Text::Table
422
 
423
=cut
424
 
425
1; #this line is important and will help the module return a true value
426
__END__