File Coverage

blib/lib/CGI/Application/Bouquet/Rose.pm
Criterion Covered Total %
statement 24 141 17.0
branch 0 18 0.0
condition 0 4 0.0
subroutine 8 13 61.5
pod 0 3 0.0
total 32 179 17.8


line stmt bran cond sub pod time code
1             package CGI::Application::Bouquet::Rose;
2              
3             # Author:
4             # Ron Savage
5             #
6             # Note:
7             # \t = 4 spaces || die.
8              
9 1     1   24199 use strict;
  1         2  
  1         39  
10 1     1   5 use warnings;
  1         10  
  1         38  
11              
12             require 5.005_62;
13              
14             require Exporter;
15              
16 1     1   5 use Carp;
  1         1  
  1         100  
17 1     1   620 use CGI::Application::Bouquet::Rose::Config;
  1         3  
  1         50  
18 1     1   1118 use File::Copy;
  1         2681  
  1         73  
19 1     1   6 use File::Path; # For mkpath and rmtree.
  1         2  
  1         57  
20 1     1   5 use File::Spec; # For copy.
  1         3  
  1         20  
21 1     1   1835 use HTML::Template;
  1         17559  
  1         2135  
22              
23             our @ISA = qw(Exporter);
24              
25             # Items to export into callers namespace by default. Note: do not export
26             # names by default without a very good reason. Use EXPORT_OK instead.
27             # Do not simply export all your public functions/methods/constants.
28              
29             # This allows declaration use CGI::Application::Bouquet::Rose ':all';
30             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
31             # will save memory.
32             our %EXPORT_TAGS = ( 'all' => [ qw(
33              
34             ) ] );
35              
36             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
37              
38             our @EXPORT = qw(
39              
40             );
41              
42             our $VERSION = '1.05';
43              
44             # -----------------------------------------------
45              
46             # Preloaded methods go here.
47              
48             # -----------------------------------------------
49              
50             # Encapsulated class data.
51              
52             {
53             my(%_attr_data) =
54             (
55             _docroot => undef,
56             _exclude => undef,
57             _module => 'Local::Wine',
58             _output_dir => undef,
59             _remove => 0,
60             _tmpl_path => undef,
61             _verbose => undef,
62             );
63              
64             sub _default_for
65             {
66 0     0     my($self, $attr_name) = @_;
67              
68 0           $_attr_data{$attr_name};
69             }
70              
71             sub _standard_keys
72             {
73 0     0     keys %_attr_data;
74             }
75             }
76              
77             # -----------------------------------------------
78              
79             sub log
80             {
81 0     0 0   my($self, $message) = @_;
82              
83 0 0         if ($$self{'_verbose'})
84             {
85 0           print STDERR "$message\n";
86             }
87              
88             } # End of log.
89              
90             # -----------------------------------------------
91              
92             sub new
93             {
94 0     0 0   my($class, $arg) = @_;
95 0           my($self) = bless({}, $class);
96 0           my($config) = CGI::Application::Bouquet::Rose::Config -> new();
97              
98 0           for my $attr_name ($self -> _standard_keys() )
99             {
100 0           my($arg_name) = $attr_name =~ /^_(.*)/;
101              
102 0 0         if (exists($$arg{$arg_name}) )
103             {
104 0           $$self{$attr_name} = $$arg{$arg_name};
105             }
106             else
107             {
108 0           $$self{$attr_name} = $self -> _default_for($attr_name);
109             }
110              
111 0 0         if (! defined $$self{$attr_name})
112             {
113             # The '' is for when the user chops the option out of the config file,
114             # and also refuses to specify a value on the command line.
115              
116 0           my($method) = "get_$arg_name";
117 0   0       $$self{$attr_name} = $config -> $method() || '';
118             }
119             }
120              
121 0           $$self{'_prefix'} = "$$self{'_module'}\::CGI";
122 0           $$self{'_dir_name'} = "$$self{'_output_dir'}\::$$self{'_prefix'}";
123 0           $$self{'_dir_name'} = File::Spec -> catdir(split(/::/, $$self{'_dir_name'}) );
124 0           $$self{'_db_module'} = "$$self{'_module'}\::Base\::DB";;
125 0           my($file) = $$self{'_db_module'};
126 0           $file = File::Spec -> catdir(split(/::/, $file) );
127              
128 0           $self -> log("docroot: $$self{'_docroot'}");
129 0           $self -> log("exclude: $$self{'_exclude'}");
130 0           $self -> log("module: $$self{'_module'}");
131 0           $self -> log("output_dir: $$self{'_output_dir'}");
132 0           $self -> log("prefix: $$self{'_prefix'}");
133 0           $self -> log("remove: $$self{'_remove'}");
134 0           $self -> log("tmpl_path: $$self{'_tmpl_path'}");
135 0           $self -> log("verbose: $$self{'_verbose'}");
136 0           $self -> log("Working dir: $$self{'_dir_name'}");
137 0           $self -> log("Rose::DB module: $$self{'_db_module'}");
138              
139             # Ensure we can load the user's Rose::DB-based module.
140              
141 0           eval "require '$file.pm'";
142 0 0         croak $@ if $@;
143              
144 0           return $self;
145              
146             } # End of new.
147              
148             # -----------------------------------------------
149              
150             sub run
151             {
152 0     0 0   my($self) = @_;
153              
154 0 0         if ($$self{'_remove'})
155             {
156 0           $self -> log("Removing: $$self{'_dir_name'}");
157 0           $self -> log('Success');
158              
159 0           rmtree([$$self{'_dir_name'}]);
160              
161 0           return 0;
162             }
163              
164 0           my($rose_db) = $$self{'_db_module'} -> new();
165 0           my(@table) = $rose_db -> list_tables();
166              
167 0           my($data);
168 0           my($module, @module);
169 0           my($name);
170 0           my($table);
171              
172 0           $self -> log('Processing tables:');
173              
174 0           for $table (@table)
175             {
176 0           ($module = ucfirst $table) =~ s/_(.)/\u$1/g;
177              
178 0           $self -> log("Table: $table. Module: $module");
179              
180 0           push @module,
181             {
182             module_name => $module,
183             table_name => $table,
184             }
185             }
186              
187 0           @module = sort{$$a{'module_name'} cmp $$b{'module_name'} } @module;
  0            
188              
189 0           $self -> log('Processing templates:');
190              
191 0           my(@component) = split(/::/, lc $$self{'_module'});
192 0           my($fcgi_name) = $component[- 1];
193 0           my(@real_tmpl_path) = split(/::/, lc $$self{'_module'});
194 0           my($real_tmpl_path) = File::Spec -> catdir('assets', 'templates');
195 0           $real_tmpl_path = File::Spec -> catdir($$self{'_docroot'}, $real_tmpl_path, @real_tmpl_path);
196              
197 0           $self -> log("Path to run-time templates: $real_tmpl_path");
198              
199             # Process: content.tmpl, main.menu.tmpl, search.form.tmpl, web.page.tmpl.
200              
201 0           my($output_dir_name) = File::Spec -> catdir('htdocs', 'assets', 'templates', @component);
202              
203 0           $self -> log("Creating $output_dir_name");
204              
205 0           mkpath([$output_dir_name], 0, 0744);
206              
207 0           my($output_file_name);
208              
209 0           for (qw/content.tmpl main.menu.tmpl search.form.tmpl web.page.tmpl/)
210             {
211 0           my($output_file_name) = File::Spec -> catfile($output_dir_name, $_);
212              
213 0           $self -> log("Copying $output_file_name");
214              
215 0           copy("$$self{'_tmpl_path'}/$_", $output_file_name);
216             }
217              
218             # Process: search.fcgi.tmpl.
219              
220 0           $output_dir_name = File::Spec -> catdir('htdocs', 'search');
221              
222 0           $self -> log("Creating $output_dir_name");
223              
224 0           mkpath([$output_dir_name], 0, 0744);
225              
226 0           $output_file_name = File::Spec -> catfile($output_dir_name, "$fcgi_name.fcgi");
227 0           my($template) = HTML::Template -> new(filename => File::Spec -> catfile($$self{'_tmpl_path'}, 'search.fcgi.tmpl') );
228              
229 0           $template -> param(prefix => $$self{'_prefix'});
230              
231 0           $self -> log("Creating $output_file_name");
232              
233 0 0         open(OUT, "> $output_file_name") || die "Can't open(> $output_file_name):$ !";
234 0           print OUT $template -> output();
235 0           close OUT;
236              
237             # Process: CGI/CGIApp.pm.
238              
239 0           $self -> log("Creating $$self{'_dir_name'}");
240              
241 0           mkpath([$$self{'_dir_name'}], 0, 0744);
242              
243 0           $output_file_name = File::Spec -> catfile($$self{'_dir_name'}, 'CGIApp.pm');
244 0           $template = HTML::Template -> new(filename => File::Spec -> catfile($$self{'_tmpl_path'}, 'cgiapp.pm.tmpl') );
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             C - 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-Wine-1.06/.
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             docroot: /var/www
334             exclude: ^(?:information_schema|pg_|sql_)
335             module: Local::Wine
336             output_dir: ./lib
337             prefix: Local::Wine::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::Wine::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::Wine
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::Wine as the basis for all discussions. See the FAQ for the availability
421             of the Local::Wine 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's 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's 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 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-Wine-1.06'
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::Wine
502              
503             Download Local::Wine from http://savage.net.au/Perl-modules/Local-Wine-1.06.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::Wine with your own code
517              
518             Short answer:
519              
520             =over 4
521              
522             =item Local::Wine
523              
524             =item Local::Wine::Config
525              
526             You can implement this module any way you want. It just has to provide the same methods.
527              
528             =item Local::Wine::Base::Create
529              
530             =item Local::Wine::DB
531              
532             This module will use the default type and domain, where 'type' and 'domain' are Rose concepts.
533              
534             =item Local::Wine::Object
535              
536             =back
537              
538             Long answer:
539              
540             See the docs for Local::Wine.
541              
542             =item Why isn't Local::Wine 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'd 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'll 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 don't 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 Author
636              
637             C was written by Ron Savage Iron@savage.net.auE> in 2008.
638              
639             Home page: http://savage.net.au/index.html
640              
641             =head1 Copyright
642              
643             Australian copyright (c) 2008, Ron Savage.
644              
645             All Programs of mine are 'OSI Certified Open Source Software';
646             you can redistribute them and/or modify them under the terms of
647             The Artistic License, a copy of which is available at:
648             http://www.opensource.org/licenses/index.html
649              
650             =cut