File Coverage

blib/lib/Unicode/CharWidth.pm
Criterion Covered Total %
statement 68 110 61.8
branch 6 24 25.0
condition 4 17 23.5
subroutine 21 28 75.0
pod n/a
total 99 179 55.3


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