line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# You may distribute under the terms of either the GNU General Public License |
2
|
|
|
|
|
|
|
# or the Artistic License (the same terms as Perl itself) |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# (C) Paul Evans, 2014 -- leonerd@leonerd.org.uk |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package String::Tagged::IRC; |
7
|
|
|
|
|
|
|
|
8
|
4
|
|
|
4
|
|
47839
|
use strict; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
145
|
|
9
|
4
|
|
|
4
|
|
16
|
use warnings; |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
108
|
|
10
|
4
|
|
|
4
|
|
22
|
use base qw( String::Tagged ); |
|
4
|
|
|
|
|
5
|
|
|
4
|
|
|
|
|
2387
|
|
11
|
|
|
|
|
|
|
String::Tagged->VERSION( '0.11' ); # ->clone |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
14
|
|
|
|
|
|
|
|
15
|
4
|
|
|
4
|
|
26457
|
use Convert::Color::mIRC; |
|
4
|
|
|
|
|
70246
|
|
|
4
|
|
|
|
|
110
|
|
16
|
4
|
|
|
4
|
|
30
|
use Convert::Color::RGB8; |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
4626
|
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 NAME |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
C - parse and format IRC messages using C |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=head1 TAGS |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
This module provides the following tags, conforming to the |
25
|
|
|
|
|
|
|
L API specification. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head2 bold, under, italic, reverse |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Boolean values indicating bold, underline, italics, or reverse-video. |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head2 fg, bg |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
L objects encoding the color. These will likely be instances |
34
|
|
|
|
|
|
|
of L, unless a full RGB triplet colour code has been |
35
|
|
|
|
|
|
|
provided; in which case it will be an instance of L. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=cut |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# IRC [well, technically mIRC but other clients have adopted it] uses Ctrl |
40
|
|
|
|
|
|
|
# characters to toggle formatting |
41
|
|
|
|
|
|
|
# ^B = bold |
42
|
|
|
|
|
|
|
# ^U = underline |
43
|
|
|
|
|
|
|
# ^_ = underline |
44
|
|
|
|
|
|
|
# ^R = reverse or italic - we'll use italic |
45
|
|
|
|
|
|
|
# ^V = reverse |
46
|
|
|
|
|
|
|
# ^] = italics |
47
|
|
|
|
|
|
|
# ^O = reset |
48
|
|
|
|
|
|
|
# ^C = colour; followed by a code |
49
|
|
|
|
|
|
|
# ^C = reset colours |
50
|
|
|
|
|
|
|
# ^Cff = foreground |
51
|
|
|
|
|
|
|
# ^Cff,bb = background |
52
|
|
|
|
|
|
|
# |
53
|
|
|
|
|
|
|
# irssi uses the following |
54
|
|
|
|
|
|
|
# ^D$$ = foreground/background, in chr('0'+$colour), |
55
|
|
|
|
|
|
|
# ^Db = underline |
56
|
|
|
|
|
|
|
# ^Dc = bold |
57
|
|
|
|
|
|
|
# ^Dd = reverse or italic - we'll use italic |
58
|
|
|
|
|
|
|
# ^Dg = reset colours |
59
|
|
|
|
|
|
|
# |
60
|
|
|
|
|
|
|
# As a side effect we'll also strip all the other Ctrl chars |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# We'll also look for "poor-man's" highlighting |
63
|
|
|
|
|
|
|
# *bold* |
64
|
|
|
|
|
|
|
# _underline_ |
65
|
|
|
|
|
|
|
# /italic/ |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=head1 METHODS |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=cut |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head2 $st = String::Tagged::IRC->parse_irc( $raw, %opts ) |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
Parses a text string containing IRC formatting codes and returns a new |
74
|
|
|
|
|
|
|
C instance. |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
Takes the following named options: |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=over 8 |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item parse_plain_formatting => BOOL |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
If true, also parse "poor-man's" plain-text formatting of B<*bold*>, |
83
|
|
|
|
|
|
|
I and _underline_. In this case, formatting tags are added but the |
84
|
|
|
|
|
|
|
original text formatting is preserved. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=back |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=cut |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub _parse_colour_mirc |
91
|
|
|
|
|
|
|
{ |
92
|
3
|
|
|
3
|
|
10
|
shift; |
93
|
3
|
|
|
|
|
4
|
my ( $colcode ) = @_; |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
# RRGGBB hex triplet |
96
|
3
|
100
|
|
|
|
14
|
$colcode =~ m/^#([0-9a-f]{6})/i and |
97
|
|
|
|
|
|
|
return Convert::Color::RGB8->new( $1 ); |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# RGB hex triplet |
100
|
2
|
50
|
|
|
|
4
|
$colcode =~ m/^#([0-9a-f])([0-9a-f])([0-9a-f])/i and |
101
|
|
|
|
|
|
|
return Convert::Color::RGB8->new( "$1$1$2$2$3$3" ); |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
# mIRC index |
104
|
2
|
50
|
33
|
|
|
24
|
$colcode =~ m/^(\d\d?)/ and $1 < 16 and |
105
|
|
|
|
|
|
|
return Convert::Color::mIRC->new( $1 ); |
106
|
|
|
|
|
|
|
|
107
|
0
|
|
|
|
|
0
|
return undef; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
my @termcolours = |
111
|
|
|
|
|
|
|
map { chomp; Convert::Color::RGB8->new( $_ ) } ; |
112
|
|
|
|
|
|
|
close DATA; |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
sub _parse_colour_ansiterm |
115
|
|
|
|
|
|
|
{ |
116
|
3
|
|
|
3
|
|
3
|
shift; |
117
|
3
|
|
|
|
|
20
|
my ( $idx ) = @_; |
118
|
|
|
|
|
|
|
|
119
|
3
|
50
|
33
|
|
|
19
|
$idx >= 0 and $idx < @termcolours and |
120
|
|
|
|
|
|
|
return $termcolours[$idx]; |
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
0
|
return undef; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub parse_irc |
126
|
|
|
|
|
|
|
{ |
127
|
7
|
|
|
7
|
1
|
10719
|
my $class = shift; |
128
|
7
|
|
|
|
|
18
|
my ( $text, %opts ) = @_; |
129
|
|
|
|
|
|
|
|
130
|
7
|
|
|
|
|
34
|
my $self = $class->new( "" ); |
131
|
|
|
|
|
|
|
|
132
|
7
|
|
|
|
|
79
|
my %format; |
133
|
|
|
|
|
|
|
|
134
|
7
|
|
|
|
|
23
|
while( length $text ) { |
135
|
35
|
100
|
|
|
|
937
|
if( $text =~ s/^([\x00-\x1f])// ) { |
136
|
16
|
|
|
|
|
33
|
my $ctrl = chr(ord($1)+0x40); |
137
|
|
|
|
|
|
|
|
138
|
16
|
100
|
33
|
|
|
118
|
if( $ctrl eq "B" ) { |
|
|
50
|
66
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
139
|
2
|
100
|
|
|
|
8
|
$format{bold} ? delete $format{bold} : ( $format{bold} = 1 ); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
elsif( $ctrl eq "U" or $ctrl eq "_" ) { |
142
|
0
|
0
|
|
|
|
0
|
$format{under} ? delete $format{under} : ( $format{under} = 1 ); |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
elsif( $ctrl eq "R" or $ctrl eq "]" ) { |
145
|
2
|
100
|
|
|
|
6
|
$format{italic} ? delete $format{italic} : ( $format{italic} = 1 ); |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
elsif( $ctrl eq "V" ) { |
148
|
0
|
0
|
|
|
|
0
|
$format{reverse} ? delete $format{reverse} : ( $format{reverse} = 1 ); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
elsif( $ctrl eq "O" ) { |
151
|
0
|
|
|
|
|
0
|
undef %format; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
elsif( $ctrl eq "C" ) { |
154
|
4
|
|
|
|
|
11
|
my $colourre = qr/#[0-9a-f]{6}|#[0-9a-f]{3}|\d\d?/i; |
155
|
|
|
|
|
|
|
|
156
|
4
|
100
|
|
|
|
101
|
if( $text =~ s/^($colourre),($colourre)// ) { |
|
|
100
|
|
|
|
|
|
157
|
1
|
|
|
|
|
3
|
$format{fg} = $self->_parse_colour_mirc( $1 ); |
158
|
1
|
|
|
|
|
10
|
$format{bg} = $self->_parse_colour_mirc( $2 ); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
elsif( $text =~ s/^($colourre)// ) { |
161
|
1
|
|
|
|
|
5
|
$format{fg} = $self->_parse_colour_mirc( $1 ); |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
else { |
164
|
2
|
|
|
|
|
5
|
delete $format{fg}; |
165
|
2
|
|
|
|
|
13
|
delete $format{bg}; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
elsif( $ctrl eq "D" ) { |
169
|
8
|
50
|
|
|
|
35
|
if( $text =~ s/^b// ) { # underline |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
170
|
0
|
0
|
|
|
|
0
|
$format{under} ? delete $format{under} : ( $format{under} = 1 ); |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
elsif( $text =~ s/^c// ) { # bold |
173
|
2
|
100
|
|
|
|
9
|
$format{bold} ? delete $format{bold} : ( $format{bold} = 1 ); |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
elsif( $text =~ s/^d// ) { # revserse/italic |
176
|
2
|
100
|
|
|
|
7
|
$format{italic} ? delete $format{italic} : ( $format{italic} = 1 ); |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
elsif( $text =~ s/^g// ) { |
179
|
2
|
|
|
|
|
6
|
undef %format |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
else { |
182
|
2
|
|
|
|
|
6
|
$text =~ s/^(.)(.)//; |
183
|
2
|
|
|
|
|
4
|
my ( $fg, $bg ) = map { ord( $_ ) - ord('0') } ( $1, $2 ); |
|
4
|
|
|
|
|
7
|
|
184
|
2
|
50
|
|
|
|
6
|
if( $fg > 0 ) { |
185
|
2
|
|
|
|
|
4
|
$format{fg} = $self->_parse_colour_ansiterm( $fg ); |
186
|
|
|
|
|
|
|
} |
187
|
2
|
100
|
|
|
|
5
|
if( $bg > 0 ) { |
188
|
1
|
|
|
|
|
3
|
$format{bg} = $self->_parse_colour_ansiterm( $bg ); |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
else { |
194
|
19
|
|
|
|
|
62
|
$text =~ s/^([^\x00-\x1f]+)//; |
195
|
19
|
|
|
|
|
35
|
my $piece = $1; |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# Now scan this piece for the text-based ones |
198
|
19
|
|
100
|
|
|
89
|
while( length $piece and $opts{parse_plain_formatting} ) { |
199
|
|
|
|
|
|
|
# Look behind/ahead asserts to ensure we don't capture e.g. |
200
|
|
|
|
|
|
|
# /usr/bin/perl by mistake |
201
|
2
|
50
|
|
|
|
74
|
$piece =~ s/^(.*?)(?
|
202
|
|
|
|
|
|
|
last; |
203
|
|
|
|
|
|
|
|
204
|
2
|
|
|
|
|
11
|
my ( $pre, $inner, $flag ) = ( $1, $2, $3 ); |
205
|
|
|
|
|
|
|
|
206
|
2
|
50
|
|
|
|
12
|
$self->append_tagged( $pre, %format ) if length $pre; |
207
|
|
|
|
|
|
|
|
208
|
2
|
|
|
|
|
130
|
my %innerformat = %format; |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
$innerformat{ |
211
|
2
|
|
|
|
|
10
|
{ '*' => "bold", '_' => "under", '/' => "italic" }->{$flag} |
212
|
|
|
|
|
|
|
} = 1; |
213
|
|
|
|
|
|
|
|
214
|
2
|
|
|
|
|
9
|
$self->append_tagged( $inner, %innerformat ); |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
19
|
100
|
|
|
|
117
|
$self->append_tagged( $piece, %format ) if length $piece; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
7
|
|
|
|
|
133
|
return $self; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=head2 $raw = $st->build_irc |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
Returns a plain text string containing IRC formatting codes built from the |
227
|
|
|
|
|
|
|
given instance. When outputting a colour index, this method always outputs it |
228
|
|
|
|
|
|
|
as a two-digit number, to avoid parsing ambiguity if the coloured text starts |
229
|
|
|
|
|
|
|
with a digit. |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
Currently this will only output F-style formatting, not F-style. |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Takes the following options: |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
=over 8 |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=item default_fg => INT |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Default foreground colour to emit for extents that have only the C tag |
240
|
|
|
|
|
|
|
set. This is required because F formatting codes cannot set just the |
241
|
|
|
|
|
|
|
background colour without setting the foreground as well. |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=back |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
=cut |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub build_irc |
248
|
|
|
|
|
|
|
{ |
249
|
3
|
|
|
3
|
1
|
1682
|
my $self = shift; |
250
|
3
|
|
|
|
|
38
|
my %opts = @_; |
251
|
|
|
|
|
|
|
|
252
|
3
|
|
50
|
|
|
15
|
my $default_fg = $opts{default_fg} // 0; |
253
|
|
|
|
|
|
|
|
254
|
3
|
|
|
|
|
5
|
my $ret = ""; |
255
|
3
|
|
|
|
|
3
|
my %formats; |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
$self->iter_extents_nooverlap( sub { |
258
|
9
|
|
|
9
|
|
248
|
my ( $extent, %tags ) = @_; |
259
|
|
|
|
|
|
|
|
260
|
9
|
100
|
|
|
|
21
|
$ret .= "\cB" if !$formats{bold} != !$tags{bold}; |
261
|
9
|
50
|
|
|
|
16
|
$ret .= "\c_" if !$formats{under} != !$tags{under}; |
262
|
9
|
100
|
|
|
|
17
|
$ret .= "\c]" if !$formats{italic} != !$tags{italic}; |
263
|
9
|
50
|
|
|
|
16
|
$ret .= "\cV" if !$formats{reverse} != !$tags{reverse}; |
264
|
9
|
|
|
|
|
30
|
$formats{$_} = $tags{$_} for qw( bold under italic reverse ); |
265
|
|
|
|
|
|
|
|
266
|
9
|
100
|
|
|
|
27
|
my $fg = $tags{fg} ? $tags{fg}->as_mirc->index : undef; |
267
|
9
|
100
|
|
|
|
11416
|
my $bg = $tags{bg} ? $tags{bg}->as_mirc->index : undef; |
268
|
|
|
|
|
|
|
|
269
|
9
|
100
|
100
|
|
|
777
|
if( ( $fg//'' ) ne ( $formats{fg}//'' ) or ( $bg//'' ) ne ( $formats{bg}//'' ) ) { |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
270
|
3
|
100
|
|
|
|
6
|
if( defined $bg ) { |
|
|
100
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# Can't set just bg alone, so if fg isn't defined, use the default |
272
|
1
|
|
33
|
|
|
6
|
$fg //= $default_fg; |
273
|
|
|
|
|
|
|
|
274
|
1
|
|
|
|
|
3
|
$ret .= sprintf "\cC%02d,%02d", $fg, $bg; |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
elsif( defined $fg ) { |
277
|
1
|
|
|
|
|
5
|
$ret .= sprintf "\cC%02d", $fg; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
else { |
280
|
1
|
|
|
|
|
2
|
$ret .= "\cC"; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
9
|
|
|
|
|
8
|
$formats{fg} = $fg; |
285
|
9
|
|
|
|
|
10
|
$formats{bg} = $bg; |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# TODO: colours |
288
|
|
|
|
|
|
|
|
289
|
9
|
|
|
|
|
20
|
$ret .= $extent->plain_substr; |
290
|
3
|
|
|
|
|
25
|
}); |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# Be polite and reset colours at least |
293
|
3
|
100
|
66
|
|
|
92
|
$ret .= "\cC" if defined $formats{fg} or defined $formats{bg}; |
294
|
|
|
|
|
|
|
|
295
|
3
|
|
|
|
|
16
|
return $ret; |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
sub new_from_formatted |
299
|
|
|
|
|
|
|
{ |
300
|
0
|
|
|
0
|
0
|
|
my $class = shift; |
301
|
0
|
|
|
|
|
|
my ( $orig ) = @_; |
302
|
|
|
|
|
|
|
|
303
|
0
|
|
|
|
|
|
return $class->clone( $orig, |
304
|
|
|
|
|
|
|
only_tags => [qw( bold under italic reverse fg bg )] |
305
|
|
|
|
|
|
|
); |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
sub as_formatted |
309
|
|
|
|
|
|
|
{ |
310
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
311
|
0
|
|
|
|
|
|
return $self; |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
=head1 TODO |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
=over 4 |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=item * |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Define a nicer way to do the ANSI terminal colour space of F-style |
321
|
|
|
|
|
|
|
formatting codes. |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=back |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
=head1 AUTHOR |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
Paul Evans |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=cut |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
0x55AA; |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# Palette used for irssi->RGB8 conversion |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
__DATA__ |