File Coverage

blib/lib/Hailo/Command.pm
Criterion Covered Total %
statement 50 50 100.0
branch 7 8 87.5
condition 2 3 66.6
subroutine 11 11 100.0
pod n/a
total 70 72 97.2


line stmt bran cond sub pod time code
1             package Hailo::Command;
2             our $AUTHORITY = 'cpan:AVAR';
3             $Hailo::Command::VERSION = '0.75';
4 12     12   209613 use v5.10.0;
  12         48  
5 12     12   595 use Moose;
  12         474683  
  12         66  
6 12     12   75924 use MooseX::Types::Moose ':all';
  12         490138  
  12         98  
7 12     12   101134 use MooseX::Getopt;
  12         3171282  
  12         585  
8 12     12   4434 use MooseX::StrictConstructor;
  12         128353  
  12         67  
9 12     12   94356 use namespace::clean -except => 'meta';
  12         27  
  12         80  
10              
11             extends 'Hailo';
12              
13             with 'MooseX::Getopt::Dashes';
14              
15             ## Our internal Getopts method that Hailo.pm doesn't care about.
16              
17             has help_flag => (
18             traits => [ qw/ Getopt / ],
19             cmd_aliases => 'h',
20             cmd_flag => 'help',
21             isa => Bool,
22             is => 'ro',
23             default => 0,
24             documentation => "You're soaking it in",
25             );
26              
27             has _go_version => (
28             traits => [ qw/ Getopt / ],
29             cmd_aliases => 'v',
30             cmd_flag => 'version',
31             documentation => 'Print version and exit',
32             isa => Bool,
33             is => 'ro',
34             );
35              
36             has _go_examples => (
37             traits => [ qw/ Getopt / ],
38             cmd_flag => 'examples',
39             documentation => 'Print examples along with the help message',
40             isa => Bool,
41             is => 'ro',
42             );
43              
44             has _go_progress => (
45             traits => [ qw/ Getopt / ],
46             cmd_aliases => 'p',
47             cmd_flag => 'progress',
48             documentation => 'Display progress during the import',
49             isa => Bool,
50             is => 'ro',
51             default => sub {
52             my ($self) = @_;
53             $self->_is_interactive();
54             },
55             );
56              
57             has _go_learn => (
58             traits => [ qw/ Getopt / ],
59             cmd_aliases => "l",
60             cmd_flag => "learn",
61             documentation => "Learn from STRING",
62             isa => Str,
63             is => "ro",
64             );
65              
66             has _go_learn_reply => (
67             traits => [ qw/ Getopt / ],
68             cmd_aliases => "L",
69             cmd_flag => "learn-reply",
70             documentation => "Learn from STRING and reply to it",
71             isa => Str,
72             is => "ro",
73             );
74              
75             has _go_train => (
76             traits => [ qw/ Getopt / ],
77             cmd_aliases => "t",
78             cmd_flag => "train",
79             documentation => "Learn from all the lines in FILE, use - for STDIN",
80             isa => Str,
81             is => "ro",
82             );
83              
84             has _go_train_fast => (
85             traits => [ qw/ Getopt / ],
86             cmd_aliases => "f",
87             cmd_flag => "train-fast",
88             documentation => "Train with aggressive caching (memory-hungry!)",
89             isa => Str,
90             is => "ro",
91             );
92              
93             has _go_reply => (
94             traits => [ qw/ Getopt / ],
95             cmd_aliases => "r",
96             cmd_flag => "reply",
97             documentation => "Reply to STRING",
98             isa => Str,
99             is => "ro",
100             );
101              
102             has _go_random_reply => (
103             traits => [ qw/ Getopt / ],
104             cmd_aliases => "R",
105             cmd_flag => "random-reply",
106             documentation => "Like --reply but takes no STRING; Babble at random",
107             isa => Bool,
108             is => "ro",
109             );
110              
111             has _go_stats => (
112             traits => [ qw/ Getopt / ],
113             cmd_aliases => "s",
114             cmd_flag => "stats",
115             documentation => "Print statistics about the brain",
116             isa => Bool,
117             is => "ro",
118             );
119              
120             ## Things we have to pass to Hailo.pm via triggers when they're set
121              
122             has _go_autosave => (
123             traits => [ qw/ Getopt / ],
124             cmd_aliases => 'a',
125             cmd_flag => 'autosave',
126             documentation => 'Save the brain on exit (on by default)',
127             isa => Bool,
128             is => 'rw',
129             trigger => sub {
130             my ($self, $bool) = @_;
131             $self->save_on_exit($bool);
132             },
133             );
134              
135             has _go_order => (
136             traits => [ qw/ Getopt / ],
137             cmd_aliases => "o",
138             cmd_flag => "order",
139             documentation => "Markov order; How deep the rabbit hole goes",
140             isa => Int,
141             is => "rw",
142             trigger => sub {
143             my ($self, $order) = @_;
144             $self->order($order);
145             },
146             );
147              
148             has _go_brain => (
149             traits => [ qw/ Getopt / ],
150             cmd_aliases => "b",
151             cmd_flag => "brain",
152             documentation => "Load/save brain to/from FILE",
153             isa => Str,
154             is => "ro",
155             trigger => sub {
156             my ($self, $brain) = @_;
157             $self->brain($brain);
158             },
159             );
160              
161             # working classes
162             has _go_engine_class => (
163             traits => [ qw/ Getopt / ],
164             cmd_aliases => "E",
165             cmd_flag => "engine",
166             isa => Str,
167             is => "rw",
168             documentation => "Use engine CLASS",
169             trigger => sub {
170             my ($self, $class) = @_;
171             $self->engine_class($class);
172             },
173             );
174              
175             has _go_storage_class => (
176             traits => [ qw/ Getopt / ],
177             cmd_aliases => "S",
178             cmd_flag => "storage",
179             isa => Str,
180             is => "rw",
181             documentation => "Use storage CLASS",
182             trigger => sub {
183             my ($self, $class) = @_;
184             $self->storage_class($class);
185             },
186             );
187              
188             has _go_tokenizer_class => (
189             traits => [ qw/ Getopt / ],
190             cmd_aliases => "T",
191             cmd_flag => "tokenizer",
192             isa => Str,
193             is => "rw",
194             documentation => "Use tokenizer CLASS",
195             trigger => sub {
196             my ($self, $class) = @_;
197             $self->tokenizer_class($class);
198             },
199             );
200              
201             has _go_ui_class => (
202             traits => [ qw/ Getopt / ],
203             cmd_aliases => "u",
204             cmd_flag => "ui",
205             isa => Str,
206             is => "rw",
207             documentation => "Use UI CLASS",
208             trigger => sub {
209             my ($self, $class) = @_;
210             $self->ui_class($class);
211             },
212             );
213              
214             # Stop Hailo from polluting our command-line interface
215             for (qw/ save_on_exit order brain /, map { qq[${_}_class] } qw/ engine storage tokenizer ui /) {
216             has "+$_" => (
217             traits => [ qw/ NoGetopt / ],
218             );
219             }
220              
221             # Check validity of options
222             before run => sub {
223             my ($self) = @_;
224              
225             if (not $self->_storage->ready and
226             (defined $self->_go_reply or
227             defined $self->_go_train or
228             defined $self->_go_train_fast or
229             defined $self->_go_stats or
230             defined $self->_go_learn or
231             defined $self->_go_learn_reply or
232             defined $self->_go_random_reply)) {
233             # TODO: Make this spew out the --help reply just like hailo
234             # with invalid options does usually, but only if run via
235             # ->new_with_options
236             die "To reply/train/learn/stat you must specify options to initialize your storage backend\n";
237             }
238              
239             if (defined $self->_go_train and defined $self->_go_train_fast) {
240             die "You can only specify one of --train and --train-fast\n";
241             }
242              
243             return;
244             };
245              
246             sub run {
247             my ($self) = @_;
248              
249             if ($self->_go_version) {
250             # Munging strictness because we don't have a version from a
251             # Git checkout. Dist::Zilla provides it.
252 12     12   17925 no strict 'vars';
  12         28  
  12         11603  
253             my $version = $VERSION // 'dev-git';
254              
255             say "hailo $version";
256             return;
257             }
258              
259             if ($self->_is_interactive() and
260             $self->_storage->ready and
261             not defined $self->_go_train and
262             not defined $self->_go_train_fast and
263             not defined $self->_go_learn and
264             not defined $self->_go_reply and
265             not defined $self->_go_learn_reply and
266             not defined $self->_go_stats and
267             not defined $self->_go_random_reply) {
268             $self->_ui->run($self);
269             }
270              
271             $self->train($self->_go_train) if defined $self->_go_train;
272             $self->train($self->_go_train_fast, 1) if defined $self->_go_train_fast;
273             $self->learn($self->_go_learn) if defined $self->_go_learn;
274              
275             if (defined $self->_go_learn_reply) {
276             my $answer = $self->learn_reply($self->_go_learn_reply);
277             say $answer // "I don't know enough to answer you yet.";
278             }
279              
280             if (defined $self->_go_random_reply) {
281             my $answer = $self->reply();
282             say $answer // "I don't know enough to answer you yet.";
283             }
284             elsif (defined $self->_go_reply) {
285             my $answer = $self->reply($self->_go_reply);
286             say $answer // "I don't know enough to answer you yet.";
287             }
288              
289             if ($self->_go_stats) {
290             my ($tok, $ex, $prev, $next) = $self->stats();
291             my $order = $self->_storage->order;
292             say "Tokens: $tok";
293             say "Expression length: $order tokens";
294             say "Expressions: $ex";
295             say "Links to preceding tokens: $prev";
296             say "Links to following tokens: $next";
297             }
298              
299             return;
300             }
301              
302             override _train_fh => sub {
303             my ($self, $fh, $fast, $filename) = @_;
304              
305             if ($self->_go_progress and $self->_is_interactive) {
306             $self->train_progress($fh, $fast, $filename);
307             } else {
308             super();
309             }
310             };
311              
312             before train_progress => sub {
313             require Term::Sk;
314             require File::CountLines;
315             File::CountLines->import('count_lines');
316             require Time::HiRes;
317             Time::HiRes->import(qw(gettimeofday tv_interval));
318             return;
319             };
320              
321             sub train_progress {
322             my ($self, $fh, $fast, $filename) = @_;
323             my $lines = count_lines($filename);
324             my $progress = Term::Sk->new('%d Elapsed: %8t %21b %4p %2d (%c lines of %m)', {
325             # Start at line 1, not 0
326             base => 1,
327             target => $lines,
328             # Every 0.1 seconds for long files
329             freq => ($lines < 10_000 ? 10 : 'd'),
330             # Override Term::Sk's default 100_000 to 100,000
331             commify => sub {
332             my $int = shift;
333             $int = reverse $int;
334             $int =~ s/(\d{3})(?=\d)(?!\d*\.)/$1,/g;
335             $int = reverse $int;
336             return $int;
337             },
338             }) or die "Error in Term::Sk->new: (code $Term::Sk::errcode) $Term::Sk::errmsg";
339              
340             my $next_update = 0;
341             my $start_time = [gettimeofday()];
342              
343             my $i = 0; while (my $line = <$fh>) {
344             $i++;
345             chomp $line;
346             $self->_learn_one($line, $fast);
347             $self->_engine->flush_cache if !$fast;
348             $progress->up;
349             }
350              
351             $progress->close;
352              
353             if ($fast) {
354             my $msg = "Flushing cache (this may take a while for large inputs)";
355             syswrite STDOUT, $msg;
356             $self->_engine->flush_cache;
357             print "\010" x length $msg;
358             }
359              
360             my $elapsed = tv_interval($start_time);
361             say sprintf "Trained from %d lines in %.2f seconds; %.2f lines/s", $i, $elapsed, ($i / $elapsed);
362              
363             return;
364             }
365              
366             # --i--do-not-exist
367 1     1   65057 sub _getopt_spec_exception { goto &_getopt_full_usage }
368              
369             # --help
370             sub _getopt_full_usage {
371 5     5   280411 my ($self, $usage, $plain_str) = @_;
372              
373             # If called from _getopt_spec_exception we get "Unknown option: foo"
374 5 100       25 my $warning = ref $usage eq 'ARRAY' ? $usage->[0] : undef;
375              
376 5         11 my ($use, $options) = do {
377             # $plain_str under _getopt_spec_exception
378 5   66     26 my $out = $plain_str // $usage->text;
379              
380             # The default getopt order sucks, use reverse sort order
381 5         9022 chomp(my @out = split /^/, $out);
382 5         41 my $opt = join "\n", sort { $b cmp $a } @out[1 .. $#out];
  534         645  
383 5         37 ($out[0], $opt);
384             };
385 5         14 my $synopsis = do {
386 5         3250 require Pod::Usage;
387 5         162794 my $out;
388 5     5   166 open my $fh, '>', \$out;
  5         36  
  5         9  
  5         37  
389              
390 12     12   95 no warnings 'once';
  12         27  
  12         3389  
391              
392 5         3962 my $hailo = File::Spec->catfile($Hailo::Command::HERE_MOMMY, 'hailo');
393             # Try not to fail on Win32 or other odd systems which might have hailo.pl not hailo
394 5 50       100 $hailo = ((glob("$hailo*"))[0]) unless -f $hailo;
395 5         37 Pod::Usage::pod2usage(
396             -input => $hailo,
397             -sections => 'SYNOPSIS',
398             -output => $fh,
399             -exitval => 'noexit',
400             );
401 5         70059 close $fh;
402              
403 5         102 $out =~ s/\n+$//s;
404 5         28 $out =~ s/^Usage:/examples:/;
405              
406 5         26 $out;
407             };
408              
409             # Unknown option provided
410 5 100       24 print $warning if $warning;
411              
412 5         389 print <<"USAGE";
413             $use
414             $options
415             \n\tNote: All input/output and files are assumed to be UTF-8 encoded.
416             USAGE
417              
418             # Hack: We can't get at our object from here so we have to inspect
419             # @ARGV directly.
420 5 100       113 say "\n", $synopsis if "@ARGV" =~ /--examples/;
421              
422 5         0 exit 1;
423             }
424              
425             __PACKAGE__->meta->make_immutable;
426              
427             =head1 NAME
428              
429             Hailo::Command - Class for the L<hailo> command-line interface to L<Hailo>
430              
431             =head1 DESCRIPTION
432              
433             This is an internal class L<hailo> uses for its command-line
434             interface. See L<Hailo> for the public interface.
435              
436             =head1 PRIVATE METHODS
437              
438             =head2 C<run>
439              
440             Run Hailo in accordance with the the attributes that were passed to
441             it, this method is called by the L<hailo> command-line utility and the
442             Hailo test suite, its behavior is subject to change.
443              
444             =head1 AUTHOR
445              
446             E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org>
447              
448             =head1 LICENSE AND COPYRIGHT
449              
450             Copyright 2010 E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org>
451              
452             This program is free software, you can redistribute it and/or modify
453             it under the same terms as Perl itself.
454              
455             =cut