File Coverage

blib/lib/CGI/Application/Bouquet/Rose.pm
Criterion Covered Total %
statement 30 133 22.5
branch 0 14 0.0
condition 0 2 0.0
subroutine 10 13 76.9
pod 0 3 0.0
total 40 165 24.2


line stmt bran cond sub pod time code
1             package CGI::Application::Bouquet::Rose;
2              
3 2     2   54895 use strict;
  2         9  
  2         47  
4 2     2   7 use warnings;
  2         4  
  2         38  
5              
6 2     2   14 use Carp;
  2         4  
  2         118  
7              
8 2     2   711 use CGI::Application::Bouquet::Rose::Config;
  2         6  
  2         56  
9              
10 2     2   942 use File::Copy;
  2         3758  
  2         94  
11 2     2   11 use File::Path; # For mkpath and rmtree.
  2         4  
  2         100  
12 2     2   11 use File::Spec; # For copy.
  2         4  
  2         31  
13              
14 2     2   1738 use HTML::Template;
  2         22605  
  2         61  
15              
16 2     2   12 use Moo;
  2         5  
  2         11  
17              
18 2     2   617 use Types::Standard qw/Int Str/;
  2         4  
  2         14  
19              
20             has db_module =>
21             (
22             default => sub {return ''},
23             is => 'rw',
24             isa => Str,
25             required => 0,
26             );
27              
28             has dir_name =>
29             (
30             default => sub {return ''},
31             is => 'rw',
32             isa => Str,
33             required => 0,
34             );
35              
36             has doc_root =>
37             (
38             default => sub {return ''},
39             is => 'rw',
40             isa => Str,
41             required => 0,
42             );
43              
44             has exclude =>
45             (
46             default => sub {return ''},
47             is => 'rw',
48             isa => Str,
49             required => 0,
50             );
51              
52             has module =>
53             (
54             default => sub {return 'Local::Wines'},
55             is => 'rw',
56             isa => Str,
57             required => 0,
58             );
59              
60             has output_dir =>
61             (
62             default => sub {return ''},
63             is => 'rw',
64             isa => Str,
65             required => 0,
66             );
67              
68             has prefix =>
69             (
70             default => sub {return ''},
71             is => 'rw',
72             isa => Str,
73             required => 0,
74             );
75              
76             has remove =>
77             (
78             default => sub {return 0},
79             is => 'rw',
80             isa => Int,
81             required => 0,
82             );
83              
84             has tmpl_path =>
85             (
86             default => sub {return ''},
87             is => 'rw',
88             isa => Str,
89             required => 0,
90             );
91              
92             has verbose =>
93             (
94             default => sub {return 0},
95             is => 'rw',
96             isa => Int,
97             required => 0,
98             );
99              
100             our $VERSION = '1.06';
101              
102             # -----------------------------------------------
103              
104             sub BUILD
105             {
106 0     0 0   my($self) = @_;
107              
108 0           $self -> prefix($self -> module . '\::CGI');
109 0           $self -> dir_name($self -> output_dir . '\::' . $self -> prefix);
110 0           $self -> dir_name(File::Spec -> catdir(split(/::/, $self -> dir_name) ) );
111 0           $self -> db_module($self -> module . '\::Base\::DB');
112              
113 0           my($file) = $self -> db_module;
114 0           $file = File::Spec -> catdir(split(/::/, $file) );
115              
116 0           $self -> log('doc_root: ' . $self -> doc_root);
117 0           $self -> log('exclude: ' . $self -> exclude);
118 0           $self -> log('module: ' . $self -> module);
119 0           $self -> log('output_dir: ' . $self -> output_dir);
120 0           $self -> log('prefix: ' . $self -> prefix);
121 0           $self -> log('remove: ' . $self -> remove);
122 0           $self -> log('tmpl_path: ' . $self -> tmpl_path);
123 0           $self -> log('verbose: ' . $self -> verbose);
124 0           $self -> log('Working dir: ' . $self -> dir_name);
125 0           $self -> log('Rose::DB module: ' . $self -> db_module);
126              
127             # Ensure we can load the user's Rose::DB-based module.
128              
129 0           eval "require '$file.pm'";
130 0 0         croak $@ if $@;
131              
132             } # End of BUILD.
133              
134             # -----------------------------------------------
135              
136             sub log
137             {
138 0     0 0   my($self, $message) = @_;
139              
140 0 0         if ($self -> verbose)
141             {
142 0           print STDERR "$message\n";
143             }
144              
145             } # End of log.
146              
147             # -----------------------------------------------
148              
149             sub run
150             {
151 0     0 0   my($self) = @_;
152              
153 0 0         if ($self -> remove)
154             {
155 0           $self -> log('Removing: ' . $self -> dir_name);
156 0           $self -> log('Success');
157              
158 0           rmtree([$self -> dir_name]);
159              
160 0           return 0;
161             }
162              
163 0           my($rose_db) = $self -> db_module -> new();
164 0           my(@table) = $rose_db -> list_tables();
165              
166 0           my($data);
167 0           my($module, @module);
168 0           my($name);
169 0           my($table);
170              
171 0           $self -> log('Processing tables:');
172              
173 0           for $table (@table)
174             {
175 0           ($module = ucfirst $table) =~ s/_(.)/\u$1/g;
176              
177 0           $self -> log("Table: $table. Module: $module");
178              
179 0           push @module,
180             {
181             module_name => $module,
182             table_name => $table,
183             }
184             }
185              
186 0           @module = sort{$$a{'module_name'} cmp $$b{'module_name'} } @module;
  0            
187              
188 0           $self -> log('Processing templates:');
189              
190 0           my(@component) = split(/::/, lc $self -> module);
191 0           my($fcgi_name) = $component[- 1];
192 0           my(@real_tmpl_path) = split(/::/, lc $self -> module);
193 0           my($real_tmpl_path) = File::Spec -> catdir('assets', 'templates');
194 0           $real_tmpl_path = File::Spec -> catdir($self -> doc_root, $real_tmpl_path, @real_tmpl_path);
195              
196 0           $self -> log("Path to run-time templates: $real_tmpl_path");
197              
198             # Process: content.tmpl, main.menu.tmpl, search.form.tmpl, web.page.tmpl.
199              
200 0           my($output_dir_name) = File::Spec -> catdir('htdocs', 'assets', 'templates', @component);
201              
202 0           $self -> log("Creating $output_dir_name");
203              
204 0           mkpath([$output_dir_name], 0, 0744);
205              
206 0           my($output_file_name);
207              
208 0           for (qw/content.tmpl main.menu.tmpl search.form.tmpl web.page.tmpl/)
209             {
210 0           my($output_file_name) = File::Spec -> catfile($output_dir_name, $_);
211              
212 0           $self -> log("Copying $output_file_name");
213              
214 0           copy($self -> tmpl_path . "/$_", $output_file_name);
215             }
216              
217             # Process: search.fcgi.tmpl.
218              
219 0           $output_dir_name = File::Spec -> catdir('htdocs', 'search');
220              
221 0           $self -> log("Creating $output_dir_name");
222              
223 0           mkpath([$output_dir_name], 0, 0744);
224              
225 0           $output_file_name = File::Spec -> catfile($output_dir_name, "$fcgi_name.fcgi");
226 0           my($template) = HTML::Template -> new(filename => File::Spec -> catfile($self -> tmpl_path, 'search.fcgi.tmpl') );
227              
228 0           $template -> param(prefix => $self -> prefix);
229              
230 0           $self -> log("Creating $output_file_name");
231              
232 0 0         open(OUT, "> $output_file_name") || die "Can't open(> $output_file_name):$ !";
233 0           print OUT $template -> output();
234 0           close OUT;
235              
236             # Process: CGI/CGIApp.pm.
237              
238 0           $self -> log('Creating ' . $self -> dir_name);
239              
240 0           mkpath([$self -> dir_name], 0, 0744);
241              
242 0           $output_file_name = File::Spec -> catfile($self -> dir_name, 'CGIApp.pm');
243 0           $template = HTML::Template -> new(filename => File::Spec -> catfile($self -> tmpl_path, 'cgiapp.pm.tmpl') );
244              
245 0           $template -> param(module => $self -> module);
246 0           $template -> param(prefix => $self -> prefix);
247 0           $template -> param(tmpl_path => $real_tmpl_path);
248              
249 0           $self -> log("Creating $output_file_name");
250              
251 0 0         open(OUT, "> $output_file_name") || die "Can't open(> $output_file_name):$ !";
252 0           print OUT $template -> output();
253 0           close OUT;
254              
255             # Process: CGI/Dispatcher.pm.
256              
257 0           $output_file_name = File::Spec -> catfile($self -> dir_name, 'Dispatcher.pm');
258 0           $template = HTML::Template -> new(filename => File::Spec -> catfile($self -> tmpl_path, 'dispatcher.pm.tmpl') );
259              
260 0           $template -> param(prefix => $self -> prefix);
261              
262 0           $self -> log("Creating $output_file_name");
263              
264 0 0         open(OUT, "> $output_file_name") || die "Can't open(> $output_file_name):$ !";
265 0           print OUT $template -> output();
266 0           close OUT;
267              
268             # Process: CGI/MainMenu.pm.
269              
270 0           $output_file_name = File::Spec -> catfile($self -> dir_name, 'MainMenu.pm');
271 0           $template = HTML::Template -> new(filename => File::Spec -> catfile($self -> tmpl_path, 'main.menu.pm.tmpl') );
272              
273 0           $template -> param(prefix => $self -> prefix);
274              
275 0           $self -> log("Creating $output_file_name");
276              
277 0 0         open(OUT, "> $output_file_name") || die "Can't open(> $output_file_name):$ !";
278 0           print OUT $template -> output();
279 0           close OUT;
280              
281             # Process: CGI/CGIApp/*.pm (1 per table).
282              
283 0           $output_dir_name = File::Spec -> catdir($self -> dir_name, 'CGIApp');
284              
285 0           $self -> log("Creating $output_dir_name");
286              
287 0           mkpath([$output_dir_name], 0, 0744);
288              
289 0           $template = HTML::Template -> new(filename => File::Spec -> catfile($self -> tmpl_path, 'generator.pl.tmpl') );
290              
291 0           $template -> param(dir_name => $output_dir_name);
292 0           $template -> param(module_loop => \@module);
293 0           $template -> param(module => $self -> module);
294 0           $template -> param(tmpl_path => $self -> tmpl_path);
295 0   0       $template -> param(verbose => $self -> verbose || 0);
296              
297 0           print $template -> output();
298              
299 0           $self -> log('Success');
300              
301 0           return 0;
302              
303             } # End of run.
304              
305             # -----------------------------------------------
306              
307             1;
308              
309             =head1 NAME
310              
311             CGI::Application::Bouquet::Rose - Generate a set of CGI::Application-based classes
312              
313             =head1 Synopsis
314              
315             =head2 Security Warning
316              
317             The generated code allows SQL to be entered via a CGI form. This means you absolutely
318             must restrict usage of the generated code to trusted persons.
319              
320             =head2 Sample Code
321              
322             Step 1: Run the steps from the synopsis for Rose::DBx::Bouquet.
323             Remember, the current dir /must/ still be Local-Wines-1.29/.
324              
325             Step 2: Edit:
326             o lib/Rose/DBx/Bouquet/.htcgi.bouquet.conf
327             o lib/Local/Wine/.htwine.conf
328              
329             Step 3: Run the third code generator (see scripts/rosy):
330             shell> scripts/run.cgi.app.gen.pl > scripts/run.cgi.pl
331              
332             Step 4: This is the log from run.cgi.app.gen.pl:
333             doc_root: /var/www
334             exclude: ^(?:information_schema|pg_|sql_)
335             module: Local::Wines
336             output_dir: ./lib
337             prefix: Local::Wines::CGI
338             remove: 0
339             tmpl_path: ../CGI-Application-Bouquet-Rose/templates
340             verbose: 1
341             Working dir: lib/Local/Wine/CGI
342             Rose::DB module: Local::Wines::Base::DB
343             Processing tables:
344             Table: grape. Module: Grape
345             Table: vineyard. Module: Vineyard
346             Table: wine. Module: Wine
347             Table: wine_maker. Module: WineMaker
348             Processing templates:
349             Path to run-time templates: /var/www/assets/templates/local/wine
350             Creating htdocs/assets/templates/local/wine
351             Copying htdocs/assets/templates/local/wine/content.tmpl
352             Copying htdocs/assets/templates/local/wine/main.menu.tmpl
353             Copying htdocs/assets/templates/local/wine/search.form.tmpl
354             Copying htdocs/assets/templates/local/wine/web.page.tmpl
355             Creating htdocs/search
356             Creating htdocs/search/wine.fcgi
357             Creating lib/Local/Wine/CGI
358             Creating lib/Local/Wine/CGI/CGIApp.pm
359             Creating lib/Local/Wine/CGI/Dispatcher.pm
360             Creating lib/Local/Wine/CGI/MainMenu.pm
361             Creating lib/Local/Wine/CGI/CGIApp
362             Success
363              
364             Step 5: Run the fourth code generator:
365             shell> perl -Ilib scripts/run.cgi.pl
366              
367             Step 6: This is the log from run.cgi.pl:
368             Processing CGI::Application-based modules:
369             Updating htdocs/assets/templates/local/wine/main.menu.tmpl
370             Generated lib/Local/Wine/CGI/CGIApp/Grape.pm
371             Generated lib/Local/Wine/CGI/CGIApp/Vineyard.pm
372             Generated lib/Local/Wine/CGI/CGIApp/Wine.pm
373             Generated lib/Local/Wine/CGI/CGIApp/WineMaker.pm
374             Success
375              
376             Step 7: Install the templates:
377             shell> scripts/install.templates.pl
378              
379             Step 8: Install Local::Wines
380             shell> perl Build.PL
381             shell> perl Build
382             shell> sudo perl Build install
383              
384             Step 9: Install the FastCGId script:
385             shell> sudo cp -r htdocs/search /var/www
386             shell> sudo chmod a+x /var/www/search/wine.fcgi
387              
388             Step 10: Patch httpd.conf (see httpd/httpd.conf.patch):
389             LoadModule fcgid_module modules/mod_fcgid.so
390            
391             SetHandler fcgid-script
392             Options ExecCGI
393             Order deny,allow
394             Deny from all
395             Allow from 127.0.0.1
396            
397              
398             Step 11: Restart Apache:
399             shell> sudo /etc/init.d/apache2 restart
400              
401             Step 12: Use a web client to hit http://127.0.0.1/search/wine.fcgi
402             Start searching!
403              
404             =head1 Description
405              
406             C is a pure Perl module.
407              
408             It uses a database schema, and code generated by C, to generate
409             C source code.
410              
411             The result is an CGI script which implements a search engine customised to the given database.
412              
413             At run-time, a menu of database tables is displayed in the web client, and when one is chosen, a CGI form
414             is displayed which allows the user to enter any value for any column. These values are the search keys, and
415             may include SQL tokens such as '%' and '_'.
416              
417             The N rows returned by the search are displayed as a HTML table, and you can page back and forth around this
418             data set.
419              
420             This documentation uses Local::Wines as the basis for all discussions. See the FAQ for the availability
421             of the Local::Wines distro.
422              
423             =head1 Distributions
424              
425             This module is available as a Unix-style distro (*.tgz).
426              
427             See http://savage.net.au/Perl-modules.html for details.
428              
429             See http://savage.net.au/Perl-modules/html/installing-a-module.html for
430             help on unpacking and installing.
431              
432             =head1 Constructor and initialization
433              
434             new(...) returns an object of type C.
435              
436             This is the class contructor.
437              
438             Usage: C<< CGI::Application::Bouquet::Rose -> new() >>.
439              
440             This method takes a hashref of options.
441              
442             Call C as C<< new({option_1 => value_1, option_2 => value_2, ...}) >>.
443              
444             Available options:
445              
446             =over 4
447              
448             =item doc_root
449              
450             This takes a directory name, which is the name of your web server document root.
451              
452             If not specified, the value defaults to the value in lib/Rose/DBx/Bouquet/.htcgi.bouquet.conf.
453              
454             The default value is /var/www, which suits me.
455              
456             =item exclude
457              
458             This takes a regexp (without the //) of table names to exclude.
459              
460             Code is generated for each table which is I excluded.
461              
462             If not specified, the value defaults to the value in lib/CGI/Application/Bouquet/Rose/.htcgi.bouquet.conf.
463              
464             The default value is ^(?:information_schema|pg_|sql_), which suits users of C.
465              
466             =item output_dir
467              
468             This takes the path where the output modules are to be written.
469              
470             If not specified, the value defaults to the value in lib/CGI/Application/Bouquet/Rose/.htcgi.bouquet.conf.
471              
472             The default value is ./lib.
473              
474             =item tmpl_path
475              
476             This is the path to the C template directory.
477              
478             These templates are input to the code generation process.
479              
480             If not specified, the value defaults to the value in lib/CGI/Application/Bouquet/Rose/.htcgi.bouquet.conf.
481              
482             The default value is ../CGI-Application-Bouquet-Rose/templates.
483              
484             Note: The point of the '../' is because I assume you have done 'cd Local-Wines-1.29'
485             or the equivalent for whatever module you are working with.
486              
487             =item verbose
488              
489             This takes either a 0 or a 1.
490              
491             Write more or less progress messages to STDERR, during code generation.
492              
493             The default value is 0.
494              
495             =back
496              
497             =head1 FAQ
498              
499             =over 4
500              
501             =item Availability of Local::Wines
502              
503             Download Local::Wines from http://savage.net.au/Perl-modules/Local-Wines-1.29.tgz
504              
505             The schema is at: http://savage.net.au/Perl-modules/wine.png
506              
507             C ships with C in the bin/ directory, whereas
508             C ships with various programs in the scripts/ directory.
509              
510             Files in the /bin directory get installed via 'make install'. Files in the scripts/ directory
511             are not intended to be installed; they are only used during the code-generation process.
512              
513             Note also that 'make install' installs lib/CGI/Application/Bouquet/Rose/.htcgi.bouquet.conf, and
514             - depending on your OS - you may need to change its permissions in order to edit it.
515              
516             =item Minimum modules required when replacing Local::Wines with your own code
517              
518             Short answer:
519              
520             =over 4
521              
522             =item Local::Wines
523              
524             =item Local::Wines::Config
525              
526             You can implement this module any way you want. It just has to provide the same methods.
527              
528             =item Local::Wines::Base::Create
529              
530             =item Local::Wines::DB
531              
532             This module will use the default type and domain, where 'type' and 'domain' are Rose concepts.
533              
534             =item Local::Wines::Object
535              
536             =back
537              
538             Long answer:
539              
540             See the docs for Local::Wines.
541              
542             =item Why is Local::Wines not on CPAN?
543              
544             To avoid the problem of people assuming it can be downloaded and used just like any other module.
545              
546             =item How does this module interact with Rose?
547              
548             See the FAQ for .
549              
550             =item What is the syntax used for search terms at run-time?
551              
552             SQL. So, to find the name of a grape starting with S, you type S%.
553              
554             And yes, I know there is the potential for sabotage with such a system. This means you absolutely
555             must restrict usage of the generated code to trusted persons.
556              
557             =item Can I search in Primary Keys?
558              
559             Yes. They are text fields like any other column.
560              
561             =item What happens when I enter several seach terms on the CGI form?
562              
563             The values are combined with 'and'. There is no provision for using 'or'.
564              
565             =item Do you ever write to the database?
566              
567             No.
568              
569             My intention is to provide CRUD features one day.
570              
571             =item How do you handle sessions?
572              
573             Sessions are not implemented, simply because they are not needed.
574              
575             The only data which needs to be passed from CGI form to form is the database paging state,
576             and that is done with a hidden form field.
577              
578             =item How are HTML entities handled?
579              
580             Output from the database is encoded using HTML::Entities::Interpolate.
581              
582             =item A note on option management
583              
584             You will see a list of option names and default values near the top of this file, in the hash %_attr_data.
585              
586             Some default values are undef, and some are scalars.
587              
588             My policy is this:
589              
590             =over 4
591              
592             =item If the default is undef...
593              
594             Then the real default comes from a config file, and is obtained via the *::Config module.
595              
596             =item If the default is a scalar...
597              
598             Then that scalar is the default, and cannot be over-ridden by a value from a conf file.
599              
600             =back
601              
602             =item But why have such a method of handling options?
603              
604             Because I believe it makes sense for the end user (you, dear reader), to have the power to change
605             configuration values without patching the source code. Hence the conf file.
606              
607             However, for some values, I do not think it makes sense to do that. So, for those options, the default
608             value is a scalar in the source code of this module.
609              
610             =item Is this option arrangement permanent?
611              
612             Yes.
613              
614             Options whose defaults are already in the config file will never be deleted from that file.
615              
616             However, options not currently in the config file may be made available via the config file,
617             depending on feedback.
618              
619             Also, the config file is an easy way of preparing for more user-editable options.
620              
621             =back
622              
623             =head1 Method: log($message)
624              
625             If C was called as C<< new({verbose => 1}) >>, write the message to STDERR.
626              
627             If C was called as C<< new({verbose => 0}) >> (the default), do nothing.
628              
629             =head1 Method: run()
630              
631             Do everything.
632              
633             See C for an example of how to call C.
634              
635             =head1 Machine-Readable Change Log
636              
637             The file Changes was converted into Changelog.ini by L.
638              
639             =head1 Version Numbers
640              
641             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
642              
643             =head1 Repository
644              
645             L
646              
647             =head1 Support
648              
649             Email the author, or log a bug on RT:
650              
651             L.
652              
653             =head1 Author
654              
655             C was written by Ron Savage Iron@savage.net.auE> in 2008.
656              
657             L.
658              
659             =head1 Copyright
660              
661             Australian copyright (c) 2008, Ron Savage.
662              
663             All Programs of mine are 'OSI Certified Open Source Software';
664             you can redistribute them and/or modify them under the terms of
665             The Perl License, a copy of which is available at:
666             http://dev.perl.org/licenses/
667              
668             =cut