File Coverage

blib/lib/App/Sqitch/Command/log.pm
Criterion Covered Total %
statement 52 52 100.0
branch 10 10 100.0
condition n/a
subroutine 13 13 100.0
pod 1 1 100.0
total 76 76 100.0


line stmt bran cond sub pod time code
1             package App::Sqitch::Command::log;
2              
3 2     2   2160 use 5.010;
  2         9  
4 2     2   18 use strict;
  2         4  
  2         55  
5 2     2   13 use warnings;
  2         4  
  2         64  
6 2     2   13 use utf8;
  2         8  
  2         19  
7 2     2   61 use Locale::TextDomain qw(App-Sqitch);
  2         5  
  2         16  
8 2     2   486 use App::Sqitch::X qw(hurl);
  2         35  
  2         25  
9 2     2   666 use Moo;
  2         16  
  2         14  
10 2     2   838 use Types::Standard qw(Str Int ArrayRef Bool);
  2         5  
  2         20  
11 2     2   2579 use Type::Utils qw(class_type);
  2         5  
  2         20  
12 2     2   1799 use App::Sqitch::ItemFormatter;
  2         7  
  2         58  
13 2     2   17 use namespace::autoclean;
  2         5  
  2         11  
14 2     2   113 use Try::Tiny;
  2         7  
  2         1930  
15              
16             extends 'App::Sqitch::Command';
17             with 'App::Sqitch::Role::ConnectingCommand';
18              
19             our $VERSION = 'v1.4.0'; # VERSION
20              
21             my %FORMATS;
22             $FORMATS{raw} = <<EOF;
23             %{:event}C%e %H%{reset}C%T
24             name %n
25             project %o
26             %{requires}a%{conflicts}aplanner %{name}p <%{email}p>
27             planned %{date:raw}p
28             committer %{name}c <%{email}c>
29             committed %{date:raw}c
30              
31             %{ }B
32             EOF
33              
34             $FORMATS{full} = <<EOF;
35             %{:event}C%L %h%{reset}C%T
36             %{name}_ %n
37             %{project}_ %o
38             %R%X%{planner}_ %p
39             %{planned}_ %{date}p
40             %{committer}_ %c
41             %{committed}_ %{date}c
42              
43             %{ }B
44             EOF
45              
46             $FORMATS{long} = <<EOF;
47             %{:event}C%L %h%{reset}C%T
48             %{name}_ %n
49             %{project}_ %o
50             %{planner}_ %p
51             %{committer}_ %c
52              
53             %{ }B
54             EOF
55              
56             $FORMATS{medium} = <<EOF;
57             %{:event}C%L %h%{reset}C
58             %{name}_ %n
59             %{committer}_ %c
60             %{date}_ %{date}c
61              
62             %{ }B
63             EOF
64              
65             $FORMATS{short} = <<EOF;
66             %{:event}C%L %h%{reset}C
67             %{name}_ %n
68             %{committer}_ %c
69              
70             %{ }s
71             EOF
72              
73             $FORMATS{oneline} = '%{:event}C%h %l%{reset}C %o:%n %s';
74              
75             has target => (
76             is => 'ro',
77             isa => Str,
78             );
79              
80             has event => (
81             is => 'ro',
82             isa => ArrayRef,
83             );
84              
85             has change_pattern => (
86             is => 'ro',
87             isa => Str,
88             );
89              
90             has project_pattern => (
91             is => 'ro',
92             isa => Str,
93             );
94              
95             has committer_pattern => (
96             is => 'ro',
97             isa => Str,
98             );
99              
100             has max_count => (
101             is => 'ro',
102             isa => Int,
103             );
104              
105             has skip => (
106             is => 'ro',
107             isa => Int,
108             );
109              
110             has reverse => (
111             is => 'ro',
112             isa => Bool,
113             default => 0,
114             );
115              
116             has headers => (
117             is => 'ro',
118             isa => Bool,
119             default => 1,
120             );
121              
122             has format => (
123             is => 'ro',
124             isa => Str,
125             default => $FORMATS{medium},
126             );
127              
128             has formatter => (
129             is => 'ro',
130             isa => class_type('App::Sqitch::ItemFormatter'),
131             lazy => 1,
132             default => sub { App::Sqitch::ItemFormatter->new },
133             );
134              
135             sub options {
136             return qw(
137             event=s@
138             target|t=s
139             change-pattern|change=s
140             project-pattern|project=s
141             committer-pattern|committer=s
142             format|f=s
143             date-format|date=s
144             max-count|n=i
145             skip=i
146             reverse!
147             color=s
148             no-color
149             abbrev=i
150             oneline
151             headers!
152             );
153             }
154              
155             sub configure {
156             my ( $class, $config, $opt ) = @_;
157              
158             # Set base values if --oneline.
159             if ($opt->{oneline}) {
160             $opt->{format} ||= 'oneline';
161             $opt->{abbrev} //= 6;
162             }
163              
164             # Determine and validate the date format.
165             my $date_format = delete $opt->{date_format} || $config->get(
166             key => 'log.date_format'
167             );
168             if ($date_format) {
169             require App::Sqitch::DateTime;
170             App::Sqitch::DateTime->validate_as_string_format($date_format);
171             } else {
172             $date_format = 'iso';
173             }
174              
175             # Make sure the log format is valid.
176             if (my $format = $opt->{format}
177             || $config->get(key => 'log.format')
178             ) {
179             if ($format =~ s/^format://) {
180             $opt->{format} = $format;
181             } else {
182             $opt->{format} = $FORMATS{$format} or hurl log => __x(
183             'Unknown log format "{format}"',
184             format => $format
185             );
186             }
187             }
188              
189             # Determine how to handle ANSI colors.
190             my $color = delete $opt->{no_color} ? 'never'
191             : delete $opt->{color} || $config->get(key => 'log.color');
192              
193             $opt->{formatter} = App::Sqitch::ItemFormatter->new(
194             ( $date_format ? ( date_format => $date_format ) : () ),
195             ( $color ? ( color => $color ) : () ),
196             ( $opt->{abbrev} ? ( abbrev => delete $opt->{abbrev} ) : () ),
197             );
198              
199             return $class->SUPER::configure( $config, $opt );
200             }
201              
202             sub execute {
203 8     8 1 5560 my $self = shift;
204 8         70 my ($targets) = $self->parse_args(
205             target => $self->target,
206             args => \@_,
207             );
208              
209             # Warn on multiple targets.
210 8         17 my $target = shift @{ $targets };
  8         18  
211             $self->warn(__x(
212             'Too many targets specified; connecting to {target}',
213             target => $target->name,
214 8 100       11 )) if @{ $targets };
  8         26  
215 8         334 my $engine = $target->engine;
216              
217             # Exit with status 1 on uninitialized database, probably not expected.
218 8 100       1271 hurl {
219             ident => 'log',
220             exitval => 1,
221             message => __x(
222             'Database {db} has not been initialized for Sqitch',
223             db => $engine->registry_destination,
224             ),
225             } unless $engine->initialized;
226              
227             # Exit with status 1 on no events, probably not expected.
228 7         44 my $iter = $engine->search_events(limit => 1);
229 7 100       54 hurl {
230             ident => 'log',
231             exitval => 1,
232             message => __x(
233             'No events logged for {db}',
234             db => $engine->destination,
235             ),
236             } unless $iter->();
237              
238             # Search the event log.
239 6 100       88 $iter = $engine->search_events(
240             event => $self->event,
241             change => $self->change_pattern,
242             project => $self->project_pattern,
243             committer => $self->committer_pattern,
244             limit => $self->max_count,
245             offset => $self->skip,
246             direction => $self->reverse ? 'ASC' : 'DESC',
247             );
248              
249             # Send the results.
250 6         140 my $formatter = $self->formatter;
251 6         419 my $format = $self->format;
252 6 100       31 $self->page( __x 'On database {db}', db => $engine->destination )
253             if $self->headers;
254 6         600 while ( my $change = $iter->() ) {
255 7         283 $self->page( $formatter->format( $format, $change ) );
256             }
257              
258 5         1369 return $self;
259             }
260              
261             1;
262              
263             __END__
264              
265             =head1 Name
266              
267             App::Sqitch::Command::log - Show a database event log
268              
269             =head1 Synopsis
270              
271             my $cmd = App::Sqitch::Command::log->new(%params);
272             $cmd->execute;
273              
274             =head1 Description
275              
276             If you want to know how to use the C<log> command, you probably want to be
277             reading C<sqitch-log>. But if you really want to know how the C<log> command
278             works, read on.
279              
280             =head1 Interface
281              
282             =head2 Attributes
283              
284             =head3 C<change_pattern>
285              
286             Regular expression to match against change names.
287              
288             =head3 C<committer_pattern>
289              
290             Regular expression to match against committer names.
291              
292             =head3 C<project_pattern>
293              
294             Regular expression to match against project names.
295              
296             =head3 C<event>
297              
298             Event type buy which to filter entries to display.
299              
300             =head3 C<format>
301              
302             Display format template.
303              
304             =head3 C<max_count>
305              
306             Maximum number of entries to display.
307              
308             =head3 C<reverse>
309              
310             Reverse the usual order of the display of entries.
311              
312             =head3 C<headers>
313              
314             Output headers. Defaults to true.
315              
316             =head3 C<skip>
317              
318             Number of entries to skip before displaying entries.
319              
320             =head3 C<target>
321              
322             The database target from which to read the log.
323              
324             =head2 Instance Methods
325              
326             =head3 C<execute>
327              
328             $log->execute;
329              
330             Executes the log command. The current log for the target database will be
331             searched and the resulting change history displayed.
332              
333             =head1 See Also
334              
335             =over
336              
337             =item L<sqitch-log>
338              
339             Documentation for the C<log> command to the Sqitch command-line client.
340              
341             =item L<sqitch>
342              
343             The Sqitch command-line client.
344              
345             =back
346              
347             =head1 Author
348              
349             David E. Wheeler <david@justatheory.com>
350              
351             =head1 License
352              
353             Copyright (c) 2012-2023 iovation Inc., David E. Wheeler
354              
355             Permission is hereby granted, free of charge, to any person obtaining a copy
356             of this software and associated documentation files (the "Software"), to deal
357             in the Software without restriction, including without limitation the rights
358             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
359             copies of the Software, and to permit persons to whom the Software is
360             furnished to do so, subject to the following conditions:
361              
362             The above copyright notice and this permission notice shall be included in all
363             copies or substantial portions of the Software.
364              
365             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
366             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
367             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
368             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
369             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
370             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
371             SOFTWARE.
372              
373             =cut