File: Format.pm

package info (click to toggle)
libstring-format-perl 1.18-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, buster, sid
  • size: 132 kB
  • sloc: perl: 173; makefile: 2
file content (237 lines) | stat: -rw-r--r-- 7,565 bytes parent folder | download
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
package String::Format;

# ----------------------------------------------------------------------
#  Copyright (C) 2002,2009 darren chamberlain <darren@cpan.org>
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation; version 2.
#
#  This program is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
#  02110-1301 USA.
# -------------------------------------------------------------------

use strict;
use vars qw($VERSION @EXPORT);
use Exporter;
use base qw(Exporter);

$VERSION = '1.18';
@EXPORT = qw(stringf);

sub _replace {
    my ($args, $orig, $alignment, $min_width,
        $max_width, $passme, $formchar) = @_;

    # For unknown escapes, return the orignial
    return $orig unless defined $args->{$formchar};

    $alignment = '+' unless defined $alignment;

    my $replacement = $args->{$formchar};
    if (ref $replacement eq 'CODE') {
        # $passme gets passed to subrefs.
        $passme ||= "";
        $passme =~ tr/{}//d;
        $replacement = $replacement->($passme);
    }

    my $replength = length $replacement;
    $min_width  ||= $replength;
    $max_width  ||= $replength;

    # length of replacement is between min and max
    if (($replength > $min_width) && ($replength < $max_width)) {
        return $replacement;
    }

    # length of replacement is longer than max; truncate
    if ($replength > $max_width) {
        return substr($replacement, 0, $max_width);
    }
    
    # length of replacement is less than min: pad
    if ($alignment eq '-') {
        # left align; pad in front
        return $replacement . " " x ($min_width - $replength);
    }

    # right align, pad at end
    return " " x ($min_width - $replength) . $replacement;
}

my $regex = qr/
               (%             # leading '%'
                (-)?          # left-align, rather than right
                (\d*)?        # (optional) minimum field width
                (?:\.(\d*))?  # (optional) maximum field width
                (\{.*?\})?    # (optional) stuff inside
                (\S)          # actual format character
             )/x;
sub stringf {
    my $format = shift || return;
    my $args = UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
       $args->{'n'} = "\n" unless exists $args->{'n'};
       $args->{'t'} = "\t" unless exists $args->{'t'};
       $args->{'%'} = "%"  unless exists $args->{'%'};

    $format =~ s/$regex/_replace($args, $1, $2, $3, $4, $5, $6)/ge;

    return $format;
}

sub stringfactory {
    shift;  # It's a class method, but we don't actually want the class
    my $args = UNIVERSAL::isa($_[0], "HASH") ? shift : { @_ };
    return sub { stringf($_[0], $args) };
}

1;
__END__

=head1 NAME

String::Format - sprintf-like string formatting capabilities with
arbitrary format definitions

=head1 ABSTRACT

String::Format allows for sprintf-style formatting capabilities with
arbitrary format definitions

=head1 SYNOPSIS

  use String::Format;

  my %fruit = (
        'a' => "apples",
        'b' => "bannanas",
        'g' => "grapefruits",
        'm' => "melons",
        'w' => "watermelons",
  );

  my $format = "I like %a, %b, and %g, but not %m or %w.";

  print stringf($format, %fruit);
  
  # prints:
  # I like apples, bannanas, and grapefruits, but not melons or watermelons.

=head1 DESCRIPTION

String::Format lets you define arbitrary printf-like format sequences
to be expanded.  This module would be most useful in configuration
files and reporting tools, where the results of a query need to be
formatted in a particular way.  It was inspired by mutt's index_format
and related directives (see <URL:http://www.mutt.org/doc/manual/manual-6.html#index_format>).

=head1 FUNCTIONS

=head2 stringf

String::Format exports a single function called stringf.  stringf
takes two arguments:  a format string (see FORMAT STRINGS, below) and
a reference to a hash of name => value pairs.  These name => value
pairs are what will be expanded in the format string.

=head1 FORMAT STRINGS

Format strings must match the following regular expression:

  qr/
     (%             # leading '%'
      (-)?          # left-align, rather than right
      (\d*)?        # (optional) minimum field width
      (?:\.(\d*))?  # (optional) maximum field width
      ({.*?})?      # (optional) stuff inside
      (\S)          # actual format character
     )/x;

If the escape character specified does not exist in %args, then the
original string is used.  The alignment, minimum width, and maximum
width options function identically to how they are defined in
sprintf(3) (any variation is a bug, and should be reported).

Note that Perl's sprintf definition is a little more liberal than the
above regex; the deviations were intentional, and all deal with
numeric formatting (the #, 0, and + leaders were specifically left
out).

The value attached to the key can be a scalar value or a subroutine
reference; if it is a subroutine reference, then anything between the
'{' and '}' ($5 in the above regex) will be passed as $_[0] to the
subroutine reference.  This allows for entries such as this:

  %args = (
      d => sub { POSIX::strftime($_[0], localtime) }, 
  );

Which can be invoked with this format string:

  "It is %{%M:%S}d right now, on %{%A, %B %e}d."

And result in (for example):

  It is 17:45 right now, on Monday, February 4.

Note that since the string is passed unmolested to the subroutine
reference, and strftime would Do The Right Thing with this data, the
above format string could be written as:

  "It is %{%M:%S right now, on %A, %B %e}d."

By default, the formats 'n', 't', and '%' are defined to be a newline,
tab, and '%', respectively, if they are not already defined in the
hashref of arguments that gets passed it.  So we can add carriage
returns simply:

  "It is %{%M:%S right now, on %A, %B %e}d.%n"

Because of how the string is parsed, the normal "\n" and "\t" are
turned into two characters each, and are not treated as a newline and
tab.  This is a bug.

=head1 FACTORY METHOD

String::Format also supports a class method, named B<stringfactory>,
which will return reference to a "primed" subroutine.  stringfatory
should be passed a reference to a hash of value; the returned
subroutine will use these values as the %args hash.

  my $self = Some::Groovy::Package->new($$, $<, $^T);
  my %formats = (
        'i' => sub { $self->id      },
        'd' => sub { $self->date    },
        's' => sub { $self->subject },
        'b' => sub { $self->body    },
  );
  my $index_format = String::Format->stringfactory(\%formats);

  print $index_format->($format1);
  print $index_format->($format2);

This subroutine reference can be assigned to a local symbol table
entry, and called normally, of course:

  *reformat = String::Format->stringfactory(\%formats);

  my $reformed = reformat($format_string);

=head1 LICENSE

C<String::Format> is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; version 2.


=head1 AUTHOR

darren chamberlain <darren@cpan.org>