File Coverage

blib/lib/String/Truncate.pm
Criterion Covered Total %
statement 74 74 100.0
branch 28 28 100.0
condition 5 7 71.4
subroutine 19 19 100.0
pod 2 2 100.0
total 128 130 98.4


line stmt bran cond sub pod time code
1 3     3   166599 use strict;
  3         32  
  3         70  
2 3     3   14 use warnings;
  3         4  
  3         131  
3             package String::Truncate 1.100603;
4             # ABSTRACT: a module for when strings are too long to be displayed in...
5              
6 3     3   14 use Carp qw(croak);
  3         5  
  3         169  
7 3     3   1134 use Sub::Install 0.03 qw(install_sub);
  3         4282  
  3         13  
8              
9             #pod =head1 SYNOPSIS
10             #pod
11             #pod This module handles the simple but common problem of long strings and finite
12             #pod terminal width. It can convert:
13             #pod
14             #pod "this is your brain" -> "this is your ..."
15             #pod or "...is your brain"
16             #pod or "this is... brain"
17             #pod or "... is your b..."
18             #pod
19             #pod It's simple:
20             #pod
21             #pod use String::Truncate qw(elide);
22             #pod
23             #pod my $brain = "this is your brain";
24             #pod
25             #pod elide($brain, 16); # first option
26             #pod elide($brain, 16, { truncate => 'left' }); # second option
27             #pod elide($brain, 16, { truncate => 'middle' }); # third option
28             #pod elide($brain, 16, { truncate => 'ends' }); # fourth option
29             #pod
30             #pod String::Trunc::trunc($brain, 16); # => "this is your bra"
31             #pod
32             #pod =func elide
33             #pod
34             #pod elide($string, $length, \%arg)
35             #pod
36             #pod This function returns the string, if it is less than or equal to C<$length>
37             #pod characters long. If it is longer, it truncates the string and marks the
38             #pod elision.
39             #pod
40             #pod Valid arguments are:
41             #pod
42             #pod truncate - elide at left, right, middle, or ends? (default: right)
43             #pod marker - how to mark the elision (default: ...)
44             #pod at_space - if true, strings will be broken at whitespace if possible
45             #pod
46             #pod =cut
47              
48             my %elider_for = (
49             right => \&_elide_right,
50             left => \&_elide_left,
51             middle => \&_elide_middle,
52             ends => \&_elide_ends,
53             );
54              
55             sub _elide_right {
56 38     38   72 &_assert_1ML; ## no critic Ampersand
57 37         57 my ($string, $length, $marker, $at_space) = @_;
58 37         48 my $keep = $length - length($marker);
59              
60 37 100       55 if ($at_space) {
61            
62 11         291 my ($substr) = $string =~ /\A(.{0,$keep})\s/s;
63 11 100 66     47 $substr = substr($string, 0, $keep)
64             unless defined $substr and length $substr;
65              
66 11         76 return $substr . $marker;
67             } else {
68 26         171 return substr($string, 0, $keep) . $marker;
69             }
70             }
71              
72             sub _elide_left {
73 18     18   33 &_assert_1ML; ## no critic Ampersand
74 18         31 my ($string, $length, $marker, $at_space) = @_;
75 18         24 my $keep = $length - length($marker);
76 18         44 return $marker
77             . reverse(_elide_right(scalar reverse($string), $keep, q{}, $at_space));
78             }
79              
80             sub _elide_middle {
81 5     5   10 &_assert_1ML; ## no critic Ampersand
82 5         8 my ($string, $length, $marker, $at_space) = @_;
83 5         8 my $keep = $length - length($marker);
84 5         15 my ($keep_left, $keep_right) = (int($keep / 2)) x 2;
85 5 100       10 $keep_left +=1 if ($keep_left + $keep_right) < $keep;
86 5         10 return _elide_right($string, $keep_left, q{}, $at_space)
87             . $marker
88             . _elide_left($string, $keep_right, q{}, $at_space)
89             }
90              
91             sub _elide_ends {
92 3     3   8 &_assert_2ML; ## no critic Ampersand
93 2         4 my ($string, $length, $marker, $at_space) = @_;
94 2         6 my $midpoint = int(length($string) / 2);
95 2         3 my $each = int($length / 2);
96              
97 2         6 return _elide_left(substr($string, 0, $midpoint), $each, $marker, $at_space)
98             . _elide_right(substr($string, -$midpoint), $each, $marker, $at_space)
99             }
100              
101             sub _assert_1ML {
102 61     61   89 my ($string, $length, $marker) = @_;
103 61 100       264 croak "elision marker <$marker> is longer than allowed length $length!"
104             if length($marker) > $length;
105             }
106              
107             sub _assert_2ML {
108 3     3   5 my ($string, $length, $marker) = @_;
109             # this should only complain if needed: elide('foobar', 3, {marker=>'...'})
110             # should be ok -- rjbs, 2006-02-24
111 3 100       191 croak "two elision markers <$marker> are longer than allowed length $length!"
112             if (length($marker) * 2) > $length;
113             }
114              
115             sub elide {
116 43     43 1 161 my ($string, $length, $arg) = @_;
117 43 100       82 $arg = {} unless $arg;
118 43   100     109 my $truncate = $arg->{truncate} || 'right';
119              
120             croak "invalid value for truncate argument: $truncate"
121 43 100       231 unless my $elider = $elider_for{ $truncate };
122              
123             # hey, this might be really easy:
124 42 100       116 return $string if length($string) <= $length;
125              
126 32 100       56 my $marker = defined $arg->{marker} ? $arg->{marker} : '...';
127 32 100       62 my $at_space = defined $arg->{at_space} ? $arg->{at_space} : 0;
128            
129 32         74 return $elider->($string, $length, $marker, $at_space);
130             }
131            
132             #pod =func trunc
133             #pod
134             #pod trunc($string, $length, \%arg)
135             #pod
136             #pod This acts just like C<elide>, but assumes an empty marker, so it actually
137             #pod truncates the string normally.
138             #pod
139             #pod =cut
140              
141             sub trunc {
142 14     14 1 448 my ($string, $length, $arg) = @_;
143 14 100       27 $arg = {} unless $arg;
144              
145 14 100       109 croak "marker may not be passed to trunc()" if exists $arg->{marker};
146 13         21 $arg->{marker} = q{};
147              
148 13         25 return elide($string, $length, $arg);
149             }
150              
151             #pod =head1 IMPORTING
152             #pod
153             #pod String::Truncate exports both C<elide> and C<trunc>, and also supports the
154             #pod Exporter-style ":all" tag.
155             #pod
156             #pod use String::Truncate (); # export nothing
157             #pod use String::Truncate qw(elide); # export just elide()
158             #pod use String::Truncate qw(:all); # export both elide() and trunc()
159             #pod use String::Truncate qw(-all); # export both elide() and trunc()
160             #pod
161             #pod When exporting, you may also supply default values:
162             #pod
163             #pod use String::Truncate -all => defaults => { length => 10, marker => '--' };
164             #pod
165             #pod # or
166             #pod
167             #pod use String::Truncate -all => { length => 10, marker => '--' };
168             #pod
169             #pod These values affect only the imported version of the functions. You may pass
170             #pod arguments as usual to override them, and you may call the subroutine by its
171             #pod fully-qualified name to get the standard behavior.
172             #pod
173             #pod =cut
174              
175 3     3   3300 use Sub::Exporter::Util ();
  3         35762  
  3         222  
176             use Sub::Exporter 0.953 -setup => {
177             exports => {
178             Sub::Exporter::Util::merge_col(defaults => {
179 8         1710 trunc => sub { trunc_with_defaults($_[2]) },
180 8         2971 elide => sub { elide_with_defaults($_[2]) },
181             })
182 3         24 },
183             collectors => [ qw(defaults) ]
184 3     3   19 };
  3         42  
185              
186             #pod =head1 BUILDING CODEREFS
187             #pod
188             #pod The imported builds and installs lexical closures (code references) that merge
189             #pod in given values to the defaults. You can build your own closures without
190             #pod importing them into your namespace. To do this, use the C<elide_with_defaults>
191             #pod and C<trunc_with_defaults> routines.
192             #pod
193             #pod =head2 elide_with_defaults
194             #pod
195             #pod my $elider = String::Truncate::elide_with_defaults(\%arg);
196             #pod
197             #pod This routine, never exported, builds a coderef which behaves like C<elide>, but
198             #pod uses default values when needed. All the valid arguments to C<elide> are valid
199             #pod here, as well as C<length>.
200             #pod
201             #pod =cut
202              
203             sub _code_with_defaults {
204 6     6   15 my ($code, $skip_defaults) = @_;
205            
206             sub {
207 16   50 16   68 my $defaults = shift || {};
208 16         36 my %defaults = %$defaults;
209 16         32 delete $defaults{$_} for @$skip_defaults;
210              
211 16         21 my $length = delete $defaults{length};
212              
213             sub {
214 40     40   4177 my $string = $_[0];
215 40 100       87 my $length = defined $_[1] ? $_[1] : $length;
216 40 100       89 my $arg = { %defaults, (defined $_[2] ? %{ $_[2] } : ()) };
  22         65  
217              
218 40         98 return $code->($string, $length, $arg);
219             }
220 16         81 }
221 6         35 }
222              
223             BEGIN {
224 3     3   1629 install_sub({
225             code => _code_with_defaults(\&elide),
226             as => 'elide_with_defaults',
227             });
228             }
229              
230             #pod =head2 trunc_with_defaults
231             #pod
232             #pod This routine behaves exactly like elide_with_defaults, with one obvious
233             #pod exception: it returns code that works like C<trunc> rather than C<elide>. If a
234             #pod C<marker> argument is passed, it is ignored.
235             #pod
236             #pod =cut
237              
238             BEGIN {
239 3     3   271 install_sub({
240             code => _code_with_defaults(\&trunc, ['marker']),
241             as => 'trunc_with_defaults',
242             });
243             }
244              
245             #pod =head1 SEE ALSO
246             #pod
247             #pod L<Text::Truncate> does a very similar thing. So does L<Text::Elide>.
248             #pod
249             #pod =head1 BUGS
250             #pod
251             #pod Please report any bugs or feature requests through the web interface at
252             #pod L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=String-Truncate>. I will be
253             #pod notified, and then you'll automatically be notified of progress on your bug as
254             #pod I make changes.
255             #pod
256             #pod =head1 ACKNOWLEDGEMENTS
257             #pod
258             #pod Ian Langworth gave me some good advice about naming things. (Also some bad
259             #pod jokes. Nobody wants String::ETOOLONG, Ian.) Hans Dieter Pearcey suggested
260             #pod allowing defaults just in time for a long bus ride, and I was rescued from
261             #pod boredom by that suggestion
262             #pod
263             #pod =cut
264              
265             1; # End of String::Truncate
266              
267             __END__
268              
269             =pod
270              
271             =encoding UTF-8
272              
273             =head1 NAME
274              
275             String::Truncate - a module for when strings are too long to be displayed in...
276              
277             =head1 VERSION
278              
279             version 1.100603
280              
281             =head1 SYNOPSIS
282              
283             This module handles the simple but common problem of long strings and finite
284             terminal width. It can convert:
285              
286             "this is your brain" -> "this is your ..."
287             or "...is your brain"
288             or "this is... brain"
289             or "... is your b..."
290              
291             It's simple:
292              
293             use String::Truncate qw(elide);
294              
295             my $brain = "this is your brain";
296              
297             elide($brain, 16); # first option
298             elide($brain, 16, { truncate => 'left' }); # second option
299             elide($brain, 16, { truncate => 'middle' }); # third option
300             elide($brain, 16, { truncate => 'ends' }); # fourth option
301              
302             String::Trunc::trunc($brain, 16); # => "this is your bra"
303              
304             =head1 PERL VERSION
305              
306             This library should run on perls released even a long time ago. It should work
307             on any version of perl released in the last five years.
308              
309             Although it may work on older versions of perl, no guarantee is made that the
310             minimum required version will not be increased. The version may be increased
311             for any reason, and there is no promise that patches will be accepted to lower
312             the minimum required perl.
313              
314             =head1 FUNCTIONS
315              
316             =head2 elide
317              
318             elide($string, $length, \%arg)
319              
320             This function returns the string, if it is less than or equal to C<$length>
321             characters long. If it is longer, it truncates the string and marks the
322             elision.
323              
324             Valid arguments are:
325              
326             truncate - elide at left, right, middle, or ends? (default: right)
327             marker - how to mark the elision (default: ...)
328             at_space - if true, strings will be broken at whitespace if possible
329              
330             =head2 trunc
331              
332             trunc($string, $length, \%arg)
333              
334             This acts just like C<elide>, but assumes an empty marker, so it actually
335             truncates the string normally.
336              
337             =head1 IMPORTING
338              
339             String::Truncate exports both C<elide> and C<trunc>, and also supports the
340             Exporter-style ":all" tag.
341              
342             use String::Truncate (); # export nothing
343             use String::Truncate qw(elide); # export just elide()
344             use String::Truncate qw(:all); # export both elide() and trunc()
345             use String::Truncate qw(-all); # export both elide() and trunc()
346              
347             When exporting, you may also supply default values:
348              
349             use String::Truncate -all => defaults => { length => 10, marker => '--' };
350              
351             # or
352              
353             use String::Truncate -all => { length => 10, marker => '--' };
354              
355             These values affect only the imported version of the functions. You may pass
356             arguments as usual to override them, and you may call the subroutine by its
357             fully-qualified name to get the standard behavior.
358              
359             =head1 BUILDING CODEREFS
360              
361             The imported builds and installs lexical closures (code references) that merge
362             in given values to the defaults. You can build your own closures without
363             importing them into your namespace. To do this, use the C<elide_with_defaults>
364             and C<trunc_with_defaults> routines.
365              
366             =head2 elide_with_defaults
367              
368             my $elider = String::Truncate::elide_with_defaults(\%arg);
369              
370             This routine, never exported, builds a coderef which behaves like C<elide>, but
371             uses default values when needed. All the valid arguments to C<elide> are valid
372             here, as well as C<length>.
373              
374             =head2 trunc_with_defaults
375              
376             This routine behaves exactly like elide_with_defaults, with one obvious
377             exception: it returns code that works like C<trunc> rather than C<elide>. If a
378             C<marker> argument is passed, it is ignored.
379              
380             =head1 SEE ALSO
381              
382             L<Text::Truncate> does a very similar thing. So does L<Text::Elide>.
383              
384             =head1 BUGS
385              
386             Please report any bugs or feature requests through the web interface at
387             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=String-Truncate>. I will be
388             notified, and then you'll automatically be notified of progress on your bug as
389             I make changes.
390              
391             =head1 ACKNOWLEDGEMENTS
392              
393             Ian Langworth gave me some good advice about naming things. (Also some bad
394             jokes. Nobody wants String::ETOOLONG, Ian.) Hans Dieter Pearcey suggested
395             allowing defaults just in time for a long bus ride, and I was rescued from
396             boredom by that suggestion
397              
398             =head1 AUTHOR
399              
400             Ricardo Signes <cpan@semiotic.systems>
401              
402             =head1 CONTRIBUTORS
403              
404             =for stopwords David Steinbrunner Ricardo SIGNES Signes
405              
406             =over 4
407              
408             =item *
409              
410             David Steinbrunner <dsteinbrunner@pobox.com>
411              
412             =item *
413              
414             Ricardo SIGNES <rjbs@codesimply.com>
415              
416             =item *
417              
418             Ricardo Signes <rjbs@semiotic.systems>
419              
420             =back
421              
422             =head1 COPYRIGHT AND LICENSE
423              
424             This software is copyright (c) 2022 by Ricardo Signes.
425              
426             This is free software; you can redistribute it and/or modify it under
427             the same terms as the Perl 5 programming language system itself.
428              
429             =cut