File Coverage

blib/lib/HTML/FromANSI/Tiny.pm
Criterion Covered Total %
statement 72 72 100.0
branch 24 28 85.7
condition 7 9 77.7
subroutine 13 13 100.0
pod 7 7 100.0
total 123 129 95.3


line stmt bran cond sub pod time code
1             # vim: set ts=2 sts=2 sw=2 expandtab smarttab:
2             #
3             # This file is part of HTML-FromANSI-Tiny
4             #
5             # This software is copyright (c) 2011 by Randy Stauner.
6             #
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             #
10 10     10   33788 use strict;
  10         18  
  10         274  
11 10     10   32 use warnings;
  10         14  
  10         8991  
12              
13             package HTML::FromANSI::Tiny;
14             # git description: v0.103-6-g909cd91
15              
16             our $AUTHORITY = 'cpan:RWSTAUNER';
17             # ABSTRACT: Easily convert colored command line output to HTML
18             $HTML::FromANSI::Tiny::VERSION = '0.104';
19             our @COLORS = map { "#$_" }
20             qw(
21             000 f33 2c2 bb0 55c d3d 0cc bbb
22             555 f66 6d6 dd6 99f f6f 6dd fff
23             );
24              
25             # 256 palette.
26             our @COLORS256 = (
27             # First 16 are the same.
28             @COLORS,
29             # rgbXYZ
30             do {
31             my @c;
32             for my $r ( 0 .. 5 ){
33             for my $g ( 0 .. 5 ){
34             for my $b ( 0 .. 5 ){
35             push @c, '#' . join('', map { sprintf '%02x', $_ * (255/5) } ($r, $g, $b));
36             }
37             }
38             }
39             @c; # return
40             },
41             # "nearly black to nearly white"
42             (map { '#' . join('', (sprintf '%02x', ($_ + 1) * 10) x 3) } (0 .. 23)),
43             );
44              
45             our @ALLCOLORS = (@COLORS, @COLORS256);
46              
47              
48             sub new {
49 38     38 1 28638 my $class = shift;
50             my $self = {
51             class_prefix => '',
52             selector_prefix => '',
53             tag => 'span',
54             # It seems particularly unlikely that somebody would want these in their HTML.
55             remove_escapes => 1,
56 38 100       155 @_ == 1 ? %{ $_[0] } : @_,
  5         19  
57             };
58              
59             require Parse::ANSIColor::Tiny
60 38 100       4948 if !$self->{ansi_parser};
61             require HTML::Entities
62 38 100       38261 if !$self->{html_encode};
63              
64 38         33638 bless $self, $class;
65             }
66              
67              
68             sub ansi_parser {
69 55     55 1 1649 my ($self) = @_;
70 55   66     191 return $self->{ansi_parser} ||= do {
71             # hash slice
72 36         72 my (@fields, %copy) = qw(
73             auto_reverse
74             foreground background
75             remove_escapes
76             );
77 36         110 @copy{ @fields } = @$self{ @fields };
78 36         144 Parse::ANSIColor::Tiny->new(%copy);
79             };
80             }
81              
82              
83             sub css {
84 9     9 1 2709 my ($self) = @_;
85 9         22 my $prefix = $self->{selector_prefix} . '.' . $self->{class_prefix};
86              
87 9         12 my $styles = $self->_css_class_attr;
88              
89             my @css = (
90 9         1764 map { "${prefix}$_ { " . $self->_css_attr_string($styles->{$_}) . " }" }
  4932         5152  
91             sort keys %$styles
92             );
93              
94 9 50       1047 return wantarray ? @css : join('', @css);
95             }
96              
97             sub _css_class_attr {
98 11     11   15 my ($self) = @_;
99 11   66     32 return $self->{_all_styles} ||= do {
100              
101 10         43 my $parser = $self->ansi_parser;
102 10         164 my $styles = {
103             bold => { 'font-weight' => 'bold' },
104             dark => { 'opacity' => '0.7' },
105             underline => { 'text-decoration' => 'underline' },
106             concealed => { 'visibility' => 'hidden' },
107             };
108             {
109 10         10 my $i = 0;
  10         7  
110 10         21 foreach my $fg ( $parser->foreground_colors ){
111 2720         3599 $styles->{$fg} = { color => $ALLCOLORS[$i++] };
112             }
113 10         79 $i = 0;
114 10         24 foreach my $bg ( $parser->background_colors ){
115 2720         4746 $styles->{$bg} = { 'background-color' => $ALLCOLORS[$i++] };
116             }
117             }
118              
119             # return
120             +{
121             %$styles,
122 10 100       460 %{ $self->{styles} || {} },
  10         1157  
123             };
124             };
125             }
126              
127             sub _css_attr_string {
128 4944     4944   2982 my ($self, $attr) = @_;
129 4944         4621 return join ' ', map { "$_: $attr->{$_};" } keys %$attr;
  4944         9618  
130             }
131              
132              
133             sub html {
134 36     36 1 13049 my ($self, $text) = @_;
135 36 100       137 $text = $self->ansi_parser->parse($text)
136             unless ref($text) eq 'ARRAY';
137              
138 36         3092 my $tag = $self->{tag};
139 36         38 my $prefix = $self->{class_prefix};
140             # Preload if needed; Don't load if not.
141 36 100       59 my $styles = $self->{inline_style} ? $self->_css_class_attr : {};
142              
143 36         62 local $_;
144             my @html = map {
145 36         39 my ($attr, $text) = @$_;
  91         114  
146 91         120 my $h = $self->html_encode($text);
147              
148             $self->{no_plain_tags} && !@$attr
149             ? $h
150 91 100 100     904 : do {
151             sprintf q[<%s %s="%s">%s], $tag,
152             ($self->{inline_style}
153 12         13 ? (style => join ' ', map { $self->_css_attr_string($styles->{$_}) } @$attr)
154 82 100       212 : (class => join ' ', map { $prefix . $_ } @$attr)
  75         248  
155             ), $h, $tag;
156             }
157              
158             } @$text;
159              
160 36 100       229 return wantarray ? @html : join('', @html);
161             }
162              
163              
164             sub html_encode {
165 91     91 1 70 my ($self, $text) = @_;
166             return $self->{html_encode}->($text)
167 91 100       148 if $self->{html_encode};
168 86         135 return HTML::Entities::encode_entities($text);
169             }
170              
171              
172             sub style_tag {
173 1     1 1 1566 my ($self) = @_;
174 1         17 my @style = ('');
175 1 50       106 return wantarray ? @style : join('', @style);
176             }
177              
178              
179             our @EXPORT_OK = qw( html_from_ansi );
180 2     2 1 16 sub html_from_ansi { __PACKAGE__->new->html(@_) }
181              
182             sub import {
183 1     1   4 my $class = shift;
184 1 50       3 return unless @_;
185              
186 1         8 my $caller = caller;
187 10     10   45 no strict 'refs'; ## no critic (NoStrict)
  10         11  
  10         1068  
188              
189 1         2 foreach my $arg ( @_ ){
190             die "'$arg' is not exported by $class"
191 1 50       1 unless grep { $arg eq $_ } @EXPORT_OK;
  1         3  
192 1         1 *{"${caller}::$arg"} = *{"${class}::$arg"}{CODE};
  1         932  
  1         3  
193             }
194             }
195              
196             1;
197              
198             __END__