File Coverage

blib/lib/MVC/Neaf/CLI.pm
Criterion Covered Total %
statement 80 94 85.1
branch 21 42 50.0
condition 2 5 40.0
subroutine 15 15 100.0
pod 5 5 100.0
total 123 161 76.4


line stmt bran cond sub pod time code
1             package MVC::Neaf::CLI;
2              
3 2     2   551 use strict;
  2         4  
  2         60  
4 2     2   11 use warnings;
  2         3  
  2         93  
5             our $VERSION = '0.2901';
6              
7             =head1 NAME
8              
9             MVC::Neaf::CLI - Command line debugger and runner for Not Even A Framework
10              
11             =head1 DESCRIPTION
12              
13             Run your applications from command line, with various overrides.
14              
15             May be useful for command-line mode debugging (think CGI.pm)
16             as well as starting the app from command line.
17              
18             =head1 SYNOPSIS
19              
20             perl application.pl --list
21              
22             Print routes defined in the application.
23              
24             perl application.pl --post /foo/bar arg=42
25              
26             Simulate a request without running a server.
27              
28             perl application.pl --listen :5000
29              
30             Run a psgi server.
31              
32             =head1 OPTIONS
33              
34             =over
35              
36             =item * --help - display a brief usage message.
37              
38             =item * --list - print routes configured in the application.
39              
40             =item * --listen - start application as a standalone
41             plack servers. Any subsequent options compatible with plackup(1)
42             are allowed in this mode.
43              
44             =item * --post - set method to POST.
45              
46             =item * --method METHOD - set method to anything else.
47              
48             =item * --upload id=/path/to/file - add upload. Requires --post.
49              
50             =item * --cookie name="value" - add cookie.
51              
52             =item * --header name="value" - set http header.
53              
54             =item * --view - force (JS,TT,Dumper) view.
55              
56              
57             =back
58              
59             =head2 METHODS
60              
61             The usage doesn't expect these are called directly.
62              
63             But just for the sake of completeness...
64              
65             =cut
66              
67 2     2   1621 use Getopt::Long;
  2         21823  
  2         9  
68 2     2   319 use Carp;
  2         6  
  2         98  
69 2     2   13 use HTTP::Headers::Fast;
  2         3  
  2         46  
70 2     2   11 use File::Basename qw(basename);
  2         4  
  2         83  
71              
72 2     2   11 use MVC::Neaf;
  2         3  
  2         109  
73 2     2   11 use MVC::Neaf::Upload;
  2         5  
  2         2372  
74              
75             =head2 run( $app )
76              
77             Run the application.
78             This reads command line options, as shown in the summary above.
79              
80             $app is an MVC::Neaf object.
81              
82             B Spoils @AGRV.
83              
84             =cut
85              
86             sub run {
87 5     5 1 12 my ($self, $app) = @_;
88              
89 5         7 my %test;
90              
91 5 100       10 if (grep { $_ eq '--list' } @ARGV) {
  9         26  
92 1         4 return $self->list($app);
93             };
94 4 100       8 if (grep { $_ eq '--help' } @ARGV) {
  8         20  
95 1         3 return usage();
96             };
97              
98             # TODO 0.30 --view here so that view is forced in both modes
99 3 100       7 if (grep { $_ =~ /^--listen/ } @ARGV) {
  7         19  
100 1         4 return $self->serve( $app );
101             };
102              
103             GetOptions(
104 1     1   634 "post" => sub { $test{method} = 'POST' },
105             "method=s" => \$test{method},
106             "upload=s@" => \$test{upload},
107             "cookie=s@" => \$test{cookie},
108             "header=s@" => \$test{head},
109             "view=s" => \$test{view},
110             # TODO 0.30 --session to reduce hassle
111 2 50       21 ) or croak "Bad command line options in MVC::Neaf::CLI, see $0 --help";
112              
113 2         813 return $self->run_test($app, %test);
114             };
115              
116             =head2 serve( $app, @arg )
117              
118             Use L to start server.
119              
120             =cut
121              
122             sub serve {
123 1     1 1 3 my ($self, $app) = @_;
124              
125 1         18 require Plack::Runner;
126 1         6 my $runner = Plack::Runner->new;
127 1         6 $runner->parse_options( @ARGV );
128 1         9 $runner->run( $app->run );
129             };
130              
131             =head2 run_test( $app, %override )
132              
133             Call L's C.
134              
135             =cut
136              
137             sub run_test {
138 2     2 1 8 my ($self, $app, %test) = @_;
139              
140 2 100       7 $test{method} = uc $test{method} if $test{method};
141              
142             croak "--upload requires --post"
143 2 50 33     6 if $test{upload} and $test{method} ne 'POST';
144              
145 2 50       6 if (my $up = delete $test{upload}) {
146 0         0 foreach (@$up) {
147 0 0       0 /^(\S+?)=(.+)$/ or croak "Usage: --upload key=/path/to/file";
148 0         0 my ($key, $file) = ($1, $2);
149              
150 0 0       0 open my $fd, "<", $file
151             or die "Failed to open upload $key file $file: $!";
152              
153             # TODO 0.30 create temp file
154 0         0 $test{uploads}{$key} = MVC::Neaf::Upload->new(
155             id => $key, handle => $fd, filename => $file );
156             };
157             };
158              
159 2 50       5 if (my $cook = delete $test{cookie}) {
160 0         0 foreach (@$cook) {
161 0 0       0 /^(\S+?)=(.*)$/
162             or croak "Usage: --cookie name=value";
163 0         0 $test{cookie}{$1} = $2;
164             };
165             };
166              
167 2 50       3 if (my @head = @{ delete $test{head} || [] }) {
  2 50       13  
168             $test{header_in} = HTTP::Headers::Fast->new (
169 0 0       0 map { /^([^=]+)=(.*)$/ or croak "Bad header format"; $1=>$2 } @head
  0         0  
  0         0  
170             );
171             };
172              
173 2         17 my ($path, @rest) = @ARGV;
174 2   50     4 $path ||= '/';
175 2 50       5 if (@rest) {
176 0 0       0 my $sep = $path =~ /\?/ ? '&' : '?';
177 0         0 $path .= $sep . join '&', @rest;
178             };
179              
180 2 100       7 if (my $view = delete $test{view}) {
181 1         10 $app->set_forced_view( $view );
182             };
183              
184 2         10 my ($status, $head, $content) = $app->run_test( $path, %test );
185              
186 2         12 print STDOUT "Status $status\n";
187 2         6 print STDOUT $head->as_string, "\n";
188 2         198 print STDOUT $content;
189             };
190              
191             =head2 usage()
192              
193             Display help message.
194              
195             =cut
196              
197             sub usage {
198 1     1 1 31 my $script = basename($0);
199              
200 1         12 print <<"USAGE";
201             $script
202             is a web-application powered by Perl and MVC::Neaf (Not Even A Framework).
203             It will behave according to the CGI spec if run without parameters.
204             It will return a PSGI-compliant subroutine if require'd from other Perl code.
205             To run it as a standalone server, use --listen switch along with any
206             other switches recognized by plackup(1)
207             perl $script --listen :31415 <...>
208             To peek at the application, run
209             perl $script --list
210             To get this summary, run
211             perl $script --help
212             To invoke debugging mode, run:
213             perl $script [options] [/path] ...
214             Options may include:
215             --post - force request method to POST
216             --method METHOD - force method to anything else
217             --upload id=/path/to/file - add upload. Requires --post.
218             --cookie name="value" - add cookie.
219             --header name="value" - set http header.
220             --view - force (JS,TT,Dumper) view.
221             See `perldoc MVC::Neaf::CLI` for more.
222             USAGE
223              
224             };
225              
226             =head2 list()
227              
228             List registered Neaf routes.
229              
230             =cut
231              
232             sub list {
233 1     1 1 6 my ($self, $app) = @_;
234              
235 1         1 my %inverse_descr; # {path+printable descr} = [method, method]
236              
237             my $routes = $app->get_routes( sub {
238 6     6   15 my ($route, $path, $method) = @_;
239              
240 6         8 my @features;
241             # TODO 0.30 call methods instead of ->{}
242 6 50       15 if ( my $rex = $route->{path_info_regex} ) {
243 6         12 $rex = "$rex";
244 6 50       45 $rex =~ m#^\(.*?\((.*)\).*?\)$# and $rex = $1;
245 6         16 push @features, "/$rex"
246             };
247 0         0 my $param = join "&", map { "$_=$route->{param_regex}{$_}" }
248 6         10 sort keys %{ $route->{param_regex} };
  6         25  
249 6 50       13 push @features, "?$param" if $param;
250              
251             push @features, " # $route->{description}"
252 6 50       24 if $route->{description};
253              
254 6         15 my $descr = join "", $path, @features;
255              
256 6         9 push @{ $inverse_descr{$descr} }, $method;
  6         24  
257 1         13 } );
258              
259             # Convert available methods to printable format
260 1         17 $_ = join ",", sort @$_ for values %inverse_descr;
261              
262 1         5 foreach (sort keys %inverse_descr) {
263 2         14 printf "[%s] %s\n", $inverse_descr{$_}, $_;
264             };
265             };
266              
267             =head1 LICENSE AND COPYRIGHT
268              
269             This module is part of L suite.
270              
271             Copyright 2016-2023 Konstantin S. Uvarin C.
272              
273             This program is free software; you can redistribute it and/or modify it
274             under the terms of either: the GNU General Public License as published
275             by the Free Software Foundation; or the Artistic License.
276              
277             See L for more information.
278              
279             =cut
280              
281             1;