line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::JobLog::Log::Format; |
2
|
|
|
|
|
|
|
$App::JobLog::Log::Format::VERSION = '1.039'; |
3
|
|
|
|
|
|
|
# ABSTRACT: pretty printer for log |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
1339
|
use Exporter 'import'; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
100
|
|
7
|
|
|
|
|
|
|
our @EXPORT_OK = qw( |
8
|
|
|
|
|
|
|
display |
9
|
|
|
|
|
|
|
duration |
10
|
|
|
|
|
|
|
single_interval |
11
|
|
|
|
|
|
|
summary |
12
|
|
|
|
|
|
|
wrap |
13
|
|
|
|
|
|
|
); |
14
|
|
|
|
|
|
|
|
15
|
2
|
|
|
2
|
|
10
|
use Modern::Perl; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
22
|
|
16
|
2
|
|
|
|
|
121
|
use App::JobLog::Config qw( |
17
|
|
|
|
|
|
|
day_length |
18
|
|
|
|
|
|
|
is_workday |
19
|
|
|
|
|
|
|
precision |
20
|
2
|
|
|
2
|
|
251
|
); |
|
2
|
|
|
|
|
4
|
|
21
|
2
|
|
|
2
|
|
10
|
use App::JobLog::Log::Synopsis qw(collect :merge); |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
340
|
|
22
|
2
|
|
|
2
|
|
1575
|
use Text::WrapI18N qw(); |
|
2
|
|
|
|
|
4931
|
|
|
2
|
|
|
|
|
51
|
|
23
|
2
|
|
|
2
|
|
1581
|
use App::JobLog::TimeGrammar qw(parse); |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
124
|
|
24
|
|
|
|
|
|
|
|
25
|
2
|
|
|
2
|
|
16
|
use constant TAG_COLUMN_LIMIT => 10; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
131
|
|
26
|
2
|
|
|
2
|
|
10
|
use constant MARGIN => 5; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
1723
|
|
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# minimum width of description column |
29
|
2
|
|
|
2
|
|
12
|
use constant MIN_WIDTH => 20; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
110
|
|
30
|
2
|
|
|
2
|
|
11
|
use constant DURATION_FORMAT => '%0.' . precision . 'f'; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
11
|
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
sub summary { |
34
|
15
|
|
|
15
|
0
|
407
|
my ( $phrase, $test, $hidden, $do_notes ) = @_; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
# we skip flex days if the events are at all filtered |
37
|
15
|
|
66
|
|
|
104
|
my $skip_flex = !$do_notes && ( $test || 0 ); |
38
|
15
|
|
100
|
197
|
|
129
|
$test //= sub { $_[0] }; |
|
197
|
|
|
|
|
779
|
|
39
|
15
|
|
|
|
|
123
|
my ( $start, $end ) = parse $phrase; |
40
|
15
|
|
|
|
|
75
|
my $show_year = $start->year < $end->year; |
41
|
15
|
100
|
|
|
|
136
|
unless ($skip_flex) { |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# if we are chopping off any of the first and last days we ignore flex time |
44
|
9
|
100
|
66
|
|
|
62
|
$skip_flex = 1 |
|
|
|
66
|
|
|
|
|
45
|
|
|
|
|
|
|
unless $do_notes || _break_of_dawn($start) && _witching_hour($end); |
46
|
|
|
|
|
|
|
} |
47
|
15
|
100
|
|
|
|
156
|
my $method = $do_notes ? 'find_notes' : 'find_events'; |
48
|
15
|
|
|
|
|
133
|
my $events = App::JobLog::Log->new->$method( $start, $end ); |
49
|
15
|
|
|
|
|
336
|
my @days = @{ _days( $start, $end, $skip_flex, $do_notes ) }; |
|
15
|
|
|
|
|
1794
|
|
50
|
|
|
|
|
|
|
my @periods = |
51
|
15
|
100
|
|
|
|
707
|
$hidden->{vacation} ? () : App::JobLog::Vacation->new->periods; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# drop the vacation days that can't be relevant |
54
|
15
|
100
|
|
|
|
75
|
unless ( $hidden->{vacation} ) { |
55
|
13
|
|
|
|
|
117
|
my $e = |
56
|
|
|
|
|
|
|
App::JobLog::Log::Event->new( |
57
|
|
|
|
|
|
|
App::JobLog::Log::Line->new( time => $start ) ); |
58
|
13
|
|
|
|
|
70
|
$e->end = $end; |
59
|
13
|
|
|
|
|
90
|
for ( my $i = 0 ; $i < @periods ; $i++ ) { |
60
|
0
|
|
|
|
|
0
|
my $p = $periods[$i]; |
61
|
0
|
0
|
0
|
|
|
0
|
if ( $skip_flex && $p->flex || !$p->conflicts($e) ) { |
|
|
|
0
|
|
|
|
|
62
|
0
|
|
|
|
|
0
|
splice @periods, $i, 1; |
63
|
0
|
|
|
|
|
0
|
$i--; |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# collect events into days |
69
|
15
|
|
|
|
|
28
|
my @gathered; |
70
|
15
|
|
|
|
|
45
|
for my $big_e (@$events) { |
71
|
203
|
|
|
|
|
755
|
for my $e ( $big_e->split_days ) { |
72
|
205
|
100
|
|
|
|
514
|
if ( $e = $test->($e) ) { |
73
|
203
|
|
100
|
|
|
932
|
push @gathered, shift @days |
74
|
|
|
|
|
|
|
while @days && $days[0]->end < $e->start; |
75
|
203
|
|
|
|
|
18421
|
for my $d (@days) { |
76
|
233
|
100
|
|
|
|
3492
|
if ( $e->intersects( $d->pseudo_event ) ) { |
77
|
200
|
|
|
|
|
17393
|
push @{ $d->events }, $e; |
|
200
|
|
|
|
|
575
|
|
78
|
200
|
|
|
|
|
793
|
last; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# I believe these is_open bits are mistaken |
82
|
|
|
|
|
|
|
# last if $e->is_open; |
83
|
33
|
50
|
|
|
|
3171
|
unless ($do_notes) { |
84
|
33
|
50
|
|
|
|
106
|
last if $d->start > $e->end; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
} |
90
|
15
|
|
|
|
|
179
|
unshift @days, @gathered; |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# add in vacation times |
93
|
15
|
|
|
|
|
42
|
for my $p (@periods) { |
94
|
0
|
|
|
|
|
0
|
for my $d (@days) { |
95
|
0
|
0
|
0
|
|
|
0
|
if ( is_workday( $d->start ) && $p->conflicts( $d->pseudo_event ) ) |
96
|
|
|
|
|
|
|
{ |
97
|
0
|
|
|
|
|
0
|
my $clone = $p->clone; |
98
|
0
|
0
|
|
|
|
0
|
if ( $clone->fixed ) { |
99
|
0
|
|
|
|
|
0
|
push @{ $d->events }, $clone->overlap( $d->start, $d->end ); |
|
0
|
|
|
|
|
0
|
|
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
else { |
102
|
0
|
|
|
|
|
0
|
$clone->start = $d->start->clone; |
103
|
0
|
0
|
|
|
|
0
|
if ( $clone->flex ) { |
104
|
0
|
|
|
|
|
0
|
$d->{deferred} = $clone; |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
else { |
107
|
0
|
|
|
|
|
0
|
$clone->end = |
108
|
|
|
|
|
|
|
$clone->start->clone->add( hours => day_length ); |
109
|
0
|
|
|
|
|
0
|
push @{ $d->vacation }, $clone; |
|
0
|
|
|
|
|
0
|
|
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# delete empty days |
117
|
15
|
|
|
|
|
65
|
for ( my $i = 0 ; $i < @days ; $i++ ) { |
118
|
11085
|
|
|
|
|
74822
|
my $d = $days[$i]; |
119
|
11085
|
100
|
100
|
|
|
29179
|
if ( $d->is_empty && !is_workday( $d->start ) ) { |
120
|
3145
|
|
|
|
|
23163
|
splice @days, $i, 1; |
121
|
3145
|
|
|
|
|
23471
|
$i--; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# fix deferred flex time and ensure events are chronologically ordered |
126
|
15
|
|
|
|
|
41
|
for my $d (@days) { |
127
|
7940
|
|
|
|
|
15458
|
my $flex = $d->{deferred}; |
128
|
7940
|
|
|
|
|
9215
|
my @events = @{ $d->events }; |
|
7940
|
|
|
|
|
19145
|
|
129
|
7940
|
50
|
|
|
|
16058
|
if ($flex) { |
130
|
0
|
|
|
|
|
0
|
delete $d->{deferred}; |
131
|
0
|
|
|
|
|
0
|
my $tr = $d->time_remaining; |
132
|
0
|
0
|
|
|
|
0
|
if ( $tr > 0 ) { |
133
|
0
|
|
|
|
|
0
|
$flex->end = $flex->start->clone->add( seconds => $tr ); |
134
|
0
|
|
|
|
|
0
|
push @events, $flex; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
} |
137
|
7940
|
100
|
|
|
|
19683
|
$d->{events} = [ sort { $a->cmp($b) } @events ] if @events > 1; |
|
155
|
|
|
|
|
399
|
|
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
15
|
|
|
|
|
395
|
return \@days, $show_year; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
# whether the date is the first moment in its day |
144
|
|
|
|
|
|
|
sub _break_of_dawn { |
145
|
7
|
|
|
7
|
|
14
|
my ($date) = @_; |
146
|
7
|
|
66
|
|
|
30
|
return $date->hour == 0 && $date->minute == 0 && $date->second == 0; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# whether the date is the last moment in its day |
150
|
|
|
|
|
|
|
sub _witching_hour { |
151
|
2
|
|
|
2
|
|
67
|
my ($date) = @_; |
152
|
2
|
|
33
|
|
|
14
|
return $date->hour == 23 && $date->minute == 59 && $date->second == 59; |
153
|
|
|
|
|
|
|
} |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# create a list of days about which we wish to collect information |
156
|
|
|
|
|
|
|
sub _days { |
157
|
15
|
|
|
15
|
|
41
|
my ( $start, $end, $skip_flex, $doing_notes ) = @_; |
158
|
15
|
|
|
|
|
25
|
my @days; |
159
|
15
|
|
|
|
|
27
|
my $b1 = $start; |
160
|
15
|
|
|
|
|
68
|
my $b2 = $start->clone->add( days => 1 )->truncate( to => 'day' ); |
161
|
15
|
|
|
|
|
23642
|
while ( $b2 < $end ) { |
162
|
11070
|
100
|
|
|
|
11496106
|
push @days, |
163
|
|
|
|
|
|
|
App::JobLog::Log::Day->new( |
164
|
|
|
|
|
|
|
start => $b1, |
165
|
|
|
|
|
|
|
end => $b2, |
166
|
|
|
|
|
|
|
skip_flex => $skip_flex, |
167
|
|
|
|
|
|
|
$doing_notes ? ( notes => 1 ) : (), |
168
|
|
|
|
|
|
|
); |
169
|
11070
|
|
|
|
|
22319
|
$b1 = $b2; |
170
|
11070
|
|
|
|
|
34932
|
$b2 = $b2->clone->add( days => 1 ); |
171
|
|
|
|
|
|
|
} |
172
|
15
|
100
|
|
|
|
10113
|
push @days, |
173
|
|
|
|
|
|
|
App::JobLog::Log::Day->new( |
174
|
|
|
|
|
|
|
start => $b1, |
175
|
|
|
|
|
|
|
end => $end, |
176
|
|
|
|
|
|
|
skip_flex => $skip_flex, |
177
|
|
|
|
|
|
|
$doing_notes ? ( notes => 1 ) : (), |
178
|
|
|
|
|
|
|
); |
179
|
15
|
|
|
|
|
3843
|
return \@days; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub display { |
184
|
15
|
|
|
15
|
1
|
313
|
my ( $days, $merge_level, $hidden, $screen_width, $show_year ) = @_; |
185
|
|
|
|
|
|
|
|
186
|
15
|
50
|
|
|
|
138
|
if (@$days) { |
187
|
15
|
|
|
|
|
319
|
collect $_, $merge_level for @$days; |
188
|
15
|
|
|
|
|
140
|
my @synopses = map { @{ $_->synopses } } @$days; |
|
7940
|
|
|
|
|
9353
|
|
|
7940
|
|
|
|
|
19672
|
|
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
my $columns = { |
191
|
|
|
|
|
|
|
time => single_interval($merge_level) && !$hidden->{time}, |
192
|
|
|
|
|
|
|
date => !$hidden->{date}, |
193
|
|
|
|
|
|
|
tags => !$hidden->{tags}, |
194
|
|
|
|
|
|
|
description => !$hidden->{description}, |
195
|
|
|
|
|
|
|
duration => !$hidden->{duration}, |
196
|
15
|
|
33
|
|
|
93
|
}; |
197
|
15
|
|
66
|
|
|
111
|
$show_year &&= $columns->{date}; |
198
|
15
|
|
|
|
|
90
|
my $format = _define_format( \@synopses, $columns, $screen_width ); |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
# keep track of various durations |
201
|
15
|
|
|
|
|
232
|
my $times = { |
202
|
|
|
|
|
|
|
total => 0, |
203
|
|
|
|
|
|
|
untagged => 0, |
204
|
|
|
|
|
|
|
expected => 0, |
205
|
|
|
|
|
|
|
vacation => 0, |
206
|
|
|
|
|
|
|
tags => {} |
207
|
|
|
|
|
|
|
}; |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
# display synopses and add up durations |
210
|
15
|
|
|
|
|
68
|
for my $d (@$days) { |
211
|
7940
|
|
|
|
|
22702
|
$d->times($times); |
212
|
7940
|
|
|
|
|
64603
|
$d->display( $format, $columns, $screen_width, $show_year ); |
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
|
215
|
15
|
100
|
|
|
|
261
|
unless ( $hidden->{totals} ) { |
216
|
|
|
|
|
|
|
my ( $m1, $m2 ) = |
217
|
13
|
|
|
|
|
66
|
( length 'TOTAL HOURS', length duration( $times->{total} ) ); |
218
|
13
|
|
|
|
|
45
|
my @keys = keys %{ $times->{tags} }; |
|
13
|
|
|
|
|
106
|
|
219
|
13
|
100
|
|
|
|
80
|
push @keys, 'UNTAGGED' if $times->{untagged}; |
220
|
13
|
50
|
|
|
|
62
|
push @keys, 'VACATION' if $times->{vacation}; |
221
|
13
|
|
|
|
|
34
|
for my $tag (@keys) { |
222
|
14
|
|
|
|
|
45
|
my $l = length $tag; |
223
|
14
|
50
|
|
|
|
66
|
$m1 = $l if $l > $m1; |
224
|
|
|
|
|
|
|
} |
225
|
13
|
|
|
|
|
80
|
$format = sprintf " %%-%ds %%%ds\n", $m1, $m2; |
226
|
13
|
|
|
|
|
68
|
printf $format, 'TOTAL HOURS', duration( $times->{total} ); |
227
|
|
|
|
|
|
|
printf $format, 'VACATION', duration( $times->{vacation} ) |
228
|
13
|
50
|
|
|
|
412
|
if $times->{vacation}; |
229
|
13
|
100
|
|
|
|
38
|
if ( %{ $times->{tags} } ) { |
|
13
|
|
|
|
|
126
|
|
230
|
|
|
|
|
|
|
printf $format, 'UNTAGGED', duration( $times->{untagged} ) |
231
|
11
|
50
|
|
|
|
61
|
if $times->{untagged}; |
232
|
11
|
|
|
|
|
52
|
for my $key ( sort keys %{ $times->{tags} } ) { |
|
11
|
|
|
|
|
70
|
|
233
|
12
|
|
|
|
|
65
|
my $d = $times->{tags}{$key}; |
234
|
12
|
|
|
|
|
52
|
printf $format, $key, duration($d); |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
else { |
240
|
0
|
|
|
|
|
0
|
say 'No events in interval specified.'; |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# generate printf format for synopses |
245
|
|
|
|
|
|
|
# returns format and wrap widths for tags and descriptions |
246
|
|
|
|
|
|
|
sub _define_format { |
247
|
15
|
|
|
15
|
|
34
|
my ( $synopses, $hash, $screen_width ) = @_; |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
#determine maximum width of each column |
250
|
15
|
|
|
|
|
34
|
my $widths; |
251
|
15
|
|
|
|
|
73
|
for my $s (@$synopses) { |
252
|
76
|
50
|
|
|
|
209
|
if ( $hash->{tags} ) { |
253
|
76
|
|
50
|
|
|
405
|
my $w1 = $hash->{widths}{tags} || 0; |
254
|
76
|
|
|
|
|
250
|
my $ts = $s->tag_string; |
255
|
76
|
50
|
66
|
|
|
335
|
if ( $screen_width > -1 && length $ts > TAG_COLUMN_LIMIT ) { |
256
|
0
|
|
|
|
|
0
|
my $wrapped = wrap( $ts, TAG_COLUMN_LIMIT ); |
257
|
0
|
|
|
|
|
0
|
$ts = ''; |
258
|
0
|
|
|
|
|
0
|
for my $line (@$wrapped) { |
259
|
0
|
0
|
|
|
|
0
|
$ts = $line if length $line > length $ts; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
} |
262
|
76
|
|
|
|
|
106
|
my $w2 = length $ts; |
263
|
76
|
100
|
|
|
|
235
|
$hash->{widths}{tags} = $w2 if $w2 > $w1; |
264
|
|
|
|
|
|
|
} |
265
|
76
|
50
|
|
|
|
190
|
if ( $hash->{time} ) { |
266
|
76
|
|
100
|
|
|
237
|
my $w1 = $hash->{widths}{time} || 0; |
267
|
76
|
|
|
|
|
241
|
my $w2 = length $s->time_fmt; |
268
|
76
|
100
|
|
|
|
331
|
$hash->{widths}{time} = $w2 if $w2 > $w1; |
269
|
|
|
|
|
|
|
} |
270
|
76
|
100
|
|
|
|
225
|
if ( $hash->{duration} ) { |
271
|
44
|
|
100
|
|
|
219
|
my $w1 = $hash->{widths}{duration} || 0; |
272
|
44
|
|
|
|
|
163
|
my $w2 = length duration( $s->duration ); |
273
|
44
|
100
|
|
|
|
202
|
$hash->{widths}{duration} = $w2 if $w2 > $w1; |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
} |
276
|
15
|
|
|
|
|
50
|
my $margins = 0; |
277
|
15
|
100
|
33
|
|
|
167
|
if ( $hash->{tags} && $hash->{widths}{tags} ) { |
278
|
11
|
|
|
|
|
32
|
$margins++; |
279
|
11
|
|
|
|
|
170
|
$hash->{formats}{tags} = sprintf '%%-%ds', $hash->{widths}{tags}; |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
# there seems to be a bug in Text::Wrap that requires tinkering with the column width |
282
|
11
|
|
|
|
|
62
|
$hash->{widths}{tags}++; |
283
|
|
|
|
|
|
|
} |
284
|
15
|
50
|
33
|
|
|
149
|
if ( $hash->{time} && $hash->{widths}{time} ) { |
285
|
15
|
|
|
|
|
33
|
$margins++; |
286
|
15
|
|
|
|
|
90
|
$hash->{formats}{time} = sprintf '%%%ds', $hash->{widths}{time}; |
287
|
|
|
|
|
|
|
} |
288
|
15
|
50
|
66
|
|
|
134
|
if ( $hash->{duration} && $hash->{widths}{duration} ) { |
289
|
13
|
|
|
|
|
23
|
$margins++; |
290
|
13
|
|
|
|
|
79
|
$hash->{formats}{duration} = sprintf '%%%ds', $hash->{widths}{duration}; |
291
|
|
|
|
|
|
|
} |
292
|
15
|
50
|
|
|
|
55
|
if ( $hash->{description} ) { |
293
|
15
|
100
|
|
|
|
64
|
if ( $screen_width == -1 ) { |
294
|
3
|
|
|
|
|
10
|
$hash->{formats}{description} = '%s'; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
else { |
297
|
12
|
|
|
|
|
29
|
$margins++; |
298
|
12
|
|
|
|
|
48
|
my $max_description = $screen_width; |
299
|
12
|
|
|
|
|
65
|
for my $col (qw(time duration tags)) { |
300
|
36
|
|
100
|
|
|
188
|
$max_description -= $hash->{widths}{$col} || 0; |
301
|
|
|
|
|
|
|
} |
302
|
12
|
|
|
|
|
53
|
$max_description -= $margins * 2; # left margins |
303
|
12
|
|
|
|
|
32
|
$max_description -= MARGIN; # margin on the right |
304
|
12
|
50
|
|
|
|
64
|
$max_description = MIN_WIDTH if $max_description < MIN_WIDTH; |
305
|
12
|
|
|
|
|
85
|
$hash->{widths}{description} = $max_description; |
306
|
12
|
|
|
|
|
94
|
$hash->{formats}{description} = sprintf '%%-%ds', $max_description; |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
|
310
|
15
|
|
|
|
|
59
|
my $format = ''; |
311
|
15
|
|
|
|
|
41
|
for my $col (qw(time duration tags description)) { |
312
|
60
|
|
|
|
|
119
|
my $f = $hash->{formats}{$col}; |
313
|
60
|
100
|
|
|
|
210
|
$format .= " $f" if $f; |
314
|
|
|
|
|
|
|
} |
315
|
15
|
|
|
|
|
50
|
return $format; |
316
|
|
|
|
|
|
|
} |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub wrap { |
320
|
24
|
|
|
24
|
1
|
76
|
my ( $text, $columns ) = @_; |
321
|
24
|
|
|
|
|
56
|
my @ar; |
322
|
24
|
|
|
|
|
106
|
eval { |
323
|
24
|
|
|
|
|
82
|
$Text::WrapI18N::columns = $columns; |
324
|
24
|
|
|
|
|
176
|
my $s = Text::WrapI18N::wrap( '', '', $text ); |
325
|
24
|
|
|
|
|
714692
|
@ar = $s =~ /^.*$/mg; |
326
|
|
|
|
|
|
|
}; |
327
|
24
|
50
|
|
|
|
232
|
return \@ar unless $@; |
328
|
0
|
|
|
|
|
0
|
return [$text]; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub single_interval { |
333
|
15
|
100
|
66
|
15
|
0
|
858
|
$_[0] == MERGE_ADJACENT |
334
|
|
|
|
|
|
|
|| $_[0] == MERGE_ADJACENT_SAME_TAGS |
335
|
|
|
|
|
|
|
|| $_[0] == MERGE_NONE; |
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
|
339
|
126
|
|
|
126
|
1
|
2461
|
sub duration { sprintf DURATION_FORMAT, $_[0] / ( 60 * 60 ) } |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
1; |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
__END__ |