line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::Colorist::Colorizer; |
2
|
|
|
|
|
|
|
$App::Colorist::Colorizer::VERSION = '0.142540'; |
3
|
1
|
|
|
1
|
|
6
|
use Moose; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
17
|
|
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
6345
|
use Carp; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
83
|
|
6
|
1
|
|
|
1
|
|
6
|
use IO::Handle; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
35
|
|
7
|
1
|
|
|
1
|
|
1901
|
use IO::Select; |
|
1
|
|
|
|
|
1958
|
|
|
1
|
|
|
|
|
66
|
|
8
|
1
|
|
|
1
|
|
1106
|
use POSIX; |
|
1
|
|
|
|
|
8447
|
|
|
1
|
|
|
|
|
9
|
|
9
|
1
|
|
|
1
|
|
4016
|
use Readonly; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
use Scalar::Util qw( refaddr ); |
11
|
|
|
|
|
|
|
use YAML; |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# ABSTRACT: the brain behind App::Colorist |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
has configuration => ( |
17
|
|
|
|
|
|
|
is => 'ro', |
18
|
|
|
|
|
|
|
isa => 'Str', |
19
|
|
|
|
|
|
|
required => 1, |
20
|
|
|
|
|
|
|
); |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
has ruleset => ( |
24
|
|
|
|
|
|
|
is => 'ro', |
25
|
|
|
|
|
|
|
isa => 'Str', |
26
|
|
|
|
|
|
|
required => 1, |
27
|
|
|
|
|
|
|
default => 'rules', |
28
|
|
|
|
|
|
|
); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
has colorset => ( |
32
|
|
|
|
|
|
|
is => 'ro', |
33
|
|
|
|
|
|
|
isa => 'Str', |
34
|
|
|
|
|
|
|
required => 1, |
35
|
|
|
|
|
|
|
default => 'colors', |
36
|
|
|
|
|
|
|
); |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
has include => ( |
40
|
|
|
|
|
|
|
is => 'ro', |
41
|
|
|
|
|
|
|
isa => 'ArrayRef', |
42
|
|
|
|
|
|
|
traits => [ 'Array' ], |
43
|
|
|
|
|
|
|
required => 1, |
44
|
|
|
|
|
|
|
default => sub { [] }, |
45
|
|
|
|
|
|
|
handles => { |
46
|
|
|
|
|
|
|
'include_paths' => 'elements', |
47
|
|
|
|
|
|
|
}, |
48
|
|
|
|
|
|
|
); |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
has debug => ( |
52
|
|
|
|
|
|
|
is => 'ro', |
53
|
|
|
|
|
|
|
isa => 'Bool', |
54
|
|
|
|
|
|
|
required => 1, |
55
|
|
|
|
|
|
|
default => 0, |
56
|
|
|
|
|
|
|
); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
has inputs => ( |
60
|
|
|
|
|
|
|
is => 'ro', |
61
|
|
|
|
|
|
|
isa => 'ArrayRef', |
62
|
|
|
|
|
|
|
lazy_build => 1, |
63
|
|
|
|
|
|
|
traits => [ 'Array' ], |
64
|
|
|
|
|
|
|
handles => { |
65
|
|
|
|
|
|
|
all_inputs => 'elements', |
66
|
|
|
|
|
|
|
}, |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub _build_inputs { [ \*ARGV ] } |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
has selected_inputs => ( |
73
|
|
|
|
|
|
|
is => 'ro', |
74
|
|
|
|
|
|
|
isa => 'IO::Select', |
75
|
|
|
|
|
|
|
lazy_build => 1, |
76
|
|
|
|
|
|
|
); |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
sub _build_selected_inputs { |
79
|
|
|
|
|
|
|
my $self = shift; |
80
|
|
|
|
|
|
|
my $s = IO::Select->new; |
81
|
|
|
|
|
|
|
$s->add($self->all_inputs); |
82
|
|
|
|
|
|
|
return $s; |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
has input_buffers => ( |
87
|
|
|
|
|
|
|
is => 'ro', |
88
|
|
|
|
|
|
|
isa => 'HashRef', |
89
|
|
|
|
|
|
|
lazy_build => 1, |
90
|
|
|
|
|
|
|
traits => [ 'Hash' ], |
91
|
|
|
|
|
|
|
handles => { |
92
|
|
|
|
|
|
|
input_buffer_keys => 'keys', |
93
|
|
|
|
|
|
|
get_input_buffer => 'get', |
94
|
|
|
|
|
|
|
set_input_buffer => 'set', |
95
|
|
|
|
|
|
|
}, |
96
|
|
|
|
|
|
|
); |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
sub _build_input_buffers { +{} } |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
has output => ( |
102
|
|
|
|
|
|
|
is => 'ro', |
103
|
|
|
|
|
|
|
lazy_build => 1, |
104
|
|
|
|
|
|
|
); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
sub _build_output { \*STDOUT } |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
has search_path => ( |
110
|
|
|
|
|
|
|
is => 'ro', |
111
|
|
|
|
|
|
|
isa => 'ArrayRef', |
112
|
|
|
|
|
|
|
lazy_build => 1, |
113
|
|
|
|
|
|
|
traits => [ 'Array' ], |
114
|
|
|
|
|
|
|
handles => { |
115
|
|
|
|
|
|
|
all_search_paths => 'elements', |
116
|
|
|
|
|
|
|
first_path_that => 'first', |
117
|
|
|
|
|
|
|
}, |
118
|
|
|
|
|
|
|
); |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
sub _build_search_path { |
121
|
|
|
|
|
|
|
my $self = shift; |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
return [ |
124
|
|
|
|
|
|
|
$self->include_paths, |
125
|
|
|
|
|
|
|
(grep { $_ } split /:/, ($ENV{COLORIST_CONFIG}||'')), |
126
|
|
|
|
|
|
|
"$ENV{HOME}/.colorist", |
127
|
|
|
|
|
|
|
'/etc/colorist', |
128
|
|
|
|
|
|
|
]; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
has ruleset_file => ( |
133
|
|
|
|
|
|
|
is => 'ro', |
134
|
|
|
|
|
|
|
isa => 'Str', |
135
|
|
|
|
|
|
|
lazy_build => 1, |
136
|
|
|
|
|
|
|
); |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub _build_ruleset_file { |
139
|
|
|
|
|
|
|
my $self = shift; |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
my $config = $self->configuration; |
142
|
|
|
|
|
|
|
my $ruleset = $self->ruleset; |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
my $path = $self->first_path_that(sub { |
145
|
|
|
|
|
|
|
return 0 unless -d "$_/$config"; |
146
|
|
|
|
|
|
|
return 1 if -f "$_/$config/$ruleset.pl"; |
147
|
|
|
|
|
|
|
return 0; |
148
|
|
|
|
|
|
|
}); |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
croak(qq[Unable to locate rules "$ruleset" in paths: ], join(' ', $self->all_search_paths)) |
151
|
|
|
|
|
|
|
unless defined $path; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
return "$path/$config/$ruleset.pl"; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
has colorset_file => ( |
158
|
|
|
|
|
|
|
is => 'ro', |
159
|
|
|
|
|
|
|
isa => 'Str', |
160
|
|
|
|
|
|
|
lazy_build => 1, |
161
|
|
|
|
|
|
|
); |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
sub _build_colorset_file { |
164
|
|
|
|
|
|
|
my $self = shift; |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
my $config = $self->configuration; |
167
|
|
|
|
|
|
|
my $colorset = $self->colorset; |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
my $path = $self->first_path_that(sub { |
170
|
|
|
|
|
|
|
return 0 unless -d "$_/$config"; |
171
|
|
|
|
|
|
|
return 1 if -f "$_/$config/$colorset.yml"; |
172
|
|
|
|
|
|
|
return 0; |
173
|
|
|
|
|
|
|
}); |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
croak(qq[Unable to locate colors "$colorset" in paths: ], join(' ', $self->all_search_paths)) |
176
|
|
|
|
|
|
|
unless defined $path; |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
return "$path/$config/$colorset.yml"; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
has colors_mtime => ( |
183
|
|
|
|
|
|
|
is => 'rw', |
184
|
|
|
|
|
|
|
isa => 'Int', |
185
|
|
|
|
|
|
|
default => 0, |
186
|
|
|
|
|
|
|
); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
has colors => ( |
190
|
|
|
|
|
|
|
is => 'rw', |
191
|
|
|
|
|
|
|
isa => 'HashRef', |
192
|
|
|
|
|
|
|
trigger => sub { |
193
|
|
|
|
|
|
|
my $self = shift; |
194
|
|
|
|
|
|
|
$self->colors_mtime( (stat $self->colorset_file)[9] ) |
195
|
|
|
|
|
|
|
}, |
196
|
|
|
|
|
|
|
); |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
has rules_mtime => ( |
200
|
|
|
|
|
|
|
is => 'rw', |
201
|
|
|
|
|
|
|
isa => 'Int', |
202
|
|
|
|
|
|
|
default => 0, |
203
|
|
|
|
|
|
|
); |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
has rules => ( |
207
|
|
|
|
|
|
|
is => 'rw', |
208
|
|
|
|
|
|
|
isa => 'ArrayRef', |
209
|
|
|
|
|
|
|
traits => [ 'Array' ], |
210
|
|
|
|
|
|
|
trigger => sub { |
211
|
|
|
|
|
|
|
my $self = shift; |
212
|
|
|
|
|
|
|
$self->rules_mtime( (stat $self->ruleset_file)[9] ) |
213
|
|
|
|
|
|
|
}, |
214
|
|
|
|
|
|
|
handles => { |
215
|
|
|
|
|
|
|
rule_pairs => [ 'natatime', 2 ], |
216
|
|
|
|
|
|
|
}, |
217
|
|
|
|
|
|
|
); |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub load_colorset_file { |
221
|
|
|
|
|
|
|
my $self = shift; |
222
|
|
|
|
|
|
|
return YAML::LoadFile($self->colorset_file); |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub refresh_colorset_file { |
227
|
|
|
|
|
|
|
my $self = shift; |
228
|
|
|
|
|
|
|
if ( (stat $self->colorset_file)[9] > $self->colors_mtime ) { |
229
|
|
|
|
|
|
|
$self->colors( $self->load_colorset_file ); |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub load_ruleset_file { |
235
|
|
|
|
|
|
|
my $self = shift; |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
my $ruleset_file = $self->ruleset_file; |
238
|
|
|
|
|
|
|
my $rules; |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
{ |
241
|
|
|
|
|
|
|
package |
242
|
|
|
|
|
|
|
ruleset; |
243
|
|
|
|
|
|
|
use App::Colorist::Ruleset; |
244
|
|
|
|
|
|
|
$rules = do "$ruleset_file" |
245
|
|
|
|
|
|
|
or Carp::croak(qq[Failed to read rule set "$ruleset_file": $@]); |
246
|
|
|
|
|
|
|
push @$rules, qr{.*}, [ 'DEFAULT' ]; |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
return $rules; |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub refresh_ruleset_file { |
254
|
|
|
|
|
|
|
my $self = shift; |
255
|
|
|
|
|
|
|
if ( (stat $self->ruleset_file)[9] > $self->rules_mtime ) { |
256
|
|
|
|
|
|
|
$self->rules( $self->load_ruleset_file ); |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Readonly my %color_names => ( |
261
|
|
|
|
|
|
|
black => 0, gray => 8, |
262
|
|
|
|
|
|
|
maroon => 1, red => 9, |
263
|
|
|
|
|
|
|
green => 2, lime => 10, |
264
|
|
|
|
|
|
|
olive => 3, yellow => 11, |
265
|
|
|
|
|
|
|
navy => 4, blue => 12, |
266
|
|
|
|
|
|
|
purple => 5, fuschia => 13, |
267
|
|
|
|
|
|
|
teal => 6, aqua => 14, |
268
|
|
|
|
|
|
|
silver => 7, white => 15, |
269
|
|
|
|
|
|
|
map { ($_ => $_) } (0 .. 255), |
270
|
|
|
|
|
|
|
); |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
sub print_reset_line { |
274
|
|
|
|
|
|
|
my $self = shift; |
275
|
|
|
|
|
|
|
my $fh = $self->output; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
if ($self->debug) { |
278
|
|
|
|
|
|
|
$fh->print("{reset}"); |
279
|
|
|
|
|
|
|
return; |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
$fh->print("\e[0m"); |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub get_fg { |
287
|
|
|
|
|
|
|
my ($self, $fg) = @_; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
return '' unless defined $fg; |
290
|
|
|
|
|
|
|
if ($self->debug) { |
291
|
|
|
|
|
|
|
return "{$fg}"; |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
else { |
294
|
|
|
|
|
|
|
return sprintf "\e[38;5;%03dm", $fg; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
sub get_bg { |
300
|
|
|
|
|
|
|
my ($self, $bg) = @_; |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
return '' unless defined $bg; |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
if ($self->debug) { |
305
|
|
|
|
|
|
|
return "{$bg}"; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
else { |
308
|
|
|
|
|
|
|
return sprintf "\e[48;5;%03dm", $bg; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
sub gray { |
314
|
|
|
|
|
|
|
my ($self, $offset) = @_; |
315
|
|
|
|
|
|
|
return 232 + $offset; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub rgb { |
320
|
|
|
|
|
|
|
my ($self, $r, $g, $b) = @_; |
321
|
|
|
|
|
|
|
return 16 + $r*36 + $g*6 + $b; |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
sub eval_color { |
326
|
|
|
|
|
|
|
my ($self, $c) = @_; |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
return !defined($c) ? undef |
329
|
|
|
|
|
|
|
: !ref($c) ? $color_names{$c} |
330
|
|
|
|
|
|
|
: @{$c} == 1 ? gray(@{$c}) |
331
|
|
|
|
|
|
|
: @{$c} == 3 ? rgb(@{$c}) |
332
|
|
|
|
|
|
|
: croak("unknown color type"); |
333
|
|
|
|
|
|
|
} |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub fg { |
337
|
|
|
|
|
|
|
my ($self, $c) = @_; |
338
|
|
|
|
|
|
|
$self->get_fg($self->eval_color($c)); |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
sub bg($) { |
343
|
|
|
|
|
|
|
my ($self, $c) = @_; |
344
|
|
|
|
|
|
|
$self->get_bg($self->eval_color($c)); |
345
|
|
|
|
|
|
|
} |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
sub c { |
349
|
|
|
|
|
|
|
my ($self, $n) = @_; |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
my $c = $self->colors->{$n}; |
352
|
|
|
|
|
|
|
return unless defined $c; |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
my ($fg, $bg); |
355
|
|
|
|
|
|
|
if (ref $c eq 'HASH') { |
356
|
|
|
|
|
|
|
$fg = $c->{fg}; |
357
|
|
|
|
|
|
|
$bg = $c->{bg}; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
else { |
360
|
|
|
|
|
|
|
$fg = $c; |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
return $self->fg($fg).$self->bg($bg); |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
sub run { |
368
|
|
|
|
|
|
|
my $self = shift; |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
$self->loop_and_colorize; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
sub _split { |
375
|
|
|
|
|
|
|
my ($line) = @_; |
376
|
|
|
|
|
|
|
return split /^/, $line, 2; |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
sub readline { |
380
|
|
|
|
|
|
|
my ($self) = @_; |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
my $s = $self->selected_inputs; |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# Empty pending buffers first |
385
|
|
|
|
|
|
|
for my $key ($self->input_buffer_keys) { |
386
|
|
|
|
|
|
|
my $buffer = $self->get_input_buffer($key); |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
if (defined $buffer && $buffer =~ /\n/) { |
389
|
|
|
|
|
|
|
my ($first_line, $rest) = _split($buffer); |
390
|
|
|
|
|
|
|
$self->set_input_buffer($key, $rest); |
391
|
|
|
|
|
|
|
return $first_line; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
# We will keep trying this until we get a full line |
396
|
|
|
|
|
|
|
while (1) { |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
# Quit if we've run out of handles |
399
|
|
|
|
|
|
|
return unless $s->count > 0; |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# Otherwise, block until we have something to read |
402
|
|
|
|
|
|
|
my @ready = $s->can_read; |
403
|
|
|
|
|
|
|
for my $fh (@ready) { |
404
|
|
|
|
|
|
|
$fh->blocking(0); |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
# Start with the existing buffer |
407
|
|
|
|
|
|
|
my $line = $self->get_input_buffer(refaddr($fh)); |
408
|
|
|
|
|
|
|
$line = '' unless defined $line; |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
# Read it until we run out of input or until we hit at least one newline |
411
|
|
|
|
|
|
|
my ($eof, $buffer); |
412
|
|
|
|
|
|
|
do { |
413
|
|
|
|
|
|
|
$eof = sysread($fh, $buffer, 1024); |
414
|
|
|
|
|
|
|
if (not defined $eof) { |
415
|
|
|
|
|
|
|
if ($! == POSIX::EAGAIN) { |
416
|
|
|
|
|
|
|
select undef, undef, undef, 0.1; |
417
|
|
|
|
|
|
|
next; |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
else { |
420
|
|
|
|
|
|
|
croak("Error while reading handle: $!"); |
421
|
|
|
|
|
|
|
} |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
$line .= $buffer; |
424
|
|
|
|
|
|
|
} while ($eof != 0 && $line !~ /\n/); |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
$s->remove($fh) if $eof == 0; |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# If we got a newline, return the first line and buffer the rest |
429
|
|
|
|
|
|
|
if ($line =~ /\n/) { |
430
|
|
|
|
|
|
|
my ($first_line, $rest) = _split($line); |
431
|
|
|
|
|
|
|
$self->set_input_buffer(refaddr($fh), $rest); |
432
|
|
|
|
|
|
|
return $first_line; |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
# Otherwise, we got nothing, buffer all of it and keep going |
436
|
|
|
|
|
|
|
else { |
437
|
|
|
|
|
|
|
$self->set_input_buffer(refaddr($fh), $line); |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# Guess we will try the next ready file handle |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# Guess we'll go around again and wait for ready buffers again |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
sub loop_and_colorize { |
449
|
|
|
|
|
|
|
my $self = shift; |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
while (my $line = $self->readline) { |
452
|
|
|
|
|
|
|
$self->refresh_ruleset_file; |
453
|
|
|
|
|
|
|
$self->refresh_colorset_file; |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
$self->colorize($line); |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
sub colorize { |
461
|
|
|
|
|
|
|
my ($self, $line) = @_; |
462
|
|
|
|
|
|
|
local $_ = $line; |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
my $fh = $self->output; |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
my $iter = $self->rule_pairs; |
467
|
|
|
|
|
|
|
RULE: while (my ($rule, $names) = $iter->()) { |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
if (/^$rule$/) { |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
# This sort is a little complex, so here's the explanation: |
472
|
|
|
|
|
|
|
# |
473
|
|
|
|
|
|
|
# We want to keep the parenthetical nesting in the correct order. |
474
|
|
|
|
|
|
|
# This is easy when the parenthesis is separated by index. This is |
475
|
|
|
|
|
|
|
# not easy otherwise. Here are some sample cases to explain: |
476
|
|
|
|
|
|
|
# |
477
|
|
|
|
|
|
|
# a(b)c - we can sort just by string position |
478
|
|
|
|
|
|
|
# a(b(c - DITTO |
479
|
|
|
|
|
|
|
# a)b)c - DITTO |
480
|
|
|
|
|
|
|
# a)b(c - DITTO |
481
|
|
|
|
|
|
|
# |
482
|
|
|
|
|
|
|
# Hard cases: |
483
|
|
|
|
|
|
|
# |
484
|
|
|
|
|
|
|
# 11 <--- indexes in @- and @+ |
485
|
|
|
|
|
|
|
# a()b - sorting by group index order, ascending works |
486
|
|
|
|
|
|
|
# XY <--- starting parenthesis = X, ending parenthesis = Y |
487
|
|
|
|
|
|
|
# |
488
|
|
|
|
|
|
|
# 12 <--- indexes in @- and @+ |
489
|
|
|
|
|
|
|
# a((b - we need to sort by group index order, ascending |
490
|
|
|
|
|
|
|
# XX <--- starting parenthesis = X, ending parenthesis = Y |
491
|
|
|
|
|
|
|
# |
492
|
|
|
|
|
|
|
# * 21 <--- indexes in @- and @+ |
493
|
|
|
|
|
|
|
# * a))b - we need to sort by group index order, descending |
494
|
|
|
|
|
|
|
# * YY <--- starting parenthesis = X, ending parenthesis = Y |
495
|
|
|
|
|
|
|
# |
496
|
|
|
|
|
|
|
# 12 <--- indexes in @- and @+ |
497
|
|
|
|
|
|
|
# a)(b - we need to sort by group index order, ascending |
498
|
|
|
|
|
|
|
# YX <--- starting parenthesis = X, ending parenthesis = Y |
499
|
|
|
|
|
|
|
|
500
|
|
|
|
|
|
|
my @pos = sort { |
501
|
|
|
|
|
|
|
$a->[0] <=> $b->[0] # match index first |
502
|
|
|
|
|
|
|
|| ($a->[1] eq 'Y' and $b->[1] eq 'Y' ? $b->[2] <=> $a->[2] # X? name index (asc) |
503
|
|
|
|
|
|
|
: $a->[2] <=> $b->[2]) # Y? XY? YX? index (desc) |
504
|
|
|
|
|
|
|
} ( |
505
|
|
|
|
|
|
|
(map { [ ($-[$_] // 0), 'X', $_ ] } 0 .. $#- ), |
506
|
|
|
|
|
|
|
(map { [ ($+[$_] // 0), 'Y', $_ ] } 0 .. $#+ ), |
507
|
|
|
|
|
|
|
); |
508
|
|
|
|
|
|
|
@pos = ([ 0, 'X', undef ], @pos, [ length, 'Y', undef ]); |
509
|
|
|
|
|
|
|
#warn YAML::Dump(\@pos); |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
my $offset = 0; |
512
|
|
|
|
|
|
|
my @stack; |
513
|
|
|
|
|
|
|
for my $pos (@pos) { |
514
|
|
|
|
|
|
|
my ($i, $d, $n) = @$pos; |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
my $color; |
517
|
|
|
|
|
|
|
if ($d eq 'X') { |
518
|
|
|
|
|
|
|
if (defined $n) { |
519
|
|
|
|
|
|
|
$color = $self->c($names->[$n]); |
520
|
|
|
|
|
|
|
} |
521
|
|
|
|
|
|
|
else { |
522
|
|
|
|
|
|
|
$color = $self->c('DEFAULT'); |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
push @stack, $color; |
526
|
|
|
|
|
|
|
} |
527
|
|
|
|
|
|
|
else { |
528
|
|
|
|
|
|
|
pop @stack; |
529
|
|
|
|
|
|
|
if (@stack) { |
530
|
|
|
|
|
|
|
$color = $stack[-1]; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
else { |
533
|
|
|
|
|
|
|
$color = $self->c('DEFAULT'); |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
if (defined $color) { |
538
|
|
|
|
|
|
|
substr($_, $i + $offset, 0) = $color; |
539
|
|
|
|
|
|
|
$offset += length $color; |
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
last RULE; |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
} |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
$fh->print($_); |
548
|
|
|
|
|
|
|
$self->print_reset_line; |
549
|
|
|
|
|
|
|
} |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
__END__ |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
=pod |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
=encoding UTF-8 |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=head1 NAME |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
App::Colorist::Colorizer - the brain behind App::Colorist |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=head1 VERSION |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
version 0.142540 |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
=head1 SYNOPSIS |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
my $colorizer = App::Colorist::Colorizer->enw( |
570
|
|
|
|
|
|
|
commandset => 'mycommand', |
571
|
|
|
|
|
|
|
); |
572
|
|
|
|
|
|
|
$colorizer->run; |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
=head1 DESCRIPTION |
575
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
This is primarily engineered as a separate module to make testing easier. However, if you want to embed a colorizer in some other program for some reason or you want to extend colorizer, this provides the tools for that as well. This is why I decided to provide documentation for this module here. |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
If you do provide extensions, I would love to see them. Patches are welcome. |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
=head2 configuration |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
This is the name of the master configuration to use. This is usually the name of the command whose output you are colorizing. Each configuration must contain at least one ruleset and one colorset configuration. See L<App::Colorist/CONFIGURATION> for details on how this is used to locate the configuration files. |
585
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=head2 ruleset |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
This is the name of the rule set to use. See L<App::Colorist/CONFIGURATION> for how rule sets are defined and located. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=head2 colorset |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
This is the name of the color set to use. See L<App::Colorist/CONFIGURATION> for how color sets are defined and located. |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
=head2 include |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
This is an array of extra include paths to search when looking for colorist configuration files. |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
=head2 debug |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
This is mostly useful for testing the app itself. When set to a true value, the colors are not output but a numeric representation like "{12}" is output instead. |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
=head2 inputs |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
This is an array of file handles to use for input. A builder lazily sets this to an array containing only the C<ARGV> file handle by default. If more than one file handle is passed, this will capture output of all file handles and display from each as they come. |
605
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
=head2 selected_inputs |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
This is an L<IO::Select> built from the list of input file handles in L</inputs>. |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
=head2 input_buffers |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
This is an array of strings used as input buffers. This is used with the non-blocking I/O code to store any partially read lines encountered. |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
=head2 output |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
This is the fil ehandle to use for output. A builder lazily sets this to C<STDOUT> by default. |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
=head2 search_path |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
This contains the full search path. You do not normally want to set this yourself, but use L</include> instead. It is lazily instantiated to includ the values set in L</include>, the value of the C<COLORIST_CONFIG> environment variable, followed by F<~/.colorist> and finally F</etc/colorist>. |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=head2 ruleset_file |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
This is set to the name of the actual ruleset file found by searching L</search_paths> and L</ruleset>. |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
=head2 colorset_file |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
This is the actual colorset file found by searching L</search_paths> for C<colorset>. |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
=head2 colors_mtime |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
When the colorset file is loaded, this mtime is set to the current mtime of the file. Every time a line is colored it checks to see if the colorset file has changed and will reload it automatically if it has. |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
=head2 colors |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
This is the actual colorset configuration. It's a set of keys naming the various color names defined in the ruleset and the values are the color definitions. See L<App::Colorist/CONFIGURATION> for details. |
637
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
=head2 rules_mtime |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
Whenever the rules are loaded, this mtime is recorded. If the file changes, the rules are reloaded. |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=head2 rules |
643
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
This contains the actual rules. This is an array where the even number indices point to a regular expression used to match lines and group submatches. The odd indices contain an array of names matching the overall match and the group matches, which are looked up in the L</colors> configuration. See L<App::Colorist/CONFIGURATION> for details. |
645
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=head1 METHODS |
647
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
=head2 load_colorset_file |
649
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
Loads the colorset configuration using L<YAML>. |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
=head2 refresh_colorset_file |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
Checks to see if the L</colors> need to be reloaded and calls L</load_colorset_file> if they do. |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
=head2 load_ruleset_file |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
Reads in the ruleset configuration using a Perl C<do>. |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
=head2 refresh_ruleset_file |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
Checks to see if the ruleset file has changed since it's last load and calls L<load_ruleset_file> to reload the configuration if it has. |
663
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
=head2 print_reset_line |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
Prints the escape code to reset everything to the terminal default. |
667
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
=head2 get_fg |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
my $code = $c->get_fg(10); |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
Returns the escape code required to change the foreground color to the given color number. |
673
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
=head2 get_bg |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
my $code = $self->get_bg(10); |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
Returns the escape code that will change the background color to the given color code. |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
=head2 gray |
681
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
my $number = $c->gray(10); |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
Given a number identifying the desired shade of gray, returns that color number. Only works on terminals supporting 256 colors. |
685
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
=head2 rgb |
687
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
my $number = $c->rgb(1, 3, 4); |
689
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
Given 3 numbers identifying the desired RGB color cube, returns that color number. Only works on terminals supporting 256 colors. |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
=head2 eval_color |
693
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
my $number = $c->eval_color('blue'); |
695
|
|
|
|
|
|
|
my $number = $c->eval_color(10); |
696
|
|
|
|
|
|
|
my $number = $c->eval_color([ 8 ]); |
697
|
|
|
|
|
|
|
my $number = $c->eval_color([ 1, 2, 3 ]); |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
Given one of the possible color configuration types from the color set configuration, returns a color number for it. |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
=head2 fg |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
my $code = $c->fg('blue'); |
704
|
|
|
|
|
|
|
my $code = $c->fg(10); |
705
|
|
|
|
|
|
|
my $code = $c->fg([ 8 ]); |
706
|
|
|
|
|
|
|
my $code = $c->fg([ 1, 2, 3 ]); |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
Returns the escape code for changing the foreground color to the given color identifier. |
709
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
=head2 bg |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
my $code = $c->bg('blue'); |
713
|
|
|
|
|
|
|
my $code = $c->bg(10); |
714
|
|
|
|
|
|
|
my $code = $c->bg([ 8 ]); |
715
|
|
|
|
|
|
|
my $code = $c->bg([ 1, 2, 3 ]); |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
Returns the escape code for changing the background color to the given color identifier. |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
=head2 c |
720
|
|
|
|
|
|
|
|
721
|
|
|
|
|
|
|
my $code = $c->c('rufus'); |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
Given the name of a color defined in the colorset, returns the escape codes defined for that color to change the background and foreground as configured. |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
=head2 run |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
Runs the colorization process to colorize input and send that to the output. |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
=head2 readline |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
Given an L<IO::Select> object, returns the first line it finds from the selected |
732
|
|
|
|
|
|
|
file handles. This handles all buffering on the file handles and blocks until a |
733
|
|
|
|
|
|
|
complete line is available. It returns only the first line that comes available. |
734
|
|
|
|
|
|
|
It makes no guarantees about the order the file handles will be read or |
735
|
|
|
|
|
|
|
processed. It does try to conserve memory and keep the buffers relatively small. |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
=head2 loop_and_colorize |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
Reads each line of input, reloads the ruleset and colorset configuration if they have changed, and calls L</colorize> to add color to the input and send it to the output. |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
=head2 colorize |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
$c->colorize('some input'); |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
Given a line of input, this method matches the ruleset rules agains the line until it finds a match. It then applies all the colors for the line and groups defined in the colorset and outputs that line to the output file handle. |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
=head1 AUTHOR |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
Andrew Sterling Hanenkamp <hanenkamp@cpan.org> |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
This software is copyright (c) 2014 by Qubling Software LLC. |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
756
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
=cut |