File Coverage

blib/lib/Mojolicious/Command/scaffold.pm
Criterion Covered Total %
statement 12 121 9.9
branch 0 84 0.0
condition 0 60 0.0
subroutine 4 15 26.6
pod 9 9 100.0
total 25 289 8.6


line stmt bran cond sub pod time code
1             package Mojolicious::Command::scaffold;
2 1     1   58192 use Mojo::Base 'Mojo::Console';
  1         160923  
  1         7  
3              
4 1     1   55779 use Getopt::Long;
  1         2  
  1         6  
5 1     1   106 use List::Util qw(any);
  1         2  
  1         47  
6 1     1   4 use Mojo::File 'path';
  1         2  
  1         2489  
7              
8             our $VERSION = '0.0.4';
9              
10             # Short description
11             has 'description' => <
12             Scaffold a command, controller, migration, routes, task and a template
13             EOF
14              
15             has 'options' => sub {
16             my $options = {
17             rollback => 0,
18             };
19              
20             GetOptions($options,
21             'action=s',
22             'base|b:s',
23             'create',
24             'name=s',
25             'pretend',
26             'preview',
27             'rollback',
28             'routes',
29             'table=s',
30             'template',
31             'tests',
32             );
33              
34             return $options;
35             };
36              
37             has 'piling' => sub {
38             my $self = shift;
39              
40             my @parts = split('::', $self->options->{ name });
41              
42             my $application = ref $self->app;
43             my $name = $parts[-1];
44             my $package_path = join('/', @parts[0..$#parts - 1]);
45             my $package_name = sprintf('%s%s', ($package_path ? $package_path . '::' : ''), $name);
46             $package_name =~ s/\//::/g;
47             my $template_path = join('/', map(lc($_), @parts)) . '/';
48              
49             return {
50             application => $application,
51             file_name => sprintf('%s.pm', $name),
52             package_path => $package_path,
53             package_name => $package_name,
54             template_path => $template_path,
55             };
56             };
57              
58             # Short usage message
59             has 'usage' => <
60             Usage: mojo scaffold
61             mojo scaffold controller --name="Mobile" --tests --routes --template
62             mojo scaffold routes --name="Mobile" --tests
63             mojo scaffold template --name="Mobile"
64             EOF
65              
66             sub command {
67 0     0 1   my $self = shift;
68              
69 0   0       $self->options->{ name } ||= $self->required->ask('What is the name of the command?');
70              
71             # command
72 0           my $application = $self->piling->{ application };
73 0 0         my $path = "lib/$application/Command/" . ($self->piling->{ package_path } ? $self->piling->{ package_path } . '/' : '');
74              
75             $self->process($path, $self->piling->{ file_name }, $self->stub('stubs/command.pm.stub', [
76             'Application::Command' => $self->piling->{ application } . '::Command',
77             Stub => $self->piling->{ package_name },
78 0           ]));
79             }
80              
81             sub controller {
82 0     0 1   my $self = shift;
83              
84 0   0       $self->options->{ name } ||= $self->ask('What is the name of the controller?');
85 0   0       $self->options->{ action } ||= $self->ask('What is the name of the action?', 'action');
86 0   0       $self->options->{ tests } ||= $self->confirm(sprintf('Do you want to create tests for <%s> controller?', $self->options->{ name }), 'yes');
87 0   0       $self->options->{ routes } ||= $self->confirm(sprintf('Do you want to create routes for <%s> controller?', $self->options->{ name }), 'yes');
88 0   0       $self->options->{ template } ||= $self->confirm(sprintf('Do you want to create default template for <%s> controller?', $self->options->{ name }), 'yes');
89            
90             # Controller
91 0           my $application = $self->piling->{ application };
92 0 0         my $controller_path = "lib/$application/Controller/" . ($self->piling->{ package_path } ? $self->piling->{ package_path } . '/' : '');
93              
94             my $stub = $self->stub('stubs/Controller.pm.stub', [
95             'Application::Controller' => $self->piling->{ application } . '::Controller',
96             Stub => $self->piling->{ package_name },
97             'sub action' => sprintf('sub %s', $self->options->{ action }),
98             'template/action' => sprintf('template/%s', $self->options->{ action }),
99             'template/' => $self->piling->{ template_path },
100 0           ]);
101              
102             # Replace base controller
103 0           my $base = $self->options->{ base };
104 0 0         $stub =~ s/Mojolicious::Controller/$base/g if ($base);
105              
106 0           $self->process($controller_path, $self->piling->{ file_name }, $stub);
107              
108 0 0         if ($self->options->{ tests }) {
109             # Controller Tests
110 0 0         my $controller_tests_path = "t/lib/TestCase/$application/Controller/" . ($self->piling->{ package_path } ? $self->piling->{ package_path } . '/' : '');
111              
112             $self->process($controller_tests_path, $self->piling->{ file_name }, $self->stub('stubs/ControllerTests.pm.stub', [
113             'Application::Controller' => $self->piling->{ application } . '::Controller',
114             Stub => $self->piling->{ package_name },
115 0           'sub test_action' => sprintf('sub test_%s', $self->options->{ action }),
116             ]));
117             }
118              
119 0 0         $self->routes if ($self->options->{ routes });
120 0 0         $self->template if ($self->options->{ template });
121             }
122              
123             sub migration {
124 0     0 1   my $self = shift;
125              
126 0           my @columns;
127              
128 0   0       $self->options->{ create } ||= $self->confirm('Are you going to create a new table?', 'no');
129 0 0 0       $self->options->{ table } ||= $self->required->ask(sprintf("What's the name of the table that you are you going to %s?", ($self->options->{ create } ? 'create' : 'alter')));
130              
131 0           while (my $field = $self->ask("What's the name of the column?")) {
132 0           my $column = { field => $field };
133              
134 0           $column->{ type } = $self->choice("What's the type of '$field' column?", [
135             'bigint', 'blob', 'date', 'datetime', 'int', 'longtext', 'mediumblob', 'text', 'timestamp', 'tinyint', 'varchar'
136             ]);
137              
138 0 0   0     if (any { $_ eq $column->{ type } } qw(bigint int varchar)) {
  0            
139 0           $column->{ length } = $self->ask("What's the length for '$field' column?");
140             }
141              
142 0 0   0     if (any { $_ eq $column->{ type } } qw(bigint int)) {
  0            
143 0 0         $column->{ unsigned } = $self->confirm("Is '$field' an unsigned column?", 'yes') if (!$column->{ length });
144 0           $column->{ autoincrement } = $self->confirm("Is '$field' an auto-increment column?", 'no');
145             }
146              
147 0 0         $column->{ default } = $self->ask("What's the default value for '$field' column?") if (!$column->{ autoincrement });
148 0 0 0       $column->{ nullable } = $self->confirm("Allow null values for '$field' column?", 'yes') if (!$column->{ autoincrement } && !$column->{ default });
149 0 0         $column->{ after } = $self->ask("Create the column '$field' after?") if (!$self->options->{ create });
150              
151 0           push(@columns, $column);
152             }
153              
154 0           my @up;
155             my @down;
156              
157 0           for my $column (@columns) {
158             push(@up, sprintf("%s `%s` %s%s %s %s %s %s %s",
159             ($self->options->{ create } ? '' : 'ADD COLUMN'),
160             $column->{ field },
161             $column->{ type },
162             ($column->{ length } ? sprintf('(%s)', $column->{ length }) : ''),
163             ($column->{ unsigned } ? 'unsigned' : ''),
164             ($column->{ nullable } ? 'NULL' : 'NOT NULL'),
165             (length($column->{ default }) ? sprintf('DEFAULT %s', $column->{ default }) : ($column->{ nullable } ? 'DEFAULT NULL' : '')),
166             ($column->{ autoincrement } ? 'AUTO_INCREMENT' : ''),
167 0 0         ($column->{ after } ? sprintf('AFTER `%s`', $column->{ after }) : ''),
    0          
    0          
    0          
    0          
    0          
    0          
    0          
168             ));
169              
170 0 0         if (!$self->options->{ create }) {
171 0           push(@down, sprintf("DROP COLUMN `%s`", $column->{ field }));
172             }
173             }
174              
175 0 0         if ($self->options->{ create }) {
176 0           push(@up, sprintf('PRIMARY KEY (`%s`)', $self->required->ask("What's the primary key?")));
177 0           @down = (sprintf('DROP TABLE IF EXISTS `%s`', $self->options->{ table }));
178             }
179              
180 0           my $sql = "-- UP\n";
181              
182             $sql .= $self->options->{ create } ?
183             sprintf("CREATE TABLE `%s` (\n", $self->options->{ table }) :
184 0 0         sprintf("ALTER TABLE `%s`\n", $self->options->{ table });
185              
186 0 0         $sql .= sprintf("%s%s;\n", join(",\n", @up), ($self->options->{ create } ? "\n)" : ''));
187              
188 0   0       $self->options->{ rollback } ||= $self->confirm('Would you like to have UP and DOWN version for this migration?', 'no');
189              
190 0 0         if ($self->options->{ rollback }) {
191 0           $sql .= "\n-- DOWN\n";
192              
193             $sql .= $self->options->{ create } ?
194             sprintf('DROP TABLE IF EXISTS `%s`', $self->options->{ table }) :
195 0 0         sprintf("ALTER TABLE `%s`\n%s;\n", $self->options->{ table }, join(",\n", @down));
196             }
197              
198             my $file = sprintf('%s_%s_table_%s.sql',
199             $self->app->calendar->ymd,
200             ($self->options->{ create } ? 'create' : 'alter'),
201             $self->options->{ table },
202 0 0         );
203              
204 0           $self->process('db_migrations/', $file, $sql);
205             }
206              
207             sub process {
208 0     0 1   my ($self, $path, $file, $content) = @_;
209              
210 0           my $save = !$self->options->{ pretend };
211              
212 0 0 0       if ($self->options->{ pretend } || $self->options->{ preview }) {
213 0           $self->info(sprintf("%s%s\n", $path, $file));
214 0           $self->newline('===================================================');
215 0           $self->warn($content);
216             }
217              
218 0 0         if ($self->options->{ preview }) {
219 0           $save = $self->confirm('Looks good?');
220             }
221              
222 0 0         if ($save) {
223 0           my $template = Mojo::File->new($path)->make_path;
224 0           $template->child($file)->spurt($content);
225             }
226             }
227              
228             sub routes {
229 0     0 1   my $self = shift;
230              
231 0   0       $self->options->{ name } ||= $self->required->ask('What is the name of the controller?');
232 0   0       $self->options->{ action } ||= $self->ask('What is the name of the action?', 'action');
233 0   0       $self->options->{ tests } ||= $self->confirm(sprintf('Do you want to create tests for <%s> routes?', $self->options->{ name }), 'yes');
234              
235             # Routes
236 0           my $application = $self->piling->{ application };
237 0 0         my $routes_path = "lib/$application/Routes/" . ($self->piling->{ package_path } ? $self->piling->{ package_path } . '/' : '');
238              
239             $self->process($routes_path, $self->piling->{ file_name }, $self->stub('stubs/Routes.pm.stub', [
240             Application => $self->piling->{ application },
241             Stub => $self->piling->{ package_name },
242 0           "action => 'action'" => sprintf("action => '%s'", $self->options->{ action }),
243             ]));
244              
245 0 0         if ($self->options->{ tests }) {
246             # Routes Tests
247 0 0         my $routes_tests_path = "t/lib/TestCase/$application/Routes/" . ($self->piling->{ package_path } ? $self->piling->{ package_path } . '/' : '');
248              
249             $self->process($routes_tests_path, $self->piling->{ file_name }, $self->stub('stubs/RoutesTests.pm.stub', [
250             Application => $self->piling->{ application },
251             Stub => $self->piling->{ package_name },
252 0           ]));
253             }
254             }
255              
256             sub run {
257 0     0 1   my ($self, $choice, @args) = @_;
258            
259 0 0 0       $choice = undef if ($choice eq '--pretend' || $choice eq '--preview');
260 0   0       $choice ||= $self->choice('What are you looking to scaffold?', ['command', 'controller', 'migration', 'routes', 'task', 'template'], 'controller');
261              
262 0 0         if ($self->can($choice)) {
263 0           $self->$choice();
264 0           $self->success("Done\n");
265              
266 0           return;
267             }
268              
269 0           $self->error("Unknown choice\n");
270             }
271              
272             sub stub {
273 0     0 1   my ($self, $filename, $replacements) = @_;
274              
275 0           my $file = $self->app->home->rel_file($filename);
276 0           my $content;
277              
278 0 0         if ((-e $file)) {
279 0           $content = $file->slurp;
280             } else {
281 0           $content = path(__FILE__)->sibling('resources', $filename)->slurp;
282             }
283              
284 0           for (my $i=0; $i < @$replacements; $i++) {
285 0           my ($find, $replace) = ($replacements->[$i], $replacements->[++$i]);
286 0 0         $content =~ s/$find/$replace/g if $replace;
287             }
288              
289 0           return $content;
290             }
291              
292             sub task {
293 0     0 1   my $self = shift;
294            
295 0   0       $self->options->{ name } ||= $self->required->ask('What is the name of the task?');
296 0   0       $self->options->{ tests } ||= $self->confirm(sprintf('Do you want to create tests for <%s> task?', $self->options->{ name }), 'yes');
297              
298             # Task
299 0           my $application = $self->piling->{ application };
300 0 0         my $task_path = "lib/$application/Tasks/" . ($self->piling->{ package_path } ? $self->piling->{ package_path } . '/' : '');
301              
302             $self->process($task_path, $self->piling->{ file_name }, $self->stub('stubs/Task.pm.stub', [
303             Application => $self->piling->{ application },
304             Stub => $self->piling->{ package_name },
305 0           ]));
306              
307 0 0         if ($self->options->{ tests }) {
308             # Routes Tests
309 0 0         my $task_tests_path = "t/lib/TestCase/$application/Tasks/" . ($self->piling->{ package_path } ? $self->piling->{ package_path } . '/' : '');
310              
311             $self->process($task_tests_path, $self->piling->{ file_name }, $self->stub('stubs/TaskTests.pm.stub', [
312             Application => $self->piling->{ application },
313             Stub => $self->piling->{ package_name },
314 0           ]));
315             }
316             }
317              
318             sub template {
319 0     0 1   my $self = shift;
320              
321 0   0       $self->options->{ name } ||= $self->required->ask('What is the name of the controller?');
322 0   0       $self->options->{ action } ||= $self->ask('What is the name of the action?', 'action');
323              
324              
325             # Template
326             $self->process('templates/' . $self->piling->{ template_path }, $self->options->{ action } . '.html.ep', $self->stub('stubs/template.html.ep.stub', [
327             Stub => $self->piling->{ package_name },
328 0           ]));
329             }
330              
331             1;
332              
333             =encoding utf8
334              
335             =head1 NAME
336              
337             Mojolicious::Command::scaffold - Scaffold command
338              
339             =head1 SYNOPSIS
340              
341             Usage: APPLICATION scaffold [OPTIONS]
342              
343             ./myapp.pl scaffold
344             ./myapp.pl scaffold controller
345              
346             Options:
347             'base|b:s',
348             'controller',
349             'name=s',
350             'pretend',
351             'preview',
352             'routes',
353             'table=s',
354             'template',
355             'tests',
356              
357             -b, --base Base controller
358              
359             --name Name of the controller, command that you are trying to create
360              
361             --action Default action name for controller
362              
363             --pretent When it's present, the command will just output the content
364             of the files that are about to be created
365              
366             --preview When it's present, a confirmation message will appear before
367             saving a file
368              
369             --create Tells if you are creating/altering a table
370             --table The name of the table
371              
372             --rollback Will create the UP and DOWN version of the migration
373              
374             --tests Will create tests
375              
376             --template Will create a template
377              
378             =head1 DESCRIPTION
379              
380             L helps you easily create commands, controllers, migrations, routes, tasks and templates
381              
382             See L for a list of commands that are
383             available by default.
384              
385             =head1 ATTRIBUTES
386              
387             L inherits all attributes from
388             L and implements the following new ones.
389              
390             =head2 description
391              
392             my $description = $scaffold->description;
393             $scaffold = $scaffold->description('Foo');
394              
395             =head2 options
396              
397             my $options = $scaffold->options;
398             $scaffold = $scaffold->options({
399             action => 'action',
400             base => 'Base::Class'
401             create => 1, # 0
402             name => 'My::Name'
403             pretend => 0, # 1
404             preview => 1, # 0
405             rollback => 1, # 0
406             routes => 1, # 0
407             table => 'some_table_name`,
408             template => 1, # 0
409             tests => 1, # 0
410             });
411              
412             A list of options used by scaffold command to decide what needs creating.
413              
414             =head2 piling
415              
416             my $piling = $scaffold->piling;
417             $scaffold = $scaffold->piling({ ... });
418              
419             A list of options used by scaffold command to decide how to create things.
420              
421             =head2 usage
422              
423             my $usage = $scaffold->usage;
424             $scaffold = $scaffold->usage('Foo');
425              
426             Usage information for this command, used for the help screen.
427              
428             =head1 METHODS
429              
430             L inherits all methods from
431             L and implements the following new ones.
432              
433             =head2 command
434              
435             $scaffold->command(@ARGV);
436              
437             Scaffold a mojolicious command
438              
439             =head2 controller
440              
441             $scaffold->controller(@ARGV);
442              
443             Scaffold a mojolicious controller
444              
445             =head2 migration
446              
447             $scaffold->migration(@ARGV);
448              
449             Scaffold a migration
450              
451             =head2 process
452              
453             $scaffold->process($path, $file, $content);
454              
455             Process content for a file
456              
457             =head2 routes
458              
459             $scaffold->routes(@ARGV);
460              
461             Scaffold a mojolicious routes file
462              
463             =head2 run
464              
465             $scaffold->run(@ARGV);
466              
467             Run scaffold command.
468              
469             =head2 stub
470              
471             $scaffold->stub($filename, $replacements);
472              
473             Open stub file and replace things
474              
475             =head2 task
476              
477             $scaffold->task(@ARGV);
478              
479             Scaffold a mojolicious task file
480              
481             =head2 template
482              
483             $scaffold->template(@ARGV);
484              
485             Scaffold a mojolicious template file
486              
487             =head1 SEE ALSO
488              
489             L, L, L, L, L.
490              
491             =cut