File Coverage

blib/lib/Unicode/CharWidth.pm
Criterion Covered Total %
statement 68 111 61.2
branch 5 22 22.7
condition 4 17 23.5
subroutine 20 28 71.4
pod n/a
total 97 178 54.4


line stmt bran cond sub pod time code
1             package Unicode::CharWidth;
2              
3 1     1   14979 use 5.010;
  1         3  
  1         31  
4 1     1   4 use strict;
  1         1  
  1         27  
5 1     1   3 use warnings;
  1         9  
  1         40  
6              
7             =head1 NAME
8              
9             Unicode::CharWidth - Character Width properties
10              
11             =head1 VERSION
12              
13             Version 1.03
14              
15             =cut
16              
17             our $VERSION = '1.03';
18              
19             # the names of the character classes we'll define
20             # we arrange them so, that in an array of 4 elements the mbwidth value
21             # indexes the corresponding element, -1 being equivalent to 3
22              
23 1     1   4 use Carp;
  1         1  
  1         139  
24             our @CARP_NOT = qw(utf8); # otherwise we see errors from unicode_heavy.pl
25              
26 1         84 use constant CLASS_NAMES => (
27             'InZerowidth', # mbwidth == 0
28             'InSinglewidth', # mbwidth == 1
29             'InDoublewidth', # mbwidth == 2
30             'InNowidth', # mbwidth == -1
31 1     1   5 );
  1         1  
32              
33 1     1   4 use constant WIDTH_VALUES => (0 .. 2, -1); # order corresponds to CLASS_NAMES
  1         1  
  1         47  
34 1     1   4 use constant STD_QUICKSTART => 'UCW_startup';
  1         1  
  1         191  
35              
36             sub import {
37 1     1   9 my $class = shift;
38 1         3 my ($arg) = @_;
39 1 50 33     5 if ( $arg and $arg eq '-gen' ) {
40 0         0 _gen_and_save_proptab(_startup_path());
41 0         0 carp 'Exiting';
42 0         0 exit 0; # so no useful program runs with this option
43             }
44 1         2 _compile_functions();
45 1         3 @_ = ($class);
46 1         5 require Exporter;
47 1   50     52 goto(Exporter->can('import') or die q(Exporter can't import?));
48             }
49              
50             our @EXPORT = CLASS_NAMES;
51              
52             # compile the four exported functions
53             sub _compile_functions {
54 1     1   3 my $tabs = _get_proptab(_startup_path());
55 1         3 for my $name ( CLASS_NAMES ) {
56 4         5 my $tab = $tabs->{$name};
57 1     1   4 no strict 'refs';
  1         1  
  1         52  
58 4     0   18 *$name = sub { $tab };
  0         0  
59             }
60             }
61              
62 1     1   432 use Dir::Self;
  1         306  
  1         5  
63 1     1   464 use File::Spec::Functions ();
  1         643  
  1         180  
64              
65             sub _startup_path {
66 2     2   5 File::Spec::Functions::catfile(
67             __DIR__, STD_QUICKSTART()
68             )
69             }
70              
71             sub _get_proptab {
72 1     1   55 my $file = _startup_path();
73 1 50       19 _read_startup($file) || croak(
74             "Missing $file in distribution " . __PACKAGE__
75             )
76             }
77              
78             sub _gen_and_save_proptab {
79 0 0   0   0 unless ( _effective_locale() =~ /\.UTF-8$/ ) {
80 0         0 croak "Generation must be under a UTF-8 locale"
81             }
82 0         0 _write_startup(_gen_proptab(), _startup_path());
83             }
84              
85             sub _effective_locale {
86 0 0 0 0   0 $ENV{LC_CTYPE} || $ENV{LANG} || $ENV{LC_ALL} || ''
      0        
87             }
88              
89 1     1   5 use constant MAX_UNICODE => 0x10FFFF;
  1         2  
  1         230  
90              
91             sub _gen_proptab {
92 0     0   0 require Text::CharWidth;
93 0         0 my @proptab; # we'll make it a hash later (_reform_proptab)
94             # make room for as many elements as we have class names
95             # so index -1 is index 3 (InNowidth)
96 0         0 $#proptab = $#{ [CLASS_NAMES] };
  0         0  
97 0         0 my $last_width = 99; # won't occur
98 0         0 for my $code ( 0 .. MAX_UNICODE ) {
99 0         0 my $width = Text::CharWidth::mbwidth(chr $code);
100 0 0       0 if ( $width == $last_width ) {
101             # continue current interval
102 0         0 $proptab[$width]->[-1]->[1] = $code;
103             } else {
104             # start new interval (pair) for current length
105 0         0 push @{ $proptab[$width] }, [$code, $code];
  0         0  
106             }
107 0         0 $last_width = $width;
108             }
109 0         0 _reform_proptab(@proptab) # make a hash of strings, keyed by class name
110             }
111              
112             sub _reform_proptab {
113 0     0   0 my @proptab = @_;
114 0         0 for my $tab ( @proptab ) {
115 0         0 $tab = join "\n", map _one_or_two(@$_), @$tab;
116             }
117 0         0 my %proptab;
118 0         0 @proptab{CLASS_NAMES()} = @proptab;
119 0         0 \ %proptab
120             }
121              
122 1     1   4 use constant CODEPOINT_FMT => '%04X';
  1         1  
  1         117  
123              
124             sub _one_or_two {
125 0     0   0 my ($from, $to) = @_;
126 0         0 my $fmt = CODEPOINT_FMT; # print only first element if second is equal
127 0 0       0 $fmt .= " $fmt" if $from != $to; # ... or both elements
128 0         0 sprintf $fmt, $from, $to
129             }
130              
131 1     1   632 use Storable ();
  1         2622  
  1         212  
132              
133             sub _read_startup {
134 1     1   1 my ($file) = @_;
135 1 50       1 my $tab = eval { Storable::retrieve($file) } or croak(
  1         3  
136             _strip_error($@)
137             );
138 1 50       93 unless ( _validate_proptab($tab) ) {
139 0         0 croak("File '$file' wasn't created by " . __PACKAGE__);
140             }
141 1         5 $tab;
142             }
143              
144             sub _write_startup {
145 0     0   0 my ($proptab, $file) = @_;
146             # only write validated $proptab
147 0 0       0 die "Failing our own validation" unless _validate_proptab($proptab);
148 0 0       0 if ( eval { Storable::nstore($proptab, $file); 1 } ) {
  0         0  
  0         0  
149 0         0 carp "Created startup file $file";
150             } else {
151             # remove file/line from message and re-croak
152 0         0 croak _strip_error($@);
153             }
154             return # nothing in particular, no-one cares
155 0         0 }
156              
157             sub _strip_error {
158 0     0   0 my ($error) = @_;
159 0         0 $error =~ s/at .* line \d+.*//s;
160 0         0 ucfirst $error
161             }
162              
163             $@ =~ s/at .* line \d+.*//s;
164 1     1   13 use List::Util ();
  1         2  
  1         155  
165              
166             sub _validate_proptab {
167 1     1   2 my ($tab) = @_;
168 1         1 my $ncn = @{ [CLASS_NAMES] }; # number of class names
  1         3  
169 4         11 ref $tab eq 'HASH' and
170 4         9 $ncn == grep { exists $tab->{$_} } CLASS_NAMES and
171 4         72 $ncn == grep { defined $tab->{$_} } CLASS_NAMES and
172 1 50 33     4 $ncn == grep { $tab->{$_} =~ /^[[:xdigit:]\s]*$/ } CLASS_NAMES
      33        
173             }
174              
175             __PACKAGE__
176             __END__