File Coverage

blib/lib/Catmandu/Interactive.pm
Criterion Covered Total %
statement 68 77 88.3
branch 18 28 64.2
condition 3 5 60.0
subroutine 13 13 100.0
pod 1 7 14.2
total 103 130 79.2


line stmt bran cond sub pod time code
1             package Catmandu::Interactive;
2              
3 1     1   742 use Catmandu::Sane;
  1         2  
  1         8  
4              
5             our $VERSION = '1.2020';
6              
7 1     1   403 use Catmandu;
  1         3  
  1         5  
8 1     1   246 use Moo;
  1         2  
  1         5  
9 1     1   337 use namespace::clean;
  1         3  
  1         6  
10              
11             has in => (
12             is => 'ro',
13             default => sub {
14             Catmandu::Util::io(\*STDIN);
15             }
16             );
17              
18             has out => (
19             is => 'ro',
20             default => sub {
21             Catmandu::Util::io(
22             \*STDOUT,
23             mode => 'w',
24             binmode => ':encoding(utf-8)'
25             );
26             }
27             );
28              
29             has silent => (is => 'ro');
30              
31             has exporter => (is => 'ro', default => sub {'YAML'});
32              
33             has exporter_args => (is => 'ro', default => sub {+{}});
34              
35             has header => (
36             is => 'ro',
37             default => sub {
38             "\e[36m\n"
39             . " A_A ____ _ _ \n"
40             . " (-.-) / ___|__ _| |_ _ __ ___ __ _ _ __ __| |_ _ \n"
41             . " |-| | | / _` | __| '_ ` _ \\ / _` | '_ \\ / _` | | | | \n"
42             . " / \\ | |__| (_| | |_| | | | | | (_| | | | | (_| | |_| | \n"
43             . " | | \\____\\__,_|\\__|_| |_| |_|\\__,_|_| |_|\\__,_|\\__,_|\n"
44             . " | || | | \\___ version: $Catmandu::VERSION \n"
45             . " \\_||_/_/ \e[0m\n"
46             . " \n"
47             . "Commands: | Interactive support is still \n"
48             . " \\h - the fix history | experimental. Run: \n"
49             . " \\r - repeat the previous fix | \$ catmandu run <your_fix_script> \n"
50             . " \\q - quit | to access all Catmandu features \n";
51             }
52             );
53              
54             has data => (is => 'rw', default => sub {+{}});
55              
56             has _history => (is => 'ro', default => sub {[]});
57              
58             sub run {
59 5     5 1 69 my $self = shift;
60              
61 5         11 my $keep_reading = 0;
62 5         10 my $buffer = '';
63              
64 5         21 $self->head;
65              
66 5         15 $self->prompt;
67              
68 5         19 while (my $line = $self->in->getline) {
69 10 100       315 if ($line =~ /^\\(.*)/) {
70 3 50       14 next if length $buffer;
71              
72 3         17 my ($command, $args) = split(/\s+/, $1, 2);
73              
74 3 100       20 if ($command eq 'h') {
    100          
    50          
75 1         7 $self->cmd_history;
76 1         34 $self->prompt('fix');
77 1         3 next;
78             }
79             elsif ($command eq 'r') {
80 1 50       3 if (@{$self->_history} > 0) {
  1         8  
81 1         5 $line = $self->_history->[-1];
82             }
83             else {
84 0         0 $self->prompt('fix');
85 0         0 next;
86             }
87             }
88             elsif ($command eq 'q') {
89 1         3 last;
90             }
91             else {
92 0         0 $self->error("unknown command $command");
93 0         0 $self->prompt('fix');
94 0         0 next;
95             }
96             }
97              
98 8 50       24 $line = "$buffer$line" if length $buffer;
99              
100 8 50       20 if (length $line) {
101 8         26 my ($fixes, $keep_reading, $error)
102             = $self->parse_fixes($line, $keep_reading);
103              
104 8 100       35 if ($error) {
    50          
105 2         4 $buffer = '';
106             }
107             elsif ($keep_reading == 0) {
108 6         134 my $fixer = Catmandu::Fix->new(fixes => $fixes);
109              
110 6         1835 $self->data($fixer->fix($self->data));
111 6         21 $self->export;
112              
113 6         11 push(@{$self->_history}, $line);
  6         22  
114              
115 6         259 $buffer = '';
116             }
117             else {
118 0         0 $buffer = $line;
119 0         0 $self->prompt('...');
120 0         0 next;
121             }
122             }
123              
124 8         32 $self->prompt('fix');
125             }
126             }
127              
128             sub cmd_history {
129 1     1 0 3 my ($self) = @_;
130              
131 1         6 $self->out->printf(join("", @{$self->_history}));
  1         11  
132             }
133              
134             sub head {
135 5     5 0 14 my ($self) = @_;
136              
137 5 50       25 $self->out->printf("%s\n", $self->header) unless $self->silent;
138             }
139              
140             sub error {
141 2     2 0 7 my ($self, $txt) = @_;
142 2 50       12 $self->out->print("ERROR: $txt\n") unless $self->silent;
143             }
144              
145             sub prompt {
146 14     14 0 35 my ($self, $txt) = @_;
147 14   100     52 $txt //= 'fix';
148              
149 14 50       71 $self->out->printf("\e[35m%s > \e[0m", $txt) unless $self->silent;
150             }
151              
152             sub export {
153 6     6 0 13 my ($self) = @_;
154             my $exporter = Catmandu->exporter(
155             $self->exporter,
156 6         17 %{$self->exporter_args},
  6         47  
157             fh => $self->out
158             );
159 6         131 $exporter->add($self->data);
160 6         143 $exporter->commit;
161             }
162              
163             sub parse_fixes {
164 8     8 0 23 my ($self, $string, $keep_reading) = @_;
165              
166 8         166 my $parser = Catmandu::Fix::Parser->new;
167              
168 8         478 my $fixes;
169 8         16 my $error = 0;
170              
171             try {
172 8     8   703 $fixes = $parser->parse($string);
173 6         101 $keep_reading = 0;
174             }
175             catch {
176 2 50 33 2   80 if (ref($_) eq 'Catmandu::FixParseError'
177             && $_->message
178             =~ /Can't use an undefined value as a SCALAR reference at/)
179             {
180 0         0 $keep_reading = 1;
181             }
182             else {
183 2         15 $_ =~ s/\n.*//g;
184 2         12 $self->error($_);
185 2         9 $error = 1;
186             }
187 8         64 };
188              
189 8         172 return ($fixes, $keep_reading, $error);
190             }
191              
192             1;
193              
194             __END__
195              
196             =pod
197              
198             =head1 NAME
199              
200             Catmandu::Interactive - An interactive command line interpreter of the Fix language
201              
202             =head1 SYNOPSIS
203              
204             # On the command line
205             catmandu run
206              
207             # Or, in Perl
208             use Catmandu::Interactive;
209             use Getopt::Long;
210              
211             my $exporter = 'YAML';
212              
213             GetOptions("exporter=s" => \$exporter);
214              
215             my $app = Catmandu::Interactive->new(exporter => $exporter);
216              
217             $app->run();
218              
219             =head1 DESCRIPTION
220              
221             This module provide a simple interactive interface to the Catmandu Fix language.
222              
223             =head1 CONFIGURATION
224              
225             =over
226              
227             =item in
228              
229             Read input from an IO::Handle
230              
231             =item out
232              
233             Write output to an IO::Handle
234              
235             =item silent
236              
237             If set true, then no headers or prompts are printed
238              
239             =item data
240              
241             A hash containing the input record
242              
243             =item exporter
244              
245             The name of an exporter package
246              
247             =item exporter_args
248              
249             The options for the exporter
250              
251             =back
252              
253             =head1 METHODS
254              
255             =head2 run
256              
257             Run the interactive environment.
258              
259             =head1 SEE ALSO
260              
261             L<Catmandu>
262              
263             =cut