File Coverage

blib/lib/App/JobLog/Log/Synopsis.pm
Criterion Covered Total %
statement 145 176 82.3
branch 26 38 68.4
condition 19 36 52.7
subroutine 26 29 89.6
pod 11 15 73.3
total 227 294 77.2


line stmt bran cond sub pod time code
1             package App::JobLog::Log::Synopsis;
2             $App::JobLog::Log::Synopsis::VERSION = '1.039';
3             # ABSTRACT: consolidates App::JobClock::Log::Event objects for display
4              
5              
6 2     2   1304 use Exporter 'import';
  2         4  
  2         172  
7             our @EXPORT_OK = qw(
8             collect
9             MERGE_ALL
10             MERGE_ADJACENT
11             MERGE_ADJACENT_SAME_TAGS
12             MERGE_SAME_TAGS
13             MERGE_SAME_DAY
14             MERGE_SAME_DAY_SAME_TAGS
15             MERGE_NONE
16             );
17             our %EXPORT_TAGS = (
18             merge => [
19             qw(
20             MERGE_ALL
21             MERGE_ADJACENT
22             MERGE_ADJACENT_SAME_TAGS
23             MERGE_SAME_TAGS
24             MERGE_SAME_DAY
25             MERGE_SAME_DAY_SAME_TAGS
26             MERGE_NONE
27             )
28             ]
29             );
30              
31 2     2   11 use Modern::Perl;
  2         3  
  2         18  
32 2     2   343 use autouse 'Carp' => qw(carp);
  2         4  
  2         18  
33 2     2   192 use autouse 'App::JobLog::Time' => qw(now);
  2         5  
  2         7  
34 2     2   160 use Class::Autouse qw(DateTime);
  2         4  
  2         15  
35 2     2   111 no if $] >= 5.018, warnings => "experimental::smartmatch";
  2         9  
  2         29  
36              
37 2     2   145 use constant MERGE_ALL => 1;
  2         4  
  2         128  
38 2     2   10 use constant MERGE_ADJACENT => 2;
  2         4  
  2         129  
39 2     2   12 use constant MERGE_ADJACENT_SAME_TAGS => 3;
  2         3  
  2         95  
40 2     2   10 use constant MERGE_SAME_TAGS => 4;
  2         6  
  2         119  
41 2     2   9 use constant MERGE_SAME_DAY => 5;
  2         3  
  2         91  
42 2     2   10 use constant MERGE_SAME_DAY_SAME_TAGS => 6;
  2         4  
  2         112  
43 2     2   9 use constant MERGE_NONE => 0;
  2         4  
  2         4860  
44              
45              
46             # takes in a bunch of App::JobClock::Log::Event objects
47             # returns a bunch of App::JobClock::Log::Synopsis objects
48             sub collect {
49 7940     7940 1 13861 my ( $day, $merge_level ) = @_;
50 7940         10629 my ( @synopses, $previous, @current_day );
51 7940         10113 for my $e ( @{ $day->events }, @{ $day->vacation } ) {
  7940         22377  
  7940         20538  
52 200         295 my $do_merge = 0;
53 200         280 my $mergand = $previous;
54 200 100       478 if ($previous) {
55 124         213 for ($merge_level) {
56 124         165 when (MERGE_ALL) { $do_merge = 1 }
  0         0  
57 124         165 when (MERGE_ADJACENT) { $do_merge = $previous->adjacent($e) }
  0         0  
58 124         155 when (MERGE_SAME_TAGS) {
59 0         0 for my $o (@synopses) {
60 0 0       0 if ( $o->same_tags($e) ) {
61 0         0 $mergand = $o;
62 0         0 $do_merge = 1;
63 0         0 last;
64             }
65             }
66             }
67 124         148 when (MERGE_SAME_DAY) { $do_merge = 1 }
  0         0  
68 124         144 when (MERGE_SAME_DAY_SAME_TAGS) {
69 0         0 for my $s (@current_day) {
70 0 0       0 if ( $s->same_tags($e) ) {
71 0         0 $do_merge = 1;
72 0         0 $mergand = $s;
73 0         0 last;
74             }
75             }
76             }
77 124         172 when (MERGE_ADJACENT_SAME_TAGS) {
78 124   33     264 $do_merge = $previous->adjacent($e)
79             && $previous->same_tags($e)
80             }
81 0         0 when (MERGE_NONE) { $do_merge = 0 }
  0         0  
82 0         0 default { carp 'unfamiliar merge level' };
  0         0  
83             }
84             }
85              
86             # keep vacation and regular events apart
87 200   66     665 $do_merge &&= ref $mergand->last_event eq ref $e;
88              
89 200 100       417 if ($do_merge) {
90 124         258 $mergand->merge($e);
91             }
92             else {
93 76         263 $previous = _new( $e, $merge_level );
94 76         211 push @synopses, $previous;
95 76         205 push @current_day, $previous;
96             }
97             }
98 7940         45970 $day->{synopses} = \@synopses;
99             }
100              
101             # test to make sure this and the given event
102             sub same_tags {
103 124     124 0 9728 my ( $self, $event ) = @_;
104 124         263 for my $e ( $self->events ) {
105             return 0
106 217         620 unless $e->all_tags( @{ $event->tags } )
107 217 50 33     260 && $event->all_tags( @{ $e->tags } );
  217         565  
108             }
109 124         572 return 1;
110             }
111              
112             sub same_day {
113 0     0 0 0 my ( $self, $event ) = @_;
114 0         0 my $d1 = ( $self->events )[-1]->end;
115 0         0 my $d2 = $event->start;
116             return
117 0   0     0 $d1->day == $d2->day
118             && $d1->month == $d2->month
119             && $d1->year == $d2->year;
120             }
121              
122             # whether given event is immediately adjacent to last event in synopsis
123             sub adjacent {
124 124     124 0 166 my ( $self, $event ) = @_;
125 124 100       410 return 1 if !$event->can('end'); # notes are always considered adjacent
126 93   33     1130 my $d1 = ( $self->events )[-1]->end || now;
127 93         3975 my $d2 = $event->start;
128 93         335 return DateTime->compare( $d1, $d2 ) == 0;
129             }
130              
131             # add an event to the events described
132 124     124 0 138 sub merge { push @{ $_[0]{events} }, $_[1] }
  124         448  
133              
134              
135 0     0 1 0 sub date { $_[0]->{events}[0]->start }
136              
137              
138             sub description {
139 76     76 1 130 my ($self) = @_;
140 76 50       195 unless ( exists $self->{description} ) {
141 76         89 my ( %seen, @descriptions );
142 76         185 for my $e ( $self->events ) {
143 200         225 for my $d ( @{ $e->data->description } ) {
  200         568  
144 200 50       611 unless ( $seen{$d} ) {
145 200         466 $seen{$d} = 1;
146 200         283 chomp $d; # got newline from log
147 200         522 push @descriptions, $d;
148             }
149             }
150             }
151 76         147 my $s = $descriptions[0];
152 76         189 for my $d ( @descriptions[ 1 .. $#descriptions ] ) {
153 124 50       492 $s .= $s =~ /\w$/ ? '; ' : ' ';
154 124         233 $s .= $d;
155             }
156 76         350 $self->{description} = $s;
157             }
158 76         281 return $self->{description};
159             }
160              
161              
162             sub tags {
163 76     76 1 142 my ($self) = @_;
164 76         106 my %seen;
165 76         130 my $s = '';
166 76         190 for my $e ( $self->events ) {
167 200         238 for my $t ( @{ $e->tags } ) {
  200         559  
168 12         126 $seen{$t} = 1;
169             }
170             }
171 76         392 return ( sort keys %seen );
172             }
173              
174              
175             sub tag_string {
176 87     87 1 132 my ($self) = @_;
177             $self->{tag_string} = join ', ', $self->tags
178 87 100       330 unless exists $self->{tag_string};
179 87         375 return $self->{tag_string};
180             }
181              
182              
183 733     733 1 873 sub events { @{ $_[0]->{events} } }
  733         2505  
184              
185              
186 124     124 1 265 sub last_event { ( $_[0]->events )[-1] }
187              
188             # constructs a single-event synopsis
189             # NOTE: not a package method
190             sub _new {
191 76     76   174 my ( $event, $merge_level ) = @_;
192 76 50 33     422 carp 'requires event argument'
193             unless $event && $event->isa('App::JobLog::Log::Note');
194 76         1346 my ( $one_interval, $one_day );
195 76         194 for ($merge_level) {
196 76         173 when (MERGE_ALL) { ( $one_interval, $one_day ) = ( 0, 0 ) }
  0         0  
197 76         146 when (MERGE_ADJACENT) { ( $one_interval, $one_day ) = ( 1, 1 ) }
  0         0  
198 76         111 when (MERGE_ADJACENT_SAME_TAGS) {
199 65         156 ( $one_interval, $one_day ) = ( 1, 1 )
200             }
201 11         39 when (MERGE_SAME_TAGS) { ( $one_interval, $one_day ) = ( 0, 0 ) }
  0         0  
202 11         42 when (MERGE_SAME_DAY) { ( $one_interval, $one_day ) = ( 0, 1 ) }
  0         0  
203 11         60 when (MERGE_SAME_DAY_SAME_TAGS) {
204 0         0 ( $one_interval, $one_day ) = ( 0, 1 )
205             }
206 11         40 when (MERGE_NONE) { ( $one_interval, $one_day ) = ( 1, 1 ) }
  11         94  
207             }
208 76         850 return bless {
209             events => [$event],
210             one_interval => $one_interval,
211             one_day => $one_day
212             },
213             __PACKAGE__;
214             }
215              
216              
217 88     88 1 265 sub single_interval { $_[0]->{one_interval} }
218              
219              
220 0     0 1 0 sub single_day { $_[0]->{one_day} }
221              
222              
223             sub duration {
224 88     88 1 144 my ($self) = @_;
225 88         204 my @events = $self->events;
226 88 50       362 return 0 unless $events[0]->can('end'); # notes have no duration
227 88 50       1014 if ( $self->single_interval ) {
228 88         164 my ( $se, $ee ) = ( $events[0], $events[$#events] );
229 88   66     271 my ( $start, $end ) = ( $se->start, $ee->end || now );
230 88         3856 return $end->epoch - $start->epoch;
231             }
232             else {
233 0         0 my $d = 0;
234 0         0 $d += $_->duration for @events;
235 0         0 return $d;
236             }
237             }
238              
239              
240             sub time_fmt {
241 152     152 1 284 my ($self) = @_;
242 152         320 my @events = $self->events;
243 152         323 my ( $se, $ee ) = ( $events[0], $events[$#events] );
244 152 100 100     584 if ( @events == 1 && !$se->can('end') ) { # single note
245 2         28 return $se->start->strftime('%l:%M %P');
246             }
247 150 100       1085 my ( $start, $end ) =
248             ( $se->start, $ee->can('end') ? $ee->end : $ee->start );
249 150         231 my $s;
250 150 100       485 if ($end) {
251 138 50 33     5661 return 'vacation'
252             if ref $se eq 'App::JobLog::Vacation::Period' && !$se->fixed;
253 138   66     414 my $same_period = $start->hour < 12 && $end->hour < 12
254             || $start->hour >= 12 && $end->hour >= 12;
255 138 100 66     2398 if ( $same_period
      100        
256             && $start->hour == $end->hour
257             && $start->minute == $end->minute )
258             {
259 62         1082 $s = $start->strftime('%l:%M %P');
260             }
261             else {
262 76 100       528 my ( $f1, $f2 ) =
263             ( $same_period ? '%l:%M' : '%l:%M %P', '%l:%M %P' );
264 76         280 $s = $start->strftime($f1) . ' - ' . $end->strftime($f2);
265             }
266             }
267             else {
268 12         123 $s = $start->strftime('%l:%M %P') . ' - ongoing';
269             }
270 150         12642 $s =~ s/ / /; # strftime tends to add in an extra space
271 150         443 $s =~ s/^ //;
272 150         620 return $s;
273             }
274              
275             1;
276              
277             __END__