File Coverage

blib/lib/Text/Autoformat/Hang.pm
Criterion Covered Total %
statement 86 116 74.1
branch 29 64 45.3
condition 26 53 49.0
subroutine 19 23 82.6
pod 0 12 0.0
total 160 268 59.7


line stmt bran cond sub pod time code
1             package Text::Autoformat::Hang;
2             $Text::Autoformat::Hang::VERSION = '1.75';
3 5     5   91 use 5.006;
  5         18  
4 5     5   27 use strict;
  5         10  
  5         102  
5 5     5   23 use warnings;
  5         11  
  5         3764  
6              
7             # ROMAN NUMERALS
8              
9 20     20 0 43 sub inv($@) { my ($k, %inv)=shift; for(0..$#_) {$inv{$_[$_]}=$_*$k} %inv }
  20         53  
  170         340  
  20         153  
10             my @unit= ( "" , qw ( I II III IV V VI VII VIII IX ));
11             my @ten = ( "" , qw ( X XX XXX XL L LX LXX LXXX XC ));
12             my @hund= ( "" , qw ( C CC CCC CD D DC DCC DCCC CM ));
13             my @thou= ( "" , qw ( M MM MMM ));
14             my %rval= (inv(1,@unit),inv(10,@ten),inv(100,@hund),inv(1000,@thou));
15             my $rbpat= join ")(",join("|",reverse @thou), join("|",reverse @hund), join("|",reverse @ten), join("|",reverse @unit);
16             my $rpat= join ")(?:",join("|",reverse @thou), join("|",reverse @hund), join("|",reverse @ten), join("|",reverse @unit);
17             my $rom = qq/(?:(?=[MDCLXVI])(?:$rpat))/;
18              
19             my $abbrev = join '|', qw{ etc[.] pp[.] ph[.]?d[.] },
20             "(?!$rom)(?:[A-Z][A-Za-z]+[.])+",
21             '(?:[A-Z][.])(?:[A-Z][.])+';
22              
23             sub fromRoman($)
24             {
25 3 50   3 0 95 return 0 unless $_[0] =~ /^.*?($rbpat).*$/i;
26 3         27 return $rval{uc $1} + $rval{uc $2} + $rval{uc $3} + $rval{uc $4};
27             }
28              
29             sub toRoman($$)
30             {
31 0     0 0 0 my ($num,$example) = @_;
32 0 0       0 return '' unless $num =~ /^([0-3]??)(\d??)(\d??)(\d)$/;
33 0   0     0 my $roman = $thou[$1||0] . $hund[$2||0] . $ten[$3||0] . $unit[$4||0];
      0        
      0        
      0        
34 0 0       0 return $example=~/[A-Z]/ ? uc $roman : lc $roman;
35             }
36              
37             # BITS OF A NUMERIC VALUE
38              
39             my $num = q/(?:[0-9]{1,3}\b(?!:[0-9][0-9]\b))/; # Ignore 8:20 etc.
40             my $let = q/[A-Za-z]/;
41             my $pbr = q/[[(<]/;
42             my $sbr = q/])>/;
43             my $ows = q/[ \t]*/;
44             my %close = ( '[' => ']', '(' => ')', '<' => '>', "" => '' );
45              
46             my $hangPS = qq{(?i:ps:|(?:p\\.?)+s\\b\\.?(?:[ \\t]*:)?)};
47             my $hangNB = qq{(?i:n\\.?b\\.?(?:[ \\t]*:)?)};
48             my $hangword = qq{(?:(?:Note)[ \\t]*:)};
49             my $hangbullet = qq{[*.+-]};
50             my $hang = qq{(?:(?i)(?:$hangNB|$hangword|$hangbullet)(?=[ \t]))};
51              
52             # IMPLEMENTATION
53              
54             sub new {
55 30     30 0 74 my ($class, $orig, $lists_mode) = @_;
56 30 50       63 return Text::Autoformat::NullHang->new() if !$lists_mode;
57              
58 30         49 my $origlen = length $orig;
59 30         44 my @vals;
60 30 50 66     400 if ($_[1] =~ s#\A($hangPS)##) {
    50          
    50          
61 0         0 @vals = { type => 'ps', val => $1 }
62             }
63             elsif ($lists_mode =~ /1|bullet/i && $_[1] =~ s#\A($hang)##) {
64 0         0 @vals = { type => 'bul', val => $1 }
65             }
66             elsif ($_[1] =~ m#\A\([^\s)]+\s#) {
67 0         0 @vals = ();
68             }
69             else {
70 5     5   39 no warnings "all";
  5         10  
  5         2917  
71 30         6692 my $cut;
72 30         68 while (length $_[1]) {
73 31 0 0     418 last if $_[1] =~ m#\A($ows)($abbrev)#
      33        
74             && (length $1 || !@vals); # ws-separated or first
75              
76 31 50       196 last if $_[1] =~ m{\A $ows $pbr [^$sbr \t]* \s}xms;
77              
78 31         50 $cut = $origlen - length $_[1];
79 31 50       159 my $pre = $_[1] =~ s#\A($ows$pbr$ows)## ? $1 : "";
80 31 50 66     560 my $val
    100 100        
    100 66        
81             = ($lists_mode =~ /1|number/i && $_[1] =~ s#\A($num)##)
82             ? { type=>'num', val=>$1 }
83             : ($lists_mode =~ /1|roman/i && $_[1] =~ s#\A($rom)\b##i)
84             ? { type=>'rom', val=>$1, nval=>fromRoman($1) }
85             : ($lists_mode =~ /1|alpha/i && $_[1] =~ s#\A($let(?!$let))##i)
86             ? { type=>'let', val=>$1 }
87             : { val => "", type => "" };
88 31 100 50     164 $_[1] = $pre.$_[1] and last unless length $val->{val};
89 6   100     106 $val->{post} = $pre && $_[1] =~ s#\A($ows()[.:/]?[$close{$pre}][.:/]?)## && $1
90             || $_[1] =~ s#\A($ows()[$sbr.:/])## && $1
91             || "";
92 6         16 $val->{pre} = $pre;
93 6         10 $val->{cut} = $cut;
94 6         22 push @vals, $val;
95             }
96 30   100     96 while (@vals && !$vals[-1]{post}) {
97 2         13 $_[1] = substr($orig,pop(@vals)->{cut});
98             }
99             }
100              
101             # check for orphaned years or unlikely Roman numerals...
102 30 50 66     97 if (@vals==1 && defined $vals[0]->{post} && $vals[0]->{post} =~ /[\.>)]/) {
      66        
103 4         8 my $v = $vals[0];
104 4 50 33     20 if ($v->{type} eq 'num' && $v->{val} >= 1000) {
105 0         0 $_[1] = substr($orig,pop(@vals)->{cut});
106             }
107             }
108              
109 30 100       102 return Text::Autoformat::NullHang->new if !@vals;
110 4         18 bless \@vals, $class;
111             }
112              
113             sub incr {
114 5     5   48 no warnings "all";
  5         11  
  5         2120  
115 4     4 0 12 my ($self, $prev, $prevsig) = @_;
116 4         7 my $level;
117             # check compatibility
118              
119 4 100 66     15 return unless $prev && !$prev->empty;
120              
121 2 50       10 for $level (0..(@$self<@$prev ? $#$self : $#$prev)) {
122 2 50       7 if ($self->[$level]{type} ne $prev->[$level]{type}) {
123 0 0       0 return if @$self<=@$prev; # no incr if going up
124 0         0 $prev = $prevsig;
125 0         0 last;
126             }
127             }
128 2 50 33     8 return unless $prev && !$prev->empty;
129 2 50       11 if ($self->[0]{type} eq 'ps') {
    50          
    50          
130 0         0 my $count = 1 + $prev->[0]{val} =~ s/(p[.]?)/$1/gi;
131 0         0 $prev->[0]{val} =~ /^(p[.]?).*(s[.]?[:]?)/;
132 0         0 $self->[0]{val} = $1 x $count . $2;
133             }
134             elsif ($self->[0]{type} eq 'bul') {
135             # do nothing
136             }
137             elsif (@$self>@$prev) { # going down level(s)
138 0         0 for $level (0..$#$prev) {
139 0         0 @{$self->[$level]}{'val','nval'} = @{$prev->[$level]}{'val','nval'};
  0         0  
  0         0  
140             }
141 0         0 for $level (@$prev..$#$self) {
142 0         0 _reset($self->[$level]);
143             }
144             }
145             else # same level or going up
146             {
147 2         6 for $level (0..$#$self) {
148 2         3 @{$self->[$level]}{'val','nval'} = @{$prev->[$level]}{'val','nval'};
  2         7  
  2         6  
149             }
150 2         7 _incr($self->[-1])
151             }
152             }
153              
154             sub _incr {
155 5     5   37 no warnings "all";
  5         10  
  5         754  
156 2 50   2   6 if ($_[0]{type} eq 'rom') {
157 0         0 $_[0]{val} = toRoman(++$_[0]{nval},$_[0]{val});
158             }
159             else {
160 2 50 33     12 $_[0]{val}++ unless $_[0]{type} eq 'let' && $_[0]{val}=~/Z/i;
161             }
162             }
163              
164             sub _reset {
165 5     5   35 no warnings "all";
  5         10  
  5         959  
166 0 0   0   0 if ($_[0]{type} eq 'rom') {
    0          
167 0         0 $_[0]{val} = toRoman($_[0]{nval}=1,$_[0]{val});
168             }
169             elsif ($_[0]{type} eq 'let') {
170 0 0       0 $_[0]{val} = $_[0]{val} =~ /[A-Z]/ ? 'A' : 'a';
171             }
172             else {
173 0         0 $_[0]{val} = 1;
174             }
175             }
176              
177             sub stringify {
178 8     8 0 14 my ($self) = @_;
179 8         15 my ($str, $level) = ("");
180 8         15 for $level (@$self) {
181 5     5   33 no warnings "all";
  5         10  
  5         1081  
182 8         13 $str .= join "", @{$level}{'pre','val','post'};
  8         24  
183             }
184 8         24 return $str;
185             }
186              
187             sub val {
188 0     0 0 0 my ($self, $i) = @_;
189 0         0 return $self->[$i]{val};
190             }
191              
192 2     2 0 6 sub fields { return scalar @{$_[0]} }
  2         10  
193              
194             sub field {
195 0     0 0 0 my ($self, $i, $newval) = @_;
196 0 0       0 $self->[$i]{type} = $newval if @_>2;
197 0         0 return $self->[$i]{type};
198             }
199              
200             sub signature {
201 5     5   34 no warnings "all";
  5         10  
  5         1420  
202 8     8 0 14 my ($self) = @_;
203 8         16 my ($str, $level) = ("");
204 8         13 for $level (@$self) {
205 8   50     18 $level->{type} ||= "";
206             $str .= join "", $level->{pre},
207             ($level->{type} =~ /rom|let/ ? "romlet" : $level->{type}),
208 8 50       51 $level->{post};
209             }
210 8         26 return $str;
211             }
212              
213             sub length {
214 4     4 0 15 length $_[0]->stringify
215             }
216              
217 14     14 0 45 sub empty { 0 }
218              
219             1;
220