File Coverage

blib/lib/Data/Printer/Theme.pm
Criterion Covered Total %
statement 102 104 98.0
branch 39 44 88.6
condition 13 16 81.2
subroutine 16 16 100.0
pod 0 6 0.0
total 170 186 91.4


line stmt bran cond sub pod time code
1             package Data::Printer::Theme;
2 36     36   68677 use strict;
  36         84  
  36         1068  
3 36     36   206 use warnings;
  36         78  
  36         856  
4 36     36   635 use Data::Printer::Common;
  36         77  
  36         24417  
5              
6             # the theme name
7             sub name {
8 8     8 0 75 my ($self) = @_;
9 8         45 return $self->{name};
10             }
11              
12             # true if the theme has at least one color override
13             sub customized {
14 2     2 0 7 my ($self) = @_;
15 2 100       13 return exists $self->{is_custom} ? 1 : 0;
16             }
17              
18             # displays the color as-is
19             sub color_for {
20 5     5 0 15 my ($self, $color_type) = @_;
21 5   50     46 return $self->{colors}{$color_type} || '';
22             }
23              
24             # prints the SGR (terminal) color modifier
25             sub sgr_color_for {
26 12     12 0 3336 my ($self, $color_type) = @_;
27 12 100       42 return unless exists $self->{sgr_colors}{$color_type};
28 9   100     50 return $self->{sgr_colors}{$color_type} || ''
29             }
30              
31             # prints the SGR (terminal) color reset modifier
32 1     1 0 3 sub color_reset { return "\e[m" }
33              
34             sub new {
35 265     265 0 7484 my ($class, %params) = @_;
36              
37 265         555 my $color_level = $params{color_level};
38 265         568 my $colors_to_override = $params{color_overrides};
39 265         1565 my $theme_name = $params{name};
40              
41             # before we put user info on string eval, make sure
42             # it's just a module name:
43 265         994 $theme_name =~ s/[^a-zA-Z0-9:]+//gsm;
44              
45 265         1064 my $theme = bless {
46             name => $theme_name,
47             color_level => $color_level,
48             colors => {},
49             sgr_colors => {},
50             }, $class;
51 265 100       835 $theme->_load_theme($params{ddp}) or delete $theme->{name};
52 265         950 $theme->_maybe_override_theme_colors($colors_to_override, $params{ddp});
53 265         976 return $theme;
54             }
55              
56             sub _maybe_override_theme_colors {
57 265     265   613 my ($self, $colors_to_override, $ddp) = @_;
58              
59 265 50 66     878 return unless $colors_to_override
      66        
60             && ref $colors_to_override eq 'HASH'
61             && keys %$colors_to_override;
62              
63             my $error = Data::Printer::Common::_tryme(sub {
64 13     13   35 foreach my $kind (keys %$colors_to_override ) {
65 17         34 my $override = $colors_to_override->{$kind};
66 17 100       43 die "invalid color for '$kind': must be scalar not ref" if ref $override;
67 16         31 my $parsed = $self->_parse_color($override, $ddp);
68 16 100       57 if (defined $parsed) {
69 6         11 $self->{colors}{$kind} = $override;
70 6         13 $self->{sgr_colors}{$kind} = $parsed;
71 6         17 $self->{is_custom}{$kind} = 1;
72             }
73             }
74 13         84 });
75 13 100       83 if ($error) {
76 1         16 Data::Printer::Common::_warn($ddp, "error overriding color: $error. Skipping!");
77             }
78 13         790 return;
79             }
80              
81             sub _load_theme {
82 265     265   544 my ($self, $ddp) = @_;
83 265         560 my $theme_name = $self->{name};
84              
85 265         647 my $class = 'Data::Printer::Theme::' . $theme_name;
86 265         856 my $error = Data::Printer::Common::_tryme("use $class; 1;");
87 265 100       701 if ($error) {
88 1         15 Data::Printer::Common::_warn($ddp, "error loading theme '$theme_name': $error.");
89 1         12 return;
90             }
91 264         561 my $loaded_colors = {};
92 264         505 my $loaded_colors_sgr = {};
93             $error = Data::Printer::Common::_tryme(sub {
94 264     264   391 my $class_colors;
95 36     36   311 { no strict 'refs'; $class_colors = &{ $class . '::colors'}(); }
  36         90  
  36         32934  
  264         408  
  264         380  
  264         1562  
96 264 100       995 die "${class}::colors() did not return a hash reference"
97             unless ref $class_colors eq 'HASH';
98              
99 263         1498 foreach my $kind (keys %$class_colors) {
100 6049         8896 my $loaded_color = $class_colors->{$kind};
101 6049 50       10537 die "color for '$kind' must be a scalar in theme '$theme_name'"
102             if ref $loaded_color;
103 6049         10456 my $parsed_color = $self->_parse_color($loaded_color, $ddp);
104 6049 50       11434 if (defined $parsed_color) {
105 6049         10877 $loaded_colors->{$kind} = $loaded_color;
106 6049         12570 $loaded_colors_sgr->{$kind} = $parsed_color;
107             }
108             }
109 264         1749 });
110 264 100       2065 if ($error) {
111 1         7 Data::Printer::Common::_warn($ddp, "error loading theme '$theme_name': $error. Output will have no colors");
112 1         6 return;
113             }
114 263         652 $self->{colors} = $loaded_colors;
115 263         517 $self->{sgr_colors} = $loaded_colors_sgr;
116 263         815 return 1;
117             }
118              
119             sub _parse_color {
120 6066     6066   10251 my ($self, $color_label, $ddp) = @_;
121 6066 50       10889 return unless defined $color_label;
122 6066 100       10037 return '' unless $color_label;
123              
124 6052         7794 my $color_code;
125 6052 100       20617 if ($color_label =~ /\Argb\((\d+),(\d+),(\d+)\)\z/) {
    100          
    100          
    100          
126 4         14 my ($r, $g, $b) = ($1, $2, $3);
127 4 100 100     35 if ($r < 256 && $g < 256 && $b < 256) {
      100        
128 1 50       6 if ($self->{color_level} == 3) {
129 1         5 $color_code = "\e[0;38;2;$r;$g;${b}m";
130             }
131             else {
132 0         0 my $reduced = _rgb2short($r,$g,$b);
133 0         0 $color_code = "\e[0;38;5;${reduced}m";
134             }
135             }
136             else {
137 3         20 Data::Printer::Common::_warn($ddp, "invalid color '$color_label': all colors must be between 0 and 255");
138             }
139             }
140             elsif ($color_label =~ /\A#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})\z/i) {
141 6021         20531 my ($r, $g, $b) = map hex($_), ($1, $2, $3);
142 6021 100       12417 if ($self->{color_level} == 3) {
143 344         749 $color_code = "\e[0;38;2;$r;$g;${b}m";
144             }
145             else {
146 5677         9880 my $reduced = _rgb2short($r,$g,$b);
147 5677         12777 $color_code = "\e[0;38;5;${reduced}m";
148             }
149             }
150             elsif ($color_label =~ /\A\e\[\d+(:?;\d+)*m\z/) {
151 2         81 $color_code = $color_label;
152             }
153             elsif ($color_label =~ /\A
154             (?:
155             \s*
156             (?:on_)?
157             (?:bright_)?
158             (?:black|red|green|yellow|blue|magenta|cyan|white)
159             )+
160             \s*\z/x
161             ) {
162 18         174 my %ansi_colors = (
163             'black' => 30, 'on_black' => 40,
164             'red' => 31, 'on_red' => 41,
165             'green' => 32, 'on_green' => 42,
166             'yellow' => 33, 'on_yellow' => 43,
167             'blue' => 34, 'on_blue' => 44,
168             'magenta' => 35, 'on_magenta' => 45,
169             'cyan' => 36, 'on_cyan' => 46,
170             'white' => 37, 'on_white' => 47,
171             'bright_black' => 90, 'on_bright_black' => 100,
172             'bright_red' => 91, 'on_bright_red' => 101,
173             'bright_green' => 92, 'on_bright_green' => 102,
174             'bright_yellow' => 93, 'on_bright_yellow' => 103,
175             'bright_blue' => 94, 'on_bright_blue' => 104,
176             'bright_magenta' => 95, 'on_bright_magenta' => 105,
177             'bright_cyan' => 96, 'on_bright_cyan' => 106,
178             'bright_white' => 97, 'on_bright_white' => 107,
179             );
180             $color_code = "\e["
181 18         139 . join(';' => map $ansi_colors{$_}, split(/\s+/, $color_label))
182             . 'm'
183             ;
184             }
185             else {
186 7         27 Data::Printer::Common::_warn($ddp, "invalid color '$color_label'");
187             }
188 6052         19258 return $color_code;
189             }
190              
191             sub _rgb2short {
192 5678     5678   8856 my ($r,$g,$b) = @_;
193 5678         9361 my @snaps = (47, 115, 155, 195, 235);
194 5678         7256 my @new;
195 5678         9663 foreach my $color ($r,$g,$b) {
196 17034         21570 my $big = 0;
197 17034         23446 foreach my $s (@snaps) {
198 85170 100       152419 $big++ if $s < $color;
199             }
200 17034         27358 push @new, $big
201             }
202 5678         12660 return $new[0]*36 + $new[1]*6 + $new[2] + 16
203             }
204              
205             1;
206             __END__