File Coverage

blib/lib/Mojolicious/Command/generate/resources.pm
Criterion Covered Total %
statement 209 216 96.7
branch 38 46 82.6
condition 13 21 61.9
subroutine 14 14 100.0
pod 6 6 100.0
total 280 303 92.4


line stmt bran cond sub pod time code
1             package Mojolicious::Command::generate::resources;
2 3     3   72098 use Mojo::Base 'Mojolicious::Command', -signatures;
  3         116050  
  3         33  
3              
4 3     3   49073 use Mojo::Util qw(class_to_path decamelize camelize getopt);
  3         6  
  3         182  
5 3     3   17 use Mojo::File 'path';
  3         6  
  3         122  
6 3     3   16 use List::Util 'first';
  3         7  
  3         10120  
7              
8             our $AUTHORITY = 'cpan:BEROV';
9             our $VERSION = '0.21';
10              
11             has args => sub { {} };
12             has description =>
13             (path(__FILE__)->slurp() =~ /${\__PACKAGE__}\s+-\s+(.+)\n/)[0];
14              
15             has usage => sub { shift->extract_usage };
16             has _templates_path => '';
17             has '_db_helper';
18              
19             has routes => sub {
20             $_[0]->{routes} = [];
21             foreach my $t (@{$_[0]->args->{tables}}) {
22             my $controller = camelize($t);
23             my $route = decamelize($controller);
24             push @{$_[0]->{routes}},
25             {
26             route => "/$route",
27             via => ['GET'],
28             to => "$route#index",
29             name => "home_$route"
30             },
31             {
32             route => "/$route/create",
33             via => ['GET'],
34             to => "$route#create",
35             name => "create_$route",
36             },
37             {
38             route => "/$route/:id",
39             via => ['GET'],
40             to => "$route#show",
41             name => "show_$route"
42             },
43             {
44             route => "/$route",
45             via => ['POST'],
46             to => "$route#store",
47             name => "store_$route",
48             },
49             {
50             route => "/$route/:id/edit",
51             via => ['GET'],
52             to => "$route#edit",
53             name => "edit_$route"
54             },
55             {
56             route => "/$route/:id",
57             via => ['PUT'],
58             to => "$route#update",
59             name => "update_$route"
60             },
61             {
62             route => "/$route/:id",
63             via => ['DELETE'],
64             to => "$route#remove",
65             name => "remove_$route"
66             };
67             }
68             return $_[0]->{routes};
69             };
70              
71             my $_init = sub ($self, @options) {
72             return $self if $self->{_initialised};
73              
74             # Make sure the "tables" argument exists as an empty array
75             my $args = $self->args({tables => []})->args;
76              
77             getopt(
78             \@options,
79             'H|home_dir=s' => \$args->{home_dir},
80             'L|lib=s' => \$args->{lib},
81             'A|api_dir=s' => \$args->{api_dir},
82             'C|controller_namespace=s' => \$args->{controller_namespace},
83             'M|model_namespace=s' => \$args->{model_namespace},
84              
85             # TODO: 'O|overwrite' => \$args->{overwrite},
86             'T|templates_root=s' => \$args->{templates_root},
87             't|tables=s@' => \$args->{tables},
88             'D|db_helper=s' => \$args->{db_helper},
89             );
90              
91             @{$args->{tables}} = split(/\s*?\,\s*?/, join(',', @{$args->{tables}}));
92             Carp::croak $self->usage unless scalar @{$args->{tables}};
93              
94             my $app = $self->app;
95             $args->{controller_namespace} //= $app->routes->namespaces->[0];
96             $args->{model_namespace} //= ref($app) . '::Model';
97             $args->{home_dir} //= $app->home->realpath;
98             $args->{lib} //= path($args->{home_dir})->realpath->child('lib');
99             $args->{api_dir} //= path($args->{home_dir})->realpath->child('api');
100             $args->{templates_root}
101             //= path($app->renderer->paths->[0])->realpath->to_string;
102             $args->{db_helper} //= 'sqlite';
103              
104             # Find templates.
105             for my $path (@INC) {
106             my $templates_path
107             = path($path, 'Mojolicious/resources/templates/mojo/command/resources');
108             if (-d $templates_path) {
109             $self->_templates_path($templates_path);
110             last;
111             }
112             }
113              
114             # Find the used database helper. One of sqlite, pg, mysql or passed on the
115             # commandline
116             my @db_helpers = qw(sqlite pg mysql);
117             unshift @db_helpers, $args->{db_helper}
118             unless first sub { $_ eq $args->{db_helper} }, @db_helpers;
119             for (@db_helpers) {
120             if ($app->renderer->get_helper($_)) {
121             $self->_db_helper($_);
122             last;
123             }
124             }
125             if (!$self->_db_helper) {
126             die <<'MSG';
127             Guessing the used database wrapper helper failed. One of (@db_helpers) is
128             required. This application does not use any of the supported database helpers
129             nor the one provided as argument.
130             One of Mojo::Pg, Mojo::mysql or Mojo::SQLite must be used to generate models.
131             Aborting!..
132             MSG
133             }
134              
135             $self->{_initialised} = 1;
136              
137             return $self;
138             };
139              
140             # Returns the full path to the first found template.
141             # See http://localhost:3000/perldoc/Mojolicious/Renderer#template_path
142 38     38   122 sub _template_path ($self, $template) {
  38         74  
  38         66  
  38         59  
143 38         70 state $paths = $self->app->renderer->paths;
144 38         91 state $tmpls_path = $self->_templates_path;
145 38   100     95 -r and return $_ for map { path($_, $template) } @$paths, $tmpls_path;
  95         935  
146 0         0 return;
147             }
148              
149 2     2 1 418 sub run ($self, %options) {
  2         6  
  2         8  
  2         5  
150 2         12 $self->$_init(%options);
151 2         6 my $args = $self->args;
152 2         11 my $app = $self->app;
153              
154 2         8 my $wrapper_helpers = '';
155 2         4 for my $t (@{$args->{tables}}) {
  2         14  
156              
157 4         63 my $class_name = camelize($t);
158              
159             # Models
160 4         86 my $mclass = "$args->{model_namespace}::$class_name";
161 4         19 my $m_file = path($args->{lib}, class_to_path($mclass));
162 4         124 my $table_columns = $self->_get_table_columns($t);
163 4         35 my $template_args = {
164             %$args,
165             class => $mclass,
166             t => lc $t,
167             db_helper => $self->_db_helper,
168             columns => $table_columns,
169             column_info => $self->_column_info($t),
170             };
171 4         18 my $tmpl_file = $self->_template_path('m_class.ep');
172 4         335 $self->render_template_to_file($tmpl_file, $m_file, $template_args);
173              
174             # Controllers
175 4         1414 my $class = "$args->{controller_namespace}::$class_name";
176 4         23 my $c_file = path($args->{lib}, class_to_path($class));
177 4         143 $template_args = {
178             %$template_args,
179             class => $class,
180             validation => $self->generate_validation($t)
181             };
182 4         16 $tmpl_file = $self->_template_path('c_class.ep');
183 4         290 $self->render_template_to_file($tmpl_file, $c_file, $template_args);
184              
185              
186             # Templates
187 4         1362 my $template_dir = decamelize($class_name);
188 4         105 my $template_root = $args->{templates_root};
189              
190 4         18 my @views = qw(index create show edit);
191 4         13 for my $v (@views) {
192 16         4184 my $to_t_file = path($template_root, $template_dir, $v . '.html.ep');
193 16         284 my $tmpl = $self->_template_path($v . '.html.ep');
194 16         1140 $self->render_template_to_file($tmpl, $to_t_file, $template_args);
195             }
196 4         1082 $tmpl_file = $self->_template_path('_form.html.ep');
197 4         280 my $to_t_file = path($template_root, $template_dir, '_form.html.ep');
198 4         76 $template_args
199             = {%$template_args, fields => $self->generate_formfields($t)};
200 4         21 $self->render_template_to_file($tmpl_file, $to_t_file, $template_args);
201              
202             # Helpers
203 4         1152 $template_args = {%$template_args, class => $mclass};
204 4         18 $tmpl_file = $self->_template_path('helper.ep');
205 4         291 $wrapper_helpers
206             .= Mojo::Template->new->render_file($tmpl_file, $template_args);
207             } # end foreach tables
208              
209             # OpenAPI
210 2         46 $self->generate_openapi();
211              
212             # Routes and TODO
213 2         24 my $template_args
214             = {%$args, helpers => $wrapper_helpers, routes => $self->routes};
215 2         37 my $tmpl_file = $self->_template_path('TODO.ep');
216 2         194 my $todo_file = path($args->{home_dir}, 'TODO');
217 2         42 $self->render_template_to_file($tmpl_file, $todo_file, $template_args);
218 2         682 say qq{$/Please look at $todo_file for instructions to complete the setup.}
219             . qq{$/Have fun!$/};
220 2         59 return $self;
221             }
222              
223             # Returns an array reference of columns from the table
224 4     4   9 sub _get_table_columns ($self, $table) {
  4         8  
  4         9  
  4         5  
225 4         8 my @columns = map ({ $_->{COLUMN_NAME} } @{$self->_column_info($table)});
  16         245  
  4         15  
226 4         13 return \@columns;
227             }
228              
229 22     22   30972 sub _column_info ($self, $table) {
  22         42  
  22         41  
  22         35  
230 22         41 state $tci = {}; #tables column info
231 22         57 state $db_helper = $self->_db_helper;
232 22   66     135 $tci->{$table}
233             //= $self->app->$db_helper->db->dbh->column_info(undef, undef, $table, '%')
234             ->fetchall_arrayref({});
235 22         42486 return $tci->{$table};
236             }
237              
238 32     32 1 61 sub render_template_to_file ($self, $filename, $path, $args) {
  32         64  
  32         54  
  32         53  
  32         52  
  32         50  
239 32         200 my $out = Mojo::Template->new->render_file($filename, $args);
240 32         684 return $self->write_file($path, $out);
241             }
242              
243 4     4 1 11 sub generate_formfields ($self, $table) {
  4         7  
  4         10  
  4         7  
244 4         8 my $fields = '';
245 4         9 for my $col (@{$self->_column_info($table)}) {
  4         14  
246 16         38 my $name = $col->{COLUMN_NAME};
247 16 100       38 my $required = $col->{NULLABLE} ? '' : 'required => 1,';
248 16 100       47 my $size = $col->{COLUMN_SIZE} ? "size => $col->{COLUMN_SIZE}" : '';
249 16 100       42 if ($name eq 'id') {
250 4         21 $fields
251             .= qq|\n%=hidden_field '$name' => \$${table}->{id} if (\$action ne 'create');\n|;
252 4         11 next;
253             }
254 12 100 66     96 if ($col->{TYPE_NAME} =~ /char/i && $col->{COLUMN_SIZE} < 256) {
    100 33        
      66        
255 8         22 $fields .= <<"QQ";
256 8         51 %= label_for $name =>'${\ucfirst($name)}'\n
257             %= text_field $name => \$${table}->{$name}, $required $size\n
258             QQ
259 8         22 next;
260             }
261             elsif ( $col->{TYPE_NAME} =~ /text/i
262             || $col->{TYPE_NAME} =~ /char/i && $col->{COLUMN_SIZE} > 255)
263             {
264 2         9 $fields .= <<"QQ";
265 2         15 %= label_for '$name' => '${\ucfirst($name)}'\n
266             %= text_area '$name' => \$${table}->{$name}, $required $size\n
267             QQ
268 2         8 next;
269             }
270 2 50       22 if ($col->{TYPE_NAME} =~ /INT|FLOAT|DOUBLE|DECIMAL/i) {
271 2         9 $fields .= <<"QQ";
272 2         19 %= label_for $name => '${\ucfirst($name)}'\n
273             %= number_field $name => \$${table}->{$name}, $required $size\n
274             QQ
275 2         7 next;
276             }
277             }
278 4         42 return $fields;
279             }
280              
281 4     4 1 10 sub generate_validation ($self, $table) {
  4         10  
  4         10  
  4         7  
282 4         10 my $fields = '';
283 4         10 for my $col (@{$self->_column_info($table)}) {
  4         11  
284 16         34 my $name = $col->{COLUMN_NAME};
285 16 100       41 my $required = $col->{NULLABLE} ? 0 : 1;
286 16 100       44 my $size = $col->{COLUMN_SIZE} ? "size => $col->{COLUMN_SIZE}" : '';
287 16 100       48 if ($name eq 'id') {
288 4         12 $fields .= qq|\$v->required('id') if \$c->stash->{action} ne 'store';\n|;
289 4         9 next;
290             }
291              
292             $fields
293 12 100       39 .= $required
294             ? qq|\$v->required('$name', 'trim')|
295             : qq|\$v->optional('$name', 'trim')|;
296 12 100 66     77 if ($col->{TYPE_NAME} =~ /char/i && $col->{COLUMN_SIZE} < 256) {
297 8         26 $fields .= "->size(0, $col->{COLUMN_SIZE})";
298             }
299              
300 12 100       50 if ($col->{TYPE_NAME} =~ /INT|FLOAT|DOUBLE|DECIMAL/i) {
301 2         7 $fields .= q|->like(qr/\d+(\.\d+)?/)|;
302             }
303 12         38 $fields .= ';' . $/;
304             }
305 4         38 return $fields;
306             }
307              
308 2     2 1 5 sub generate_openapi ($self) {
  2         4  
  2         3  
309 2         4 my $args = {%{$self->args}};
  2         11  
310 2         22 my $api_tmpl_file = $self->_template_path('api.json.ep');
311 2         148 my $api_file = path($args->{api_dir}, 'api.json');
312 2         37 $args->{api_title} = ref($self->app) . ' OpenAPI';
313 2         17 $args->{api_paths} = {};
314 2         6 my $api_defs = {};
315 2         7 $args->{api_definitions} = $api_defs;
316              
317 2         4 for my $t (@{$args->{tables}}) {
  2         8  
318              
319             # Generate descriptions for table objects.
320 4         22 my $class_name = $args->{class_name} = camelize($t);
321 4         87 my $object_name = $class_name . 'Item';
322 4         21 $api_defs->{$class_name}{items}{'$ref'} = "#/definitions/$object_name";
323 4         13 $api_defs->{$class_name}{type} = 'array';
324             $api_defs->{$object_name}{description}
325 4         19 = "An object, representing one item of $class_name.";
326              
327             # Generate definition and parameter description for each column.
328 4         27 $self->generate_path_api($t, $api_defs->{$object_name}, $args);
329             }
330              
331 2         15 $self->render_template_to_file($api_tmpl_file, $api_file, $args);
332              
333             # Prettify generated JSON. With this step we also make sure the generated
334             # JSON is syntactically correct.
335              
336 2         1198 my $decoded = JSON::PP::decode_json(path($api_file)->slurp());
337 2         97843 $decoded->{definitions} = {%{$decoded->{definitions}}, %$api_defs};
  2         24  
338 2         17 path($api_file)->spurt(JSON::PP->new->utf8->pretty->encode($decoded));
339              
340 2         23932 return;
341             }
342              
343 4     4 1 11 sub generate_path_api ($self, $t, $object_api_def, $args) {
  4         9  
  4         9  
  4         7  
  4         8  
  4         7  
344 4         11 $object_api_def->{properties} = {};
345 4         11 $object_api_def->{required} = [];
346 4         9 my $params = {};
347 4         8 for my $col (@{$self->_column_info($t)}) {
  4         15  
348 16         40 my $name = $col->{COLUMN_NAME};
349 16   100     57 my $size = +$col->{COLUMN_SIZE} || 0; #must be number in JSON
350 16         33 my $type = $col->{TYPE_NAME};
351 16         39 my $param_name = camelize($name) . "Of$args->{class_name}";
352 16         262 $params->{$param_name} = {name => $name};
353              
354 16 100       51 unless ($col->{NULLABLE}) {
355 6         21 $params->{$param_name}{required} = Mojo::JSON->true;
356 6         43 push @{$object_api_def->{required}}, $name;
  6         18  
357             }
358              
359 16 100       116 if ($type =~ /char|text|clob/i) {
    50          
    0          
360 10 100       47 $object_api_def->{properties}{$name}
361             = {($size ? (maxLength => $size) : ()), type => 'string'};
362 10 100       37 $params->{$param_name}{maxLength} = $size if $size;
363 10         41 $params->{$param_name}{type} = 'string';
364             }
365             elsif ($type =~ /INT/i) {
366 6 100       37 $object_api_def->{properties}{$name}
367             = {($size ? (maxLength => $size) : ()), type => 'integer'};
368 6 100       19 $params->{$param_name}{maxLength} = $size if $size;
369 6         20 $params->{$param_name}{type} = 'integer';
370             }
371             elsif ($type =~ /FLOAT|DOUBLE|DECIMAL|NUMBER/i) {
372 0   0     0 my $scale = $col->{DECIMAL_DIGITS} || 0;
373 0         0 my $precision = $size - $scale;
374 0         0 my $pattern = qr/^-?\d{1,$precision}(?:\.\d{0,$scale})?$/x;
375 0 0       0 $object_api_def->{properties}{$name} = {
376             ($size ? (maxLength => $size) : ()),
377             type => 'number',
378             pattern => $pattern
379             };
380 0 0       0 $params->{$param_name}{maxLength} = $size if $size;
381 0         0 $params->{$param_name}{type} = 'number';
382             }
383              
384             #/$t/id
385 16 100       42 if ($name eq 'id') {
386 4         13 $params->{$param_name}{in} = 'path';
387 4         40 $params->{$param_name}{required} = Mojo::JSON->true;
388              
389             # GET and DELETE
390 4         36 push @{$args->{show_params}}, $params->{$param_name};
  4         22  
391              
392             # PUT
393 4         8 push @{$args->{update_params}}, $params->{$param_name};
  4         12  
394 4         11 next;
395             }
396              
397             # All other params are in form-data
398 12         34 $params->{$param_name}{in} = 'formData';
399              
400             # POST
401 12         18 push @{$args->{store_params}}, $params->{$param_name};
  12         34  
402              
403             # PUT
404 12         20 push @{$args->{update_params}}, $params->{$param_name};
  12         38  
405             } #end for my $col (@{$self->_column_info($t)})
406 4         18 $args->{t} = lc $t;
407              
408 4         12 for my $r (qw(home_ show_ store_ update_ remove_)) {
409 20     154   77 $args->{$r . 'route'} = first { $_->{name} =~ /^$r$t/ } @{$self->routes};
  154         690  
  20         63  
410             }
411 4         15 state $path_tmpl_file = $self->_template_path('path.json.ep');
412 4         160 my $ugly = Mojo::Template->new->render_file($path_tmpl_file, $args);
413              
414             # Make sure the generated JSON is syntactically correct.
415 4         108 my $decoded = JSON::PP::decode_json($ugly);
416              
417 4         94707 $args->{api_paths} = {%{$args->{api_paths}}, %$decoded};
  4         33  
418              
419             # Cleanup for the next table
420             delete $args->{$_}
421 4         49 for (
422             qw(t store_params update_params show_params
423             home_route show_route store_route update_route remove_route)
424             );
425 4         49 return;
426             }
427              
428             1;
429              
430              
431             =encoding utf8
432              
433             =head1 NAME
434              
435             Mojolicious::Command::generate::resources - Generate MVC & OpenAPI RESTful API files from database tables
436              
437             =head1 SYNOPSIS
438              
439             Usage: APPLICATION generate resources [OPTIONS]
440              
441             my_app.pl generate help resources # help with all available options
442             my_app.pl generate resources --tables users,groups
443             my_app.pl generate resources --tables users,groups -D dbx
444              
445             =head1 PERL REQUIREMENTS
446              
447             This command uses L, therefore Perl 5.20 is required.
448              
449             =head1 DESCRIPTION
450              
451             An usable release...
452              
453             L generates directory structure for
454             a fully functional L
455             L,
456             L and RESTful API specification in
457             L format based on
458             existing tables in your application's database.
459              
460             The purpose of this tool is to promote
461             L by generating
462             the boilerplate code for model (M), templates (V) and controller (C) and help
463             programmers to quickly create well structured, fully functional applications.
464             It assumes that you already have tables created in a database and you just want
465             to generate
466             L actions
467             for them.
468              
469             In the generated actions you will find eventually working code for reading,
470             creating, updating and deleting records from the tables you specified on the
471             command-line. The generated code is just boilerplate to give you a jump start,
472             so you can concentrate on writing your business-specific code. It is assumed
473             that you will modify the generated code to suit your specific needs. All the
474             generated code is produced from templates. You can copy the folder with the
475             templates, push it to C<@{$app-Erenderer-Epaths}> and modify to your
476             taste. Please look into the C folder of this distribution for examples.
477              
478             The command expects to find and will use one of the commonly used helpers
479             C, C C. The supported wrappers are respectively L,
480             L and L.
481              
482             =head1 OPTIONS
483              
484             Below are the options this command accepts, described in Getopt::Long notation.
485             Both short and long variants are shown as well as the types of values they
486             accept. All of them, beside C<--tables>, are guessed from your application and
487             usually do not need to be specified.
488              
489              
490             =head2 H|home_dir=s
491              
492             Optional. Defaults to Chome> (which is MyApp home directory). Used to
493             set the root directory to which the files will be dumped. If you set this
494             option, respectively the C and C folders will be created under the
495             new C. If you want them elsewhere, set these options explicitly.
496              
497             =head2 L|lib=s
498              
499             Optional. Defaults to Chome/lib> (relative to the C<--home_dir>
500             directory). If you installed L in some custom path and you wish to
501             generate your controllers into e.g. C, set this option.
502              
503             =head2 api_dir=s
504              
505             Optional. Directory where
506             the L C file will
507             be generated. Defaults to Chome/api> (relative to the C<--home_dir>
508             directory). If you installed L in some custom path and you wish to
509             generate your C files into for example C, set
510             this option explicitly.
511              
512             =head2 C|controller_namespace=s
513              
514             Optional. The namespace for the controller classes to be generated. Defaults to
515             Croutes-Enamespaces-E[0]>, usually L, where
516             MyApp is the name of your application. If you decide to use another namespace
517             for the controllers, do not forget to add it to the list
518             Croutes-Enamespaces> in C or your plugin
519             configuration file. Here is an example.
520              
521             # Setting the Controller class from which all controllers must inherit.
522             # See /perldoc/Mojolicious/#controller_class
523             # See /perldoc/Mojolicious/Guides/Growing#Controller-class
524             app->controller_class('MyApp::C');
525              
526             # Namespace(s) to load controllers from
527             # See /perldoc/Mojolicious#routes
528             app->routes->namespaces(['MyApp::C']);
529              
530             =head2 M|model_namespace=s
531              
532             Optional. The namespace for the model classes to be generated. Defaults to
533             L.
534              
535             =head2 T|templates_root=s
536              
537             Optional. Defaults to Crenderer-Epaths-E[0]>. This is usually
538             Chome/templates> directory. If you want to use another directory, do
539             not forget to add it to the Crenderer-Epaths> list in your
540             configuration file. Here is how to add a new directory to
541             Crenderer-Epaths> in C.
542              
543             # Application/site specific templates
544             # See /perldoc/Mojolicious/Renderer#paths
545             unshift @{app->renderer->paths}, $home->rel_file('site_templates');
546              
547             =head2 D|db_helper=s
548              
549             Optional. If passed, this method name will be used when generating Model
550             classes and helpers. The application is still expected to support the unified
551             API of the supported database adapters. This feature helps to generate code
552             for an application that wants to support all the three adaptors or if for
553             example tomorrow suddenly appears a Mojo::Oracle tiny wrapper around
554             L.
555              
556             =head2 t|tables=s@
557              
558             Mandatory. List of tables separated by commas for which controllers should be generated.
559              
560             =head1 SUPPORT
561              
562             Please report bugs, contribute and make merge requests on
563             L.
564              
565             =head1 ATTRIBUTES
566              
567             L inherits all attributes from
568             L and implements the following new ones.
569              
570             =head2 args
571              
572             Used for storing arguments from the command-line.
573              
574             my $args = $self->args;
575              
576             =head2 description
577              
578             my $description = $command->description;
579             $command = $command->description('Foo!');
580              
581             Short description of this command, used for the C<~$ mojo generate> commands
582             list.
583              
584             =head2 routes
585              
586             $self->routes;
587              
588             Returns an ARRAY reference containing routes, prepared after
589             C<$self-Eargs-E{tables}>. Suggested Perl code for the routes is dumped
590             in a file named TODO in C<--homedir> so you can copy and paste into your
591             application code.
592              
593             =head2 usage
594              
595             my $usage = $command->usage;
596             $command = $command->usage('Foo!');
597              
598             Usage information for this command, used for the help screen.
599              
600             =head1 METHODS
601              
602             L inherits all methods from
603             L and implements the following new ones.
604              
605             =head2 run
606              
607             Mojolicious::Command::generate::resources->new(app=>$app)->run(@ARGV);
608              
609             Run this command.
610              
611             =head2 render_template_to_file
612              
613             Renders a template from a file to a file using L. Parameters:
614             C<$tmpl_file> - full path tho the template file; C<$target_file> - full path to
615             the file to be written; C<$template_args> - a hash reference containing the
616             arguments to the template. See also L.
617              
618             $self->render_template_to_file($tmpl_file, $target_file, $template_args);
619              
620             =head2 generate_formfields
621              
622             Generates form-fields from columns information found in the respective table.
623             The result is put into C<_form.html.ep>. The programmer can then modify the
624             generated form-fields.
625              
626             $form_fields = $self->generate_formfields($table_name);
627              
628             =head2 generate_openapi
629              
630             Generates L file in json
631             format. The generated file is put in L. The filename is
632             C. This is the file which will be loaded by C.
633              
634             =head2 generate_path_api
635              
636             Generates API definitions and paths for each table. Invoked in
637             L. B C<$t> - the table name;
638             C<$api_defs_object> - the object API definition, based on the table name;
639             C<$tmpl_args> - the arguments for the templates. C<$api_defs_object> and
640             C<$tmpl_args> will be enriched with additional key-value pairs as required by
641             the OpenAPI specification. Returns C.
642              
643             =head2 generate_validation
644              
645             Generates code for the C<_validation> method in the respective controler.
646              
647             $validation_code = $self->generate_validation($table_name);
648              
649             =head1 TODO
650              
651             The work on the features may not go in the same order specified here. Some
652             parts may be fully implemented while others may be left for later.
653              
654             - Improve documentation.
655             - Add initial documentation stub to the generated classes.
656             - Improve templates to generate code to which is more ready to use.
657             - Append to the existing api.json if it already exists. More tests.
658              
659             =head1 AUTHOR
660              
661             Красимир Беров
662             CPAN ID: BEROV
663             berov@cpan.org
664              
665             =head1 COPYRIGHT
666              
667             This program is free software licensed under
668              
669             Artistic License 2.0
670              
671             The full text of the license can be found in the LICENSE file included with
672             this module.
673              
674             =head1 SEE ALSO
675              
676             L,
677             L,
678             L,
679             L,
680             L.
681              
682             =cut
683