File Coverage

blib/lib/Term/Chrome.pm
Criterion Covered Total %
statement 89 92 96.7
branch 50 68 73.5
condition 14 18 77.7
subroutine 22 22 100.0
pod 3 5 60.0
total 178 205 86.8


line stmt bran cond sub pod time code
1 4     4   159321 use strict;
  4         22  
  4         91  
2 4     4   13 use warnings;
  4         7  
  4         233  
3              
4             package Term::Chrome;
5             # ABSTRACT: DSL for colors and other terminal chrome
6             our $VERSION = '2.01';
7              
8             # Pre-declare packages
9             {
10             package # no index: private package
11             Term::Chrome::Color;
12             }
13              
14              
15 4     4   19 use Exporter 5.57 'import'; # perl 5.8.3
  4         62  
  4         113  
16             # @EXPORT is defined at the end
17              
18 4     4   18 use Carp ();
  4         6  
  4         79  
19 4     4   15 use Scalar::Util ();
  4         14  
  4         917  
20             our @CARP_NOT = qw< Term::Chrome::Color >;
21              
22             # Private constructor for Term::Chrome objects. Lexical, so cross-packages.
23             # Arguments:
24             # - class name
25             # - foreground color
26             # - background color
27             # - flags list
28             my $new = sub
29             {
30             my ($class, @self) = @_;
31              
32             my $fg = $self[0];
33             Carp::croak "invalid fg color $fg"
34             if defined($fg) && ($fg < 0 || $fg > 255);
35             my $bg = $self[1];
36             Carp::croak "invalid bg color $bg"
37             if defined($bg) && ($bg < 0 || $bg > 255);
38             # TODO check flags
39              
40             bless \@self, $class
41             };
42              
43              
44             # Cache for color objects
45             my %COLOR_CACHE;
46              
47             sub color ($)
48             {
49 49     49 0 273 my $color = shift;
50 49 50       77 die "invalid color" if ref $color;
51 49         61 my $c = chr $color;
52             # We can not use '$COLOR_CACHE{$c} ||= ...' because this requires overloading
53             # We can not use 'no overloading' because this requires perl 5.10
54             exists $COLOR_CACHE{$c}
55             ? $COLOR_CACHE{$c}
56 49 100       111 : ($COLOR_CACHE{$c} = Term::Chrome::Color->$new($color, undef))
57             }
58              
59              
60             use overload
61             '""' => 'term',
62             '+' => '_plus',
63             '${}' => '_deref',
64             '&{}' => '_chromizer',
65             '.' => '_concat',
66             '!' => '_reverse',
67             'bool' => sub () { 1 },
68 4         20 fallback => 0,
69 4     4   4019 ;
  4         3159  
70              
71             sub term
72             {
73 117     117 0 351 my $self = shift;
74 117         124 my ($fg, $bg) = @{$self}[0, 1];
  117         324  
75 117         180 my $r = join(';', @{$self}[2 .. $#$self]);
  117         213  
76 117 100 66     320 if (defined($fg) || defined($bg)) {
77 47 100       95 $r .= ';' if @$self > 2;
78 47 50       75 if (defined $fg) {
79             # LeoNerd says that this should be ----------> "38:5:$fg"
80             # according to the spec but gnome-terminal doesn't support that
81 47 50       111 $r .= $fg < 8 ? (30+$fg) : $fg < 16 ? "9$fg" : "38;5;$fg";
    100          
82 47 100       71 $r .= ';' if defined $bg;
83             }
84             # -------> "48:5:$bg"
85 47 50       78 $r .= $bg < 8 ? (40+$bg) : $bg < 16 ? "10$bg" : "48;5;$bg" if defined $bg;
    100          
    100          
86             } else {
87 70 100       129 return '' unless @$self > 2
88             }
89 114         386 "\e[${r}m"
90             }
91              
92              
93             sub _plus
94             {
95 40     40   198 my ($self, $other, $swap) = @_;
96              
97 40 50       71 return $self unless defined $other;
98              
99 40 50       112 die 'invalid value for +' unless $other->isa(__PACKAGE__);
100              
101 40         75 my @new = @$self;
102 40 50       144 $new[0] = $other->[0] if defined $other->[0];
103 40 50       52 $new[1] = $other->[1] if defined $other->[1];
104 40         59 push @new, @{$other}[2 .. $#$other];
  40         63  
105              
106 40         3923 bless \@new
107             }
108              
109             my %reverse = (
110             # Unfortunately there isn't a perfect mapping
111             # Reference:
112             # https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf page 75
113             1 => 22,
114             2 => 22,
115             3 => 23,
116             4 => 24, # Underlined
117             5 => 25,
118             6 => 25,
119             7 => 27,
120             8 => 28,
121             9 => 29,
122             21 => 24, # Double underline
123              
124             22 => 1,
125             23 => 3,
126             24 => 4,
127             25 => 5,
128             27 => 7,
129             28 => 8,
130             29 => 9,
131             );
132              
133             sub _reverse
134             {
135 14     14   18 my $self = shift;
136 14         27 my @new = (undef, undef);
137 14 50       29 push @new, 39 if $self->[0]; # ResetFg
138 14 100       25 push @new, 49 if $self->[1]; # ResetBg
139             # Reset/ResetFlags/ResetFg/ResetBg are removed
140             # Other flags are reversed
141 14 100 66     18 push @new, map { (!$_ || $_ > 30 || !exists $reverse{$_}) ? () : $reverse{$_} } @{$self}[2..$#$self];
  18         86  
  14         25  
142 14         74 bless \@new, 'Term::Chrome::Flag'
143             }
144              
145             sub _deref
146             {
147 30     30   62 \("$_[0]")
148             }
149              
150             sub _concat
151             {
152 23 100   23   2231 $_[2] ? $_[1].$_[0]->term
153             : $_[0]->term.$_[1]
154             }
155              
156              
157             sub _chromizer
158             {
159 9     9   108 my $self = shift;
160 9         19 my $begin = $self->term;
161 9         18 my $end = $self->_reverse->term;
162             sub {
163 10 50   10   21 unless (defined $_[0]) {
164 0         0 Carp::carp "missing argument in Term::Chrome chromizer";
165             return
166 0         0 }
167 10         36 $begin . $_[0] . $end
168             }
169 9         56 }
170              
171             sub fg
172             {
173 4     4 1 21 my $c = $_[0]->[0];
174 4 50       11 defined($c) ? color($c) : undef
175             }
176              
177             sub bg
178             {
179 3     3 1 7 my $c = $_[0]->[1];
180 3 100       9 defined($c) ? color($c) : undef
181             }
182              
183             sub flags
184             {
185 3     3 1 4 my $self = shift;
186 3 50       10 return undef unless @$self > 2;
187 3         5 __PACKAGE__->$new(undef, undef, @{$self}[2..$#$self])
  3         6  
188             }
189              
190             package # no index: private package
191             Term::Chrome::Color;
192              
193             our @ISA = qw< Term::Chrome >;
194              
195             use overload
196             '/' => '_over',
197             # Even if overloading is set in the super class, we have to repeat it for old perls
198             (
199             $^V ge v5.18.0
200             ? ()
201             : (
202             '""' => \&Term::Chrome::term,
203             '+' => \&Term::Chrome::_plus,
204             '${}' => \&Term::Chrome::_deref,
205             '.' => \&Term::Chrome::_concat,
206             '!' => \&Term::Chrome::_reverse,
207             'bool' => sub () { 1 },
208             )
209 4 50       55 ),
210             fallback => 0,
211 4     4   3079 ;
  4         5  
212              
213             sub _over
214             {
215 21 50   21   349 die 'invalid bg color for /' unless ref($_[1]) eq __PACKAGE__;
216 21         46 Term::Chrome->$new($_[0]->[0], $_[1]->[0])
217             }
218              
219             package # no index: private package
220             Term::Chrome::Flag;
221              
222             our @ISA = qw< Term::Chrome >;
223              
224             use overload
225             '+' => '_plus',
226             '!' => '_reverse',
227             # Even if overloading is set in the super class, we have to repeat it for old perls
228             (
229             $^V ge v5.18.0
230             ? ()
231             : (
232             '""' => \&Term::Chrome::term,
233             '${}' => \&Term::Chrome::_deref,
234             '.' => \&Term::Chrome::_concat,
235             'bool' => sub () { 1 },
236             )
237 4 50       38 ),
238             fallback => 0,
239 4     4   761 ;
  4         7  
240              
241             sub _reverse
242             {
243 13     13   181 my $self = shift;
244             bless [
245             undef, undef,
246             # Reset/ResetFlags/ResetFg/ResetBg are removed
247 13 100 66     22 map { (!$_ || $_ > 30 || !exists $reverse{$_}) ? () : $reverse{$_} } @{$self}[2..$#$self]
  29         313  
  13         21  
248             ]
249             }
250              
251             sub _plus
252             {
253 24     24   46 my ($self, $other, $swap) = @_;
254              
255 24 50       36 return $self unless defined $other;
256              
257 24 50       55 Carp::croak(q{Can't combine Term::Chrome with }.$other)
258             unless Scalar::Util::blessed $other;
259              
260 24 100       71 if ($other->isa(__PACKAGE__)) {
    50          
261             # Reset
262 23 100       79 return $other if !$other->[2];
263             # ResetFlags
264 20 100 100     118 return $other if $#$other == 8 || ($self->[2] && $self->[2] < 30 && $other->[2] == $reverse{$self->[2]});
      100        
      66        
265             # Concat flags
266 18         32 __PACKAGE__->$new(@$self, @{$other}[2..$#$other])
  18         31  
267             } elsif ($other->isa(Term::Chrome::)) {
268 1         2 $other->_plus($self, '')
269             } else {
270 0           Carp::croak(q{Can't combine Term::Chrome with }.ref($other))
271             }
272             }
273              
274              
275             package
276             Term::Chrome;
277              
278             # Build the constants and the @EXPORT list
279             #
280             # This block must be after "use overload" (for both Term::Chrome
281             # and Term::Chrome::Color) because overload must be set before blessing
282             # due to a bug in perl < 5.18
283             # (according to a comment in Types::Serialiser source)
284              
285             my $mk_flag = sub { Term::Chrome::Flag->$new(undef, undef, @_) };
286              
287             my %const = (
288             Reset => $mk_flag->(''),
289             ResetFg => $mk_flag->(39),
290             ResetBg => $mk_flag->(49),
291             ResetFlags => $mk_flag->(22, 23, 24, 25, 27, 28),
292             Standout => $mk_flag->(7),
293             Underline => $mk_flag->(4),
294             Reverse => $mk_flag->(7),
295             Blink => $mk_flag->(5),
296             Bold => $mk_flag->(1),
297              
298             Black => color 0,
299             Red => color 1,
300             Green => color 2,
301             Yellow => color 3,
302             Blue => color 4,
303             Magenta => color 5,
304             Cyan => color 6,
305             White => color 7,
306              
307             # Larry Wall's favorite color
308             # The true 'chartreuse' color from X11 colors is #7fff00
309             # The xterm-256 color #118 is near: #87ff00
310             Chartreuse => color 118,
311             );
312              
313             our @EXPORT = ('color', keys %const);
314              
315             # In 17fd029f we avoided to use constant.pm on perl < 5.16
316             # This does not seem necessary anymore.
317             require constant;
318             constant->import(\%const);
319              
320             1;
321             # vim:set et ts=8 sw=4 sts=4: