| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::JobLog::Command::truncate; | 
| 2 |  |  |  |  |  |  | $App::JobLog::Command::truncate::VERSION = '1.039'; | 
| 3 |  |  |  |  |  |  | # ABSTRACT: decapitate the log | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 2 |  |  | 2 |  | 1651 | use App::JobLog -command; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 16 |  | 
| 7 | 2 |  |  | 2 |  | 743 | use autouse 'App::JobLog::TimeGrammar' => qw(parse); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 10 |  | 
| 8 |  |  |  |  |  |  | use Class::Autouse | 
| 9 | 2 |  |  | 2 |  | 112 | qw(IO::File App::JobLog::Log App::JobLog::Log::Line File::Temp File::Spec); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 12 |  | 
| 10 | 2 |  |  | 2 |  | 257 | use autouse 'App::JobLog::Time'   => qw(now); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 10 |  | 
| 11 | 2 |  |  | 2 |  | 148 | use autouse 'App::JobLog::Config' => qw(log dir); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 7 |  | 
| 12 | 2 |  |  | 2 |  | 179 | use autouse 'File::Copy'          => qw(move); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 8 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 2 |  |  | 2 |  | 136 | use Modern::Perl; | 
|  | 2 |  |  |  |  | 9 |  | 
|  | 2 |  |  |  |  | 12 |  | 
| 15 | 2 |  |  | 2 |  | 241 | no if $] >= 5.018, warnings => "experimental::smartmatch"; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 15 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | sub execute { | 
| 18 | 0 |  |  | 0 | 1 |  | my ( $self, $opt, $args ) = @_; | 
| 19 | 0 |  |  |  |  |  | my $expression = join ' ', @$args; | 
| 20 | 0 |  |  |  |  |  | my ( $s, $is_interval ); | 
| 21 | 0 |  |  |  |  |  | eval { ( $s, undef, $is_interval ) = parse $expression; }; | 
|  | 0 |  |  |  |  |  |  | 
| 22 | 0 | 0 |  |  |  |  | $self->usage_error($@) if $@; | 
| 23 | 0 | 0 |  |  |  |  | $self->usage_error('truncation date must not be a interval') | 
| 24 |  |  |  |  |  |  | if $is_interval; | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | # determine name of head log | 
| 27 | 0 |  |  |  |  |  | my $log = App::JobLog::Log->new; | 
| 28 | 0 |  |  |  |  |  | my ($p) = $log->find_previous($s); | 
| 29 | 0 | 0 |  |  |  |  | $self->usage("no event in log prior to $expression") unless $p; | 
| 30 | 0 |  |  |  |  |  | my ($e) = $log->first_event; | 
| 31 | 0 |  |  |  |  |  | my $base = 'log-' . $e->start->ymd . '--' . $p->start->ymd; | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | # create output handle for head log | 
| 34 | 0 | 0 |  |  |  |  | my $io = | 
| 35 |  |  |  |  |  |  | $opt->compression ? _pick_compression( $opt->compression ) : 'IO::File'; | 
| 36 | 0 |  |  |  |  |  | my $suffix = ''; | 
| 37 | 0 |  |  |  |  |  | my @args   = (); | 
| 38 | 0 |  |  |  |  |  | for ($io) { | 
| 39 | 0 |  |  |  |  |  | when ('IO::File') { push @args, 'w' } | 
|  | 0 |  |  |  |  |  |  | 
| 40 | 0 |  |  |  |  |  | when ('IO::Compress::Zip') { | 
| 41 | 0 |  |  |  |  |  | $suffix = '.zip'; | 
| 42 | 0 |  |  |  |  |  | push @args, Name => $base; | 
| 43 |  |  |  |  |  |  | } | 
| 44 | 0 |  |  |  |  |  | when ('IO::Compress::Gzip')  { $suffix = '.gz' } | 
|  | 0 |  |  |  |  |  |  | 
| 45 | 0 |  |  |  |  |  | when ('IO::Compress::Bzip2') { $suffix = '.bz2' } | 
|  | 0 |  |  |  |  |  |  | 
| 46 | 0 |  |  |  |  |  | when ('IO::Compress::Lzma')  { $suffix = '.lzma' } | 
|  | 0 |  |  |  |  |  |  | 
| 47 | 0 |  |  |  |  |  | default { die "unprepared to handle $io; please report bug" }; | 
|  | 0 |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | } | 
| 49 | 0 |  |  |  |  |  | my $old_f = File::Spec->catfile( dir, $base . $suffix ); | 
| 50 | 0 |  |  |  |  |  | my $old_fh     = $io->new( $old_f, @args ); | 
| 51 | 0 |  |  |  |  |  | my $fh         = File::Temp->new; | 
| 52 | 0 |  |  |  |  |  | my $current_fh = $old_fh; | 
| 53 | 0 |  |  |  |  |  | my $log_handle = IO::File->new( log, 'r' ); | 
| 54 | 0 |  |  |  |  |  | my ( $unswitched, @buffer, $previous ) = (1); | 
| 55 | 0 |  |  |  |  |  | while ( defined( my $line = $log_handle->getline ) ) { | 
| 56 | 0 |  |  |  |  |  | my $ll = App::JobLog::Log::Line->parse($line); | 
| 57 | 0 | 0 |  |  |  |  | if ( $ll->is_event ) { | 
| 58 | 0 | 0 |  |  |  |  | if ($unswitched) { | 
| 59 | 0 | 0 |  |  |  |  | $previous = $ll if $ll->is_beginning; | 
| 60 | 0 | 0 |  |  |  |  | if ( $ll->time > $s ) { | 
|  |  | 0 |  |  |  |  |  | 
| 61 | 0 | 0 |  |  |  |  | if ($previous) {    # event spanning border | 
| 62 | 0 |  |  |  |  |  | my $end_time = $s->clone->subtract( seconds => 1 ); | 
| 63 | 0 |  |  |  |  |  | $current_fh->print( | 
| 64 |  |  |  |  |  |  | App::JobLog::Log::Line->new( | 
| 65 |  |  |  |  |  |  | done => 1, | 
| 66 |  |  |  |  |  |  | time => $end_time | 
| 67 |  |  |  |  |  |  | ) | 
| 68 |  |  |  |  |  |  | ); | 
| 69 | 0 |  |  |  |  |  | $previous->time = $s; | 
| 70 | 0 |  |  |  |  |  | $line = $previous->to_string . "\n"; | 
| 71 |  |  |  |  |  |  | } | 
| 72 | 0 |  |  |  |  |  | $current_fh->close; | 
| 73 | 0 |  |  |  |  |  | $current_fh = $fh; | 
| 74 | 0 |  |  |  |  |  | _header( $base, $suffix, \@buffer ); | 
| 75 | 0 |  |  |  |  |  | $unswitched = undef; | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  | elsif ( $ll->is_end ) { | 
| 78 | 0 |  |  |  |  |  | $previous = undef; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  | } | 
| 81 | 0 |  |  |  |  |  | while (@buffer) { | 
| 82 | 0 |  |  |  |  |  | $current_fh->print( shift @buffer ); | 
| 83 |  |  |  |  |  |  | } | 
| 84 | 0 |  |  |  |  |  | $current_fh->print($line); | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  | else { | 
| 87 | 0 |  |  |  |  |  | push @buffer, $line; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  | } | 
| 90 | 0 |  |  |  |  |  | while (@buffer) { | 
| 91 | 0 |  |  |  |  |  | $current_fh->print( shift @buffer ); | 
| 92 |  |  |  |  |  |  | } | 
| 93 | 0 |  |  |  |  |  | $current_fh->close; | 
| 94 | 0 |  |  |  |  |  | move( "$fh", log ); | 
| 95 | 0 |  |  |  |  |  | print "truncated portion of log saved in $old_f\n"; | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | sub validate { | 
| 99 | 0 |  |  | 0 | 0 |  | my ( $self, $opt, $args ) = @_; | 
| 100 | 0 | 0 |  |  |  |  | $self->usage_error('no time expression provided') unless @$args; | 
| 101 | 0 | 0 |  |  |  |  | if ( $opt->compression ) { | 
| 102 | 0 |  |  |  |  |  | my $alg = _pick_compression( $opt->compression ); | 
| 103 | 0 |  |  |  |  |  | eval "require $alg"; | 
| 104 | 0 | 0 |  |  |  |  | $self->usage_error( | 
| 105 |  |  |  |  |  |  | "$@: you must install $alg to use compression option --" | 
| 106 |  |  |  |  |  |  | . $opt->compression ) | 
| 107 |  |  |  |  |  |  | if $@; | 
| 108 |  |  |  |  |  |  | } | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | sub usage_desc { | 
| 112 | 0 |  |  | 0 | 1 |  | '%c ' . __PACKAGE__->name . ' %o '; | 
| 113 |  |  |  |  |  |  | } | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | sub abstract { | 
| 116 | 0 |  |  | 0 | 1 |  | 'shorten the log to contain only those moments after a given date'; | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | sub options { | 
| 120 |  |  |  |  |  |  | return ( | 
| 121 |  |  |  |  |  |  | [ | 
| 122 | 0 |  |  | 0 | 0 |  | compression => hidden => { | 
| 123 |  |  |  |  |  |  | one_of => [ | 
| 124 |  |  |  |  |  |  | [ 'zip|z',   'pass truncated head of log through zip', ], | 
| 125 |  |  |  |  |  |  | [ 'gzip|g',  'pass truncated head of log through gzip', ], | 
| 126 |  |  |  |  |  |  | [ 'bzip2|b', 'pass truncated head of log through bzip2', ], | 
| 127 |  |  |  |  |  |  | [ 'lzma|l',  'pass truncated head of log through lzma', ], | 
| 128 |  |  |  |  |  |  | ] | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  | ] | 
| 131 |  |  |  |  |  |  | ); | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub full_description { | 
| 135 |  |  |  |  |  |  | < | 
| 136 |  |  |  |  |  |  | Over time your log will fill with cruft: work no one is interested in any longer, | 
| 137 |  |  |  |  |  |  | tags whose meaning you've forgotten. What you want to do at this point is chop off | 
| 138 |  |  |  |  |  |  | all the old stuff, stash it somewhere you can find it if need be, and retain in | 
| 139 |  |  |  |  |  |  | your active log only the more recent events. This is what truncate is for. You give | 
| 140 |  |  |  |  |  |  | it a starting date and it splits your log into two with the active portion containing | 
| 141 |  |  |  |  |  |  | all moments on that date or after. The older portion is retained in your joblog hidden | 
| 142 |  |  |  |  |  |  | directory. | 
| 143 |  |  |  |  |  |  | END | 
| 144 | 0 |  |  | 0 | 0 |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | # comment header added to truncated log | 
| 147 |  |  |  |  |  |  | sub _header { | 
| 148 | 0 |  |  | 0 |  |  | my ( $base, $suffix, $buffer ) = @_; | 
| 149 |  |  |  |  |  |  | unshift @$buffer, | 
| 150 | 0 |  |  |  |  |  | map { App::JobLog::Log::Line->new( comment => "$_\n" ) } | 
|  | 0 |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | < | 
| 152 | 0 |  |  |  |  |  | Log file truncated on @{[now]}. | 
| 153 |  |  |  |  |  |  | Head of log to be found in $base$suffix | 
| 154 |  |  |  |  |  |  | END | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | # converts chosen compression opt into appropriate IO:: algorithm | 
| 158 |  |  |  |  |  |  | sub _pick_compression { | 
| 159 | 0 |  |  | 0 |  |  | my $alg = shift; | 
| 160 | 0 |  |  |  |  |  | for ($alg) { | 
| 161 | 0 |  |  |  |  |  | when ('zip')   { return 'IO::Compress::Zip' } | 
|  | 0 |  |  |  |  |  |  | 
| 162 | 0 |  |  |  |  |  | when ('gzip')  { return 'IO::Compress::Gzip' } | 
|  | 0 |  |  |  |  |  |  | 
| 163 | 0 |  |  |  |  |  | when ('bzip2') { return 'IO::Compress::Bzip2' } | 
|  | 0 |  |  |  |  |  |  | 
| 164 | 0 |  |  |  |  |  | when ('lzma')  { return 'IO::Compress::Lzma' } | 
|  | 0 |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | 1; | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | __END__ |