File Coverage

blib/lib/Log/Progress/Parser.pm
Criterion Covered Total %
statement 58 62 93.5
branch 27 38 71.0
condition 9 12 75.0
subroutine 5 5 100.0
pod 2 2 100.0
total 101 119 84.8


line stmt bran cond sub pod time code
1             package Log::Progress::Parser;
2             $Log::Progress::Parser::VERSION = '0.13';
3 5     5   7102 use Moo 2;
  5         59677  
  5         30  
4 5     5   10882 use JSON;
  5         43548  
  5         28  
5              
6             # ABSTRACT: Parse progress data from a file
7              
8              
9             has input => ( is => 'rw' );
10             has input_pos => ( is => 'rw' );
11             has state => ( is => 'rw', default => sub { {} } );
12             *status= *state; # alias, since I changed the API
13             has sticky_message => ( is => 'rw' );
14             has on_data => ( is => 'rw' );
15              
16              
17             sub parse {
18 28     28 1 2414167 my $self= shift;
19 28         190 my $fh= $self->input;
20 28 100       191 if (!ref $fh) {
21 19         39 my $input= $fh;
22 19         26 undef $fh;
23 2 50   2   13 open $fh, '<', \$input or die "open(scalar): $!";
  2         3  
  2         22  
  19         331  
24             }
25 28 100       1829 if ($self->input_pos) {
26 18 50       355 seek($fh, $self->input_pos, 0)
27             or die "seek: $!";
28             }
29             # TODO: If input is seekable, then seek to end and work backward
30             # Substeps will make that rather complicated.
31 28         79 my $pos;
32             my %parent_cleanup;
33 28         75 local $_;
34 28         560 while (<$fh>) {
35 170 100       616 last unless substr($_,-1) eq "\n";
36 169         390 $pos= tell($fh);
37 169 100       1125 next unless $_ =~ /^progress: (([[:alpha:]][\w.]*) )?(.*)/;
38 166         751 my ($step_id, $remainder)= ($2, $3);
39 166         684 my $state= $self->step_state($step_id, 1, \my @state_parent);
40             # First, check for progress number followed by optional message
41 166 100       930 if ($remainder =~ m,^([\d.]+)(/(\d+))?( (.*))?,) {
    50          
    0          
42 151         682 my ($num, $denom, $message)= ($1, $3, $5);
43 151 100       407 if (defined $message) {
44 20         118 $message =~ s/^- //; # "- " is optional syntax
45 20         77 $state->{message}= $message;
46             } else {
47 131 100 100     806 $state->{message}= '' if !defined $state->{message} or !$self->sticky_message;
48             }
49 151         634 $state->{progress}= $num+0;
50 151 100       424 if (defined $denom) {
51 120         316 $state->{current}= $num;
52 120         278 $state->{total}= $denom;
53 120 50       405 $state->{progress} /= $denom if $denom;
54             }
55 151 50       424 $state->{progress}= 1 if $state->{progress} > 1;
56 151 100       401 if ($state->{contribution}) {
57             # Need to apply progress to parent nodes at end
58             $parent_cleanup{$state_parent[$_]}= [ $_, $state_parent[$_] ]
59 139         1429 for 0..$#state_parent;
60             }
61             }
62             elsif ($remainder =~ m,^\(([\d.]+)\) (.*),) {
63 15         54 my ($contribution, $title)= ($1, $2);
64 15         35 $title =~ s/^- //; # "- " is optional syntax
65 15         27 $state->{title}= $title;
66 15         111 $state->{contribution}= $contribution+0;
67             }
68             elsif ($remainder =~ /^\{/) {
69 0         0 my $data= JSON->new->decode($remainder);
70 0 0       0 $state->{data}= !defined $self->on_data? $data
71             : $self->on_data->($self, $step_id, $data);
72             }
73             else {
74 0         0 warn "can't parse progress message \"$remainder\"\n";
75             }
76             }
77             # Mark file position for next call
78 28         134 $self->input_pos($pos);
79             # apply child progress contributions to parent nodes
80 28         154 for (sort { $b->[0] <=> $a->[0] } values %parent_cleanup) {
  0         0  
81 13         41 my $state= $_->[1];
82 13         34 $state->{progress}= 0;
83 13         36 for (values %{$state->{step}}) {
  13         74  
84             $state->{progress} += $_->{progress} * $_->{contribution}
85 31 50 66     224 if $_->{progress} && $_->{contribution};
86             }
87             }
88 28         299 return $self->state;
89             }
90              
91              
92             sub step_state {
93 166     166 1 445 my ($self, $step_id, $create, $path)= @_;
94 166         459 my $state= $self->state;
95 166 100 66     779 if (defined $step_id and length $step_id) {
96 154         573 for (split /\./, $step_id) {
97 154 50       458 push @$path, $state if $path;
98 154   66     541 $state= ($state->{step}{$_} or do {
99             return undef unless $create;
100             my $idx= scalar(keys %{$state->{step}});
101             $state->{step}{$_}= { idx => $idx };
102             });
103             }
104             }
105 166         493 $state;
106             }
107              
108             1;
109              
110             __END__