File Coverage

blib/lib/Bigtop/Parser.pm
Criterion Covered Total %
statement 41 41 100.0
branch 4 4 100.0
condition 3 3 100.0
subroutine 10 10 100.0
pod n/a
total 58 58 100.0


line stmt bran cond sub pod time code
1             package Bigtop::Parser;
2 41     41   6651476 use strict; use warnings;
  41     41   103  
  41         1872  
  41         254  
  41         80  
  41         1381  
3              
4 41     41   216 use File::Find;
  41         78  
  41         3288  
5 41     41   244 use File::Spec;
  41         95  
  41         1230  
6 41     41   55080 use Data::Dumper;
  41         550026  
  41         3269  
7 41     41   390 use Carp;
  41         83  
  41         3528  
8              
9 41     41   210699 use Bigtop::Grammar;
  41         9731  
  41         2315  
10 41     41   41237 use Bigtop::Keywords;
  41         139  
  41         3999  
11 41     41   34592 use Bigtop::ScriptHelp;
  41         155  
  41         73376  
12              
13             # These don't work since we moved grammar to bigtop.grammar.
14             # $::RD_TRACE = 1;
15             # $::RD_HINT = 1;
16             # Set them in Grammar.pm directly under the use Parse::RecDescent statement.
17              
18             my $ident_counter = 0;
19             my $parser;
20             my %valid_keywords;
21             my %keyword_for;
22              
23             #---------------------------------------------------------------------
24             # Methods which add and validate keywords in the grammar
25             #---------------------------------------------------------------------
26              
27             sub add_valid_keywords {
28 24     24   37 my $class = shift;
29 24         33 my $type = shift;
30 24         43 my $caller = caller( 0 );
31              
32 24         27 my %callers;
33              
34             KEYWORD:
35 24         40 foreach my $statement ( @_ ) {
36 101         216 my $keyword = $statement->{keyword};
37              
38 101         182 my $seen_it = $valid_keywords{ $type }{ $keyword };
39              
40 101         6794 $valid_keywords{ $type }{ $keyword }++;
41              
42 101 100 100     444 next KEYWORD if ( defined $statement->{type}
43             and $statement->{type} eq 'deprecated' );
44              
45 99         101 push @{ $keyword_for{ $type }{ $keyword }{ callers } }, $caller;
  99         326  
46              
47 99 100       318 next KEYWORD if $seen_it;
48              
49 83         96 push @{ $keyword_for{ $type }{ statements } }, $statement;
  83         2909  
50             }
51             }
52              
53             BEGIN {
54             Bigtop::Parser->add_valid_keywords(
55             Bigtop::Keywords->get_docs_for(
56             'config',
57             qw( engine template_engine plugins base_dir app_dir )
58             )
59             );
60              
61             # register no_gen as a keyword for (almost) all block types
62             # sequence and table are not included since SQL happens all at once
63             foreach my $keyword_type qw( app controller method ) {
64             Bigtop::Parser->add_valid_keywords(
65             Bigtop::Keywords->get_docs_for(
66             $keyword_type,
67             'no_gen',
68             )
69             );
70             }
71              
72             # to allow a table to be described, but to be omitted from either
73             # a Model or SQL output
74              
75             Bigtop::Parser->add_valid_keywords(
76             Bigtop::Keywords->get_docs_for( 'table', 'not_for' )
77             );
78              
79             Bigtop::Parser->add_valid_keywords(
80             Bigtop::Keywords->get_docs_for( 'field', 'not_for' )
81             );
82             }
83              
84             sub is_valid_keyword {
85             my $class = shift;
86             my $type = shift;
87             my $keyword = shift;
88              
89             return $valid_keywords{$type}{$keyword};
90             }
91              
92             sub get_valid_keywords {
93             my $class = shift;
94             my $type = shift;
95              
96             my %trailer_for = (
97             config => 'or a valid backend block',
98             app => 'or a valid block (controller, sequence, ' .
99             'config, table, or join_table)',
100             controller => 'or a valid method block',
101             table => 'or a valid field block',
102             );
103              
104             my %extras_for = (
105             app => [ 'literal' ],
106             );
107              
108             my @extra_expected = @{ $extras_for{ $type } }
109             if ( defined $extras_for{ $type } );
110              
111             my $trailer = $trailer_for{ $type };
112              
113             my @expected = sort @extra_expected, keys %{ $valid_keywords{ $type } };
114             push( @expected, $trailer ) if $trailer;
115              
116             return @expected;
117             }
118              
119             sub get_keyword_docs {
120              
121             foreach my $type ( keys %keyword_for ) {
122             my @sorted = sort { $a->{ sort_order } <=> $b->{ sort_order } }
123             @{ $keyword_for{ $type }{ statements } };
124              
125             $keyword_for{ $type }{ statements } = \@sorted;
126             }
127              
128             return \%keyword_for;
129             }
130              
131             #---------------------------------------------------------------------
132             # The ident factory
133             #---------------------------------------------------------------------
134              
135             sub get_ident {
136             $ident_counter++;
137              
138             return "ident_$ident_counter";
139             }
140              
141             #---------------------------------------------------------------------
142             # The import method
143             #---------------------------------------------------------------------
144              
145             sub import {
146             my $class = shift;
147             my @modules = @_;
148              
149             foreach my $module ( @modules ) {
150             my ( $type, $name, $template ) = split /=/, $module;
151              
152             # build full path to module and require it
153             my $module_file = File::Spec->catfile(
154             'Bigtop', 'Backend', $type, "$name.pm"
155             );
156             require $module_file;
157              
158             my $package = 'Bigtop::Backend::' . $type . '::' . $name;
159              
160             # allow caller to fill in a template file
161             if ( $class->gen_mode && $package->can( 'setup_template' ) ) {
162             $package->setup_template( $template );
163             }
164             }
165             }
166              
167             my $gen_mode = 1;
168             sub gen_mode {
169             my $class = shift;
170              
171             return $gen_mode;
172             }
173              
174             sub set_gen_mode {
175             my $class = shift;
176             my $value = shift;
177              
178             $gen_mode = $value;
179              
180             return $gen_mode;
181             }
182              
183             #---------------------------------------------------------------------
184             # Methods which handle errors
185             #---------------------------------------------------------------------
186              
187             sub fatal_keyword_error {
188             my $class = shift;
189             my $args = shift;
190              
191             my $bad_keyword = $args->{ bad_keyword };
192             my $diag_text = $args->{ diag_text };
193             my $bigtop_input_linenum = $args->{ input_linenum };
194             my $keyword_type = $args->{ type };
195             my @expected = @{ $args->{ expected } };
196              
197             $diag_text =~ s/\n.*//sg; # trim to one line
198              
199             # see if they forget a block name
200             my %block_types = (
201             config => {},
202             app => { controller => 1,
203             sequence => 1,
204             config => 1,
205             table => 1,
206             join_table => 1,
207             },
208             controller => { method => 1, },
209             table => { field => 1, },
210             );
211              
212             if ( $block_types{ $keyword_type }{ $bad_keyword } ) {
213             die "Error: missing name for $bad_keyword block (line "
214             . "$bigtop_input_linenum) near:\n"
215             . "$diag_text\n";
216             }
217              
218             my $expected = join ', ', @expected;
219              
220             die "Error: invalid keyword '$bad_keyword' (line $bigtop_input_linenum) " . "near:\n"
221             . "$diag_text\n"
222             . "I was expecting one of these: $expected.\n";
223             }
224              
225             sub fatal_error_two_lines {
226             my $class = shift;
227             my $message = shift;
228             my $diag_text = shift;
229             my $bigtop_input_linenum = shift;
230              
231             $diag_text = substr $diag_text, 0, 65;
232              
233             die "Error: $message\n "
234             . "on line $bigtop_input_linenum near:\n$diag_text\n";
235             }
236              
237             #---------------------------------------------------------------------
238             # The grammar has been moved to the generated Bigtop::Grammar
239             #---------------------------------------------------------------------
240              
241             #---------------------------------------------------------------------
242             # The preprocessor (comment stripper)
243             #---------------------------------------------------------------------
244             #
245             # The single parameter should be a bigtop string. It will be modified
246             # in place, by having all comments removed. A comment is a line where
247             # the first non-whitespace char is #
248             #
249             # Returns: a hash each key is a line numbers whose value is the comment
250             # which was on that line of the source.
251             #
252             sub preprocess {
253              
254             # first capture all the comments
255             my %retval;
256             my $line_count = 0;
257             foreach my $line ( split /\n/, $_[0] ) {
258             if ( $line =~ /^\s*#.*/ ) {
259             $retval{ $line_count } = $line;
260             }
261             $line_count++;
262             }
263              
264             # then expunge all comments
265             $_[0] =~ s/^\s*#.*//mg;
266              
267             return \%retval;
268             }
269              
270             #---------------------------------------------------------------------
271             # Methods which parse input
272             #---------------------------------------------------------------------
273              
274             sub get_parser {
275             $parser = Bigtop::Grammar->new() if ( not defined $parser );
276              
277             return $parser;
278             }
279              
280             # This is the method that bigtop uses.
281             sub gen_from_file {
282             my $class = shift;
283             my $bigtop_file = shift;
284             my $create = shift;
285             my @gen_list = shift;
286              
287             my $BIGTOP_FILE;
288             open ( $BIGTOP_FILE, '<', $bigtop_file )
289             or die "Couldn't read bigtop file $bigtop_file: $!\n";
290              
291             my $bigtop_string = join '', <$BIGTOP_FILE>;
292              
293             close $BIGTOP_FILE;
294              
295             my $flags;
296             if ( $create ) {
297             $flags = "-c $bigtop_file @gen_list";
298             }
299              
300             return $class->gen_from_string(
301             {
302             bigtop_string => $bigtop_string,
303             bigtop_file => $bigtop_file,
304             create => $create,
305             build_list => \@gen_list,
306             flags => $flags,
307             }
308             );
309             }
310              
311             # This is the method that gen_from_file uses.
312             sub gen_from_string {
313             my $class = shift;
314             my $opts = shift;
315              
316             my $bigtop_string = $opts->{ bigtop_string };
317             my $bigtop_file = $opts->{ bigtop_file };
318             my $create = $opts->{ create };
319             my $flags = $opts->{ flags };
320             my @args = @{ $opts->{ build_list } };
321              
322             my $config = $class->parse_config_string( $bigtop_string );
323              
324             my $build_types = $class->load_backends( $bigtop_string, $config );
325              
326             # build the whole parse tree
327             my $bigtop_tree = $class->parse_string( $bigtop_string );
328              
329             # check to see if an app wide no_gen is in effect
330             my $lookup = $bigtop_tree->{application}{lookup};
331              
332             if ( defined $lookup->{app_statements}{no_gen}
333             and
334             $lookup->{app_statements}{no_gen}
335             ) {
336             warn "Warning: app level is marked no_gen, skipping generation\n";
337             return;
338             }
339              
340             # make the build directory (if needed)
341             my $build_dir = _build_app_home_dir( $bigtop_tree, $create );
342              
343             # make sure we are in the right place
344             # if there are init backends, ask the first one to verify build dir
345             if ( $config->{__BACKENDS__}{ Init } ) {
346             my $module = join '::', (
347             'Bigtop',
348             'Backend',
349             'Init',
350             $config->{__BACKENDS__}{ Init }[0]{__NAME__}
351             );
352             $module->validate_build_dir( $build_dir, $bigtop_tree, $create );
353             }
354             else {
355             my $init_str = 'Init=Std';
356             $class->import( $init_str );
357             my $init_pack = 'Bigtop::Backend::Init::Std';
358             $init_pack->validate_build_dir( $build_dir, $bigtop_tree, $create );
359             }
360              
361             # replace all with a list of all available backends
362             my @gen_list;
363             foreach my $gen_type ( @args ) {
364             if ( $gen_type eq 'all' ) { push @gen_list, @{ $build_types}; }
365             else { push @gen_list, $gen_type; }
366             }
367              
368             # generate the files
369             my @available_backends = sort keys %{ $config->{ __BACKENDS__ } };
370             unshift @available_backends, 'all';
371             my $backends_called = 0;
372             foreach my $gen_type ( @gen_list ) {
373              
374             BACKEND:
375             foreach my $backend ( @{ $config->{__BACKENDS__}{ $gen_type } } ) {
376              
377             next BACKEND
378             if ( defined $backend->{no_gen} and $backend->{no_gen} );
379              
380             my $module = join '::', (
381             'Bigtop', 'Backend', $gen_type, $backend->{__NAME__}
382             );
383             my $method = "gen_$gen_type";
384             $module->$method( $build_dir, $bigtop_tree, $bigtop_file, $flags );
385              
386             $backends_called++;
387             }
388             }
389              
390             if ( $backends_called == 0 ) {
391             rmdir $build_dir;
392             _purge_inline();
393             die "I didn't build anything, please check for no_gen in\n"
394             . "$bigtop_file and choose from:\n"
395             . " @available_backends\n";
396             }
397              
398             return ( $bigtop_tree->get_appname, $build_dir );
399             }
400              
401             sub load_backends {
402             my $class = shift;
403             my $bigtop_string = shift;
404             my $config = shift;
405              
406             # import the moudles mentioned in the config
407              
408             my @modules_to_require;
409             my @build_types;
410             my %seen_build_type;
411              
412             my $saw_init = 0;
413             BACKEND:
414             foreach my $backend_statement ( @{ $config->{__STATEMENTS__} } ) {
415             my $backend_type = $backend_statement->[0];
416             next BACKEND unless $config->{__BACKENDS__}{$backend_type};
417             $saw_init = 1 if $backend_type eq 'Init';
418             foreach my $backend ( @{ $config->{__BACKENDS__}{$backend_type} } ) {
419             my $backend_name = $backend->{__NAME__};
420             my $template = $backend->{template} || '';
421              
422             my $module_str = join '=', $backend_type, $backend_name, $template;
423              
424             push @modules_to_require, $module_str;
425             push @build_types, $backend_type
426             unless ( $seen_build_type{ $backend_type }++ );
427             }
428             }
429              
430             $class->import( @modules_to_require );
431              
432             push @build_types, 'Init' if $saw_init;
433              
434             return \@build_types;
435             }
436              
437             sub _build_app_home_dir {
438             my $tree = shift;
439             my $create = shift;
440             my $config = $tree->get_config();
441              
442             my $base_dir = '.';
443            
444             if ( $create ) {
445             $base_dir = $config->{base_dir} if defined $config->{base_dir};
446             }
447             elsif ( defined $config->{base_dir} ) {
448             warn "Warning: config's base_dir ignored, "
449             . "because we're not in create mode\n";
450             }
451              
452             # make sure base_dir exists
453             die "You must make the base directory $base_dir\n" unless ( -d $base_dir );
454              
455             # get app name and make a directory of it
456             my $build_dir = _form_build_dir( $base_dir, $tree, $config, $create );
457              
458             if ( $create ) {
459             if ( -d $build_dir ) {
460             die "cowardly refusing to create,\n"
461             . "...build dir $build_dir already exists\n";
462             }
463              
464             mkdir $build_dir;
465              
466             die "couldn't make directory $build_dir\n" unless ( -d $build_dir );
467             }
468             else {
469             die "$build_dir is not a directory, perhaps you need to use --create\n"
470             unless ( -d $base_dir );
471             }
472              
473             $tree->{configuration}{build_dir} = $build_dir;
474              
475             return $build_dir;
476             }
477              
478             sub _form_build_dir {
479             my $base_dir = shift;
480             my $tree = shift;
481             my $config = shift;
482             my $create = shift;
483              
484             my $app_dir = '';
485             if ( $create ) {
486             if ( defined $config->{app_dir} and $config->{app_dir} ) {
487             $app_dir = $config->{app_dir};
488             }
489             else {
490             $app_dir = $tree->get_appname();
491             $app_dir =~ s/::/-/g;
492             }
493             }
494             else {
495             if ( defined $config->{app_dir} ) {
496             warn "config's app_dir ignored, because we're not in create mode\n";
497             }
498             }
499              
500             return File::Spec->catdir( $base_dir, $app_dir );
501             }
502              
503             sub _purge_inline {
504             my $doomed_dir = '_Inline';
505             return unless -d $doomed_dir;
506              
507             my $purger = sub {
508             my $name = $_;
509              
510             if ( -f $name ) { unlink $name; }
511             elsif ( -d $name ) { rmdir $name; }
512             };
513              
514             require File::Find;
515              
516             File::Find::finddepth( $purger, $doomed_dir );
517             rmdir $doomed_dir;
518             }
519              
520             sub parse_config_string {
521             my $class = shift;
522             my $string = shift
523             or croak "usage: Bigtop::Parser->parse_config_string(bigtop_string)";
524              
525             preprocess( $string );
526              
527             my $retval = $class->get_parser->config_only( $string );
528              
529             unless ( $retval ) {
530             die "Couldn't parse config in your bigtop input.\n";
531             }
532              
533             return $retval;
534             }
535              
536             sub parse_string {
537             my $class = shift;
538             my $string = shift
539             or croak "usage: Bigtop::Parser->parse_string(bigtop_string)";
540              
541             # strip comments
542             my $comments = preprocess( $string );
543              
544             my $build_types = $class->load_backends(
545             $string,
546             $class->parse_config_string( $string )
547             );
548              
549             my $retval = $class->get_parser->bigtop_file( $string );
550              
551             $retval->set_comments( $comments );
552              
553             unless ( $retval ) {
554             die "Couldn't parse your bigtop input.\n";
555             }
556              
557             return $retval;
558             }
559              
560             sub parse_file {
561             my $class = shift;
562             my $bigtop_file = shift
563             or croak "usage: BigtoP::Parser->parse_file(bigtop_file)";
564              
565             open my $BIGTOP_INPUT, "<", $bigtop_file
566             or croak "Couldn't open $bigtop_file: $!\n";
567              
568             my $data = join '', <$BIGTOP_INPUT>;
569              
570             close $BIGTOP_INPUT;
571              
572             return $class->parse_string( $data );
573             }
574              
575             #---------------------------------------------------------------------
576             # Packages for each node type. These can walk_postorder.
577             # Start with $your_tree->walk_postorder( 'action', $data_object ).
578             #
579             # Most of these have a useful dumpme which trims the Data::Dumper
580             # output. The closer you are to the bottom of the tree, the
581             # better it looks relative to a regular dump.
582             #---------------------------------------------------------------------
583              
584             package # application_ancestor
585             application_ancestor;
586             use strict; use warnings;
587              
588             sub set_parent {
589             my $self = shift;
590             my $output = shift;
591             my $data = shift;
592             my $parent = shift;
593              
594             $self->{__PARENT__} = $parent;
595              
596             return;
597             }
598              
599             sub dumpme {
600             my $self = shift;
601              
602             my $parent = delete $self->{__PARENT__};
603              
604             use Data::Dumper; warn Dumper( $self );
605              
606             $self->{__PARENT__} = $parent;
607             }
608              
609             sub find_primary_key {
610             my $self = shift;
611             my $table = shift;
612             my $lookup = shift;
613              
614             my $fields = $lookup->{ tables }{ $table }{ fields };
615              
616             my @primaries;
617              
618             FIELD:
619             foreach my $field_name ( keys %{ $fields } ) {
620              
621             my $field = $fields->{$field_name};
622              
623             foreach my $statement_keyword ( keys %{ $field } ) {
624              
625             next unless $statement_keyword eq 'is';
626              
627             my $statement = $field->{ $statement_keyword };
628              
629             foreach my $arg ( @{ $statement->{args} } ) {
630             if ( $arg eq 'primary_key' ) {
631             push @primaries, $field_name;
632             }
633             } # end of foreach argument
634             } # end of foreach statement
635             } # end of foreach field
636              
637             if ( @primaries > 1 ) {
638             return \@primaries;
639             }
640             elsif ( @primaries == 1 ) {
641             return $primaries[0];
642             }
643             else {
644             return;
645             }
646             }
647              
648             sub find_unique_name {
649             my $self = shift;
650             my $table = shift;
651             my $lookup = shift;
652              
653             my $fields = $lookup->{ tables }{ $table }{ fields };
654              
655             my $uniques;
656              
657             FIELD:
658             foreach my $field_name ( keys %{ $fields } ) {
659              
660             my $field = $fields->{$field_name};
661              
662             foreach my $statement_keyword ( keys %{ $field } ) {
663              
664             next unless $statement_keyword eq 'unique_name';
665              
666             my $statement = $field->{ $statement_keyword };
667              
668             my $constraint_name = $statement->{args}[0];
669              
670             push( @{$uniques->{$constraint_name}}, $field_name );
671             } # end of foreach statement
672             } # end of foreach field
673              
674             if ( scalar(keys %{$uniques}) > 0 ) {
675             return $uniques;
676             }
677              
678             else {
679             return;
680             }
681             }
682              
683             package # bigtop_file
684             bigtop_file;
685             use strict; use warnings;
686              
687             use Config;
688              
689             sub walk_postorder {
690             my $self = shift;
691             my $action = shift;
692             my $data = shift;
693              
694             return $self->{application}->walk_postorder( $action, $data );
695             }
696              
697             sub get_comments {
698             my $self = shift;
699              
700             return $self->{comments};
701             }
702              
703             sub set_comments {
704             my $self = shift;
705             my $comments = shift;
706              
707             $self->{comments} = $comments;
708             }
709              
710             sub get_app_configs {
711             my $self = shift;
712              
713             my $app_configs = $self->walk_postorder( 'get_app_configs' );
714              
715             my %retval;
716              
717             foreach my $config_type ( @{ $app_configs } ) {
718             $retval{ $config_type->{ type } } = $config_type->{ statements };
719             }
720              
721             return \%retval;
722             }
723              
724             sub get_controller_configs {
725             my $self = shift;
726              
727             my $app_configs = $self->walk_postorder( 'get_controller_configs' );
728              
729             my %retval;
730              
731             foreach my $config_type ( @{ $app_configs } ) {
732             $retval{ $config_type->{ controller } } = $config_type->{ configs };
733             }
734              
735             return \%retval;
736             }
737              
738             sub get_app_config_types {
739             my $self = shift;
740              
741             return $self->walk_postorder( 'get_app_config_types' );
742             }
743              
744             sub get_lookup {
745             my $self = shift;
746              
747             return $self->{application}{lookup};
748             }
749              
750             sub get_authors {
751             my $self = shift;
752              
753             if ( defined $self->{application}{lookup}{app_statements}{authors} ) {
754             my $authors = $self->{application}{lookup}{app_statements}{authors};
755              
756             my $retval = [];
757              
758             foreach my $author ( @{ $authors } ) {
759             if ( ref( $author ) eq 'HASH' ) {
760             push @{ $retval }, [ %{ $author } ];
761             }
762             else {
763             push @{ $retval }, [ $author, '' ];
764             }
765             }
766              
767             $retval;
768             }
769             else { # fall back on password file or local equivalent
770             my $retval = [];
771             # this eval was stolen from h2xs, but has been reformatted
772             eval {
773             my ( $username, $author_gcos ) = ( getpwuid($>) )[0,6];
774              
775             if ( defined $username && defined $author_gcos ) {
776              
777             $author_gcos =~ s/,.*$//; # in case of sub fields
778              
779             my $domain = $Config{ mydomain };
780             $domain =~ s/^\.//;
781              
782             push @{ $retval }, [ $author_gcos, "$username\@$domain" ];
783             }
784             };
785             return $retval;
786             }
787             }
788              
789             sub get_contact_us {
790             my $self = shift;
791             my $statements = $self->{application}{lookup}{app_statements};
792              
793             if ( defined $statements->{contact_us} ) {
794             return $statements->{contact_us}[0];
795             }
796             elsif ( defined $statements->{email} ) {
797             return $statements->{email}[0];
798             }
799             else {
800             return '';
801             }
802             }
803              
804             sub get_copyright_holder {
805             my $self = shift;
806             my $statements = $self->{application}{lookup}{app_statements};
807              
808             if ( defined $statements->{copyright_holder} ) {
809             return $statements->{copyright_holder}[0];
810             }
811             else {
812             my $first_author = $self->get_authors->[0];
813             return $first_author->[0] || '';
814             }
815             }
816              
817             sub get_license_text {
818             my $self = shift;
819              
820             return $self->{application}{lookup}{app_statements}{license_text}[0];
821             }
822              
823             sub get_config {
824             my $tree = shift;
825              
826             return $tree->{configuration};
827             }
828              
829             sub get_app {
830             my $tree = shift;
831              
832             return $tree->{application};
833             }
834              
835             sub get_app_blocks {
836             my $tree = shift;
837              
838             return $tree->get_app()->get_blocks();
839             }
840              
841             sub get_appname {
842             my $tree = shift;
843              
844             return $tree->get_app()->get_name();
845             }
846              
847             sub set_appname {
848             my $tree = shift;
849             my $new_name = shift;
850              
851             $tree->{application}->set_name( $new_name );
852             }
853              
854             sub get_top_level_configs {
855             my $tree = shift;
856              
857             my %retval;
858              
859             STATEMENT:
860             foreach my $statement ( @{ $tree->{ configuration }{__STATEMENTS__} } ) {
861             my ( $name, $value ) = @{ $statement };
862             next STATEMENT if ref( $value );
863              
864             $retval{ $name } = $value;
865             }
866              
867             return \%retval;
868             }
869              
870             sub set_top_level_config {
871             my $tree = shift;
872             my $keyword = shift;
873             my $new_value = shift;
874              
875             $new_value =~ s/^\s+//;
876             $new_value =~ s/\s+$//;
877              
878             if ( $new_value !~ /^\w[\w\d_:\.]*$/ ) {
879             $new_value = "`$new_value`";
880             }
881              
882             my $config = $tree->{configuration};
883              
884             # change it in the quick lookup hash...
885             $config->{ $keyword } = $new_value;
886              
887             # ... and in the __STATEMENTS__ list
888             my $we_changed_it = 0;
889             STATEMENT:
890             foreach my $statement ( @{ $config->{__STATEMENTS__} } ) {
891             my ( $candidate_keyword, $value ) = @{ $statement };
892             if ( $candidate_keyword eq $keyword ) {
893             $statement->[1] = $new_value;
894             $we_changed_it++;
895             last STATEMENT;
896             }
897             }
898              
899             # add the statement at the top if it wasn't already there
900             unless ( $we_changed_it ) {
901             unshift @{ $config->{__STATEMENTS__} }, [ $keyword, $new_value ];
902             }
903             }
904              
905             sub clear_top_level_config {
906             my $tree = shift;
907             my $keyword = shift;
908              
909             my $config = $tree->{configuration};
910              
911             # clear it from the quick lookup hash...
912             delete $config->{ $keyword };
913              
914             # ... and from the __STATEMENTS__ list
915             my $doomed_index = -1;
916             my $count = 0;
917             STATEMENT:
918             foreach my $statement ( @{ $config->{__STATEMENTS__} } ) {
919             my ( $candidate_keyword ) = @{ $statement };
920             if ( $candidate_keyword eq $keyword ) {
921             $doomed_index = $count;
922             last STATEMENT;
923             }
924              
925             $count++;
926             }
927              
928             if ( $doomed_index >= 0 ) {
929             splice @{ $config->{__STATEMENTS__} }, $doomed_index, 1;
930             }
931             }
932              
933             sub get_engine {
934             my $tree = shift;
935              
936             return $tree->{configuration}{engine};
937             }
938              
939             sub set_engine {
940             my $tree = shift;
941             my $new_engine = shift;
942              
943             my $config = $tree->{configuration};
944              
945             # change it in the quick lookup hash...
946             $config->{engine} = $new_engine;
947              
948             # ... and in the __STATEMENTS__ list
949             my $we_changed_engines = 0;
950             STATEMENT:
951             foreach my $statement ( @{ $config->{__STATEMENTS__} } ) {
952             my ( $keyword, $value ) = @{ $statement };
953             if ( $keyword eq 'engine' ) {
954             $statement->[1] = $new_engine;
955             $we_changed_engines++;
956             last STATEMENT;
957             }
958             }
959              
960             # add the statement at the top if it wasn't already there
961             unless ( $we_changed_engines ) {
962             unshift @{ $config->{__STATEMENTS__} }, [ 'engine', $new_engine ];
963             }
964             }
965              
966             sub get_template_engine {
967             my $tree = shift;
968              
969             return $tree->{configuration}{template_engine};
970             }
971              
972             sub set_template_engine {
973             my $tree = shift;
974             my $new_engine = shift;
975              
976             my $config = $tree->{configuration};
977              
978             # change it in the quick lookup hash...
979             $config->{template_engine} = $new_engine;
980              
981             # ... and in the __STATEMENTS__ list
982             my $we_changed_engines = 0;
983             STATEMENT:
984             foreach my $statement ( @{ $config->{__STATEMENTS__} } ) {
985             my ( $keyword, $value ) = @{ $statement };
986             if ( $keyword eq 'template_engine' ) {
987             $statement->[1] = $new_engine;
988             $we_changed_engines++;
989             last STATEMENT;
990             }
991             }
992              
993             # add the statement at the top if it wasn't already there
994             unless ( $we_changed_engines ) {
995             unshift @{ $config->{__STATEMENTS__} },
996             [ 'template_engine', $new_engine ];
997             }
998             }
999              
1000             sub change_statement {
1001             my $self = shift;
1002             my $params = shift;
1003              
1004             $params->{ app } = $self->get_app;
1005              
1006             my ( undef, $doc_hash ) = Bigtop::Keywords->get_docs_for(
1007             $params->{type}, $params->{keyword}
1008             );
1009              
1010             $params->{ pair_required } = $doc_hash->{ pair_required };
1011              
1012             my $walk_action = "change_$params->{ type }_statement";
1013              
1014             my $result = $self->walk_postorder( $walk_action, $params );
1015              
1016             if ( @{ $result } == 0 ) {
1017             die "Couldn't change $params->{type} statement "
1018             . "'$params->{keyword}' for '$params->{ident}'\n";
1019             }
1020              
1021             return $result->[0];
1022             }
1023              
1024             sub data_statement_change {
1025             my $self = shift;
1026             my $params = shift;
1027              
1028             return $self->walk_postorder( 'change_data_statement', $params );
1029             }
1030              
1031             sub get_statement {
1032             my $self = shift;
1033             my $params = shift;
1034              
1035             my $walk_action = "get_$params->{ type }_statement";
1036              
1037             my $result = $self->walk_postorder( $walk_action, $params );
1038              
1039             return $result->[0];
1040             }
1041              
1042             sub remove_statement {
1043             my $self = shift;
1044             my $params = shift;
1045              
1046             my $walk_action = "remove_$params->{ type }_statement";
1047             my $result = $self->walk_postorder( $walk_action, $params );
1048              
1049             if ( @{ $result } == 0 ) {
1050             warn "Couldn't remove statement: couldn't find it with $walk_action\n";
1051             require Data::Dumper;
1052             Data::Dumper->import( 'Dumper' );
1053             warn Dumper( $params );
1054             }
1055             }
1056              
1057             sub change_name {
1058             my $self = shift;
1059             my $params = shift;
1060              
1061             my $method = "change_name_$params->{type}";
1062              
1063             $params->{__THE_TREE__} = $self;
1064             my $instructions = $self->walk_postorder( $method, $params );
1065              
1066             if ( $instructions->[1] ) { # Should this be 0?
1067             return $instructions;
1068             }
1069             else {
1070             return [];
1071             }
1072             }
1073              
1074             sub create_block {
1075             my $self = shift;
1076             my $type = shift;
1077             my $name = shift;
1078             my $data = shift;
1079              
1080             my $result = $self->walk_postorder(
1081             'add_block', { type => $type, name => $name, %{ $data } }
1082             );
1083              
1084             return $result->[ 0 ];
1085             }
1086              
1087             sub delete_block {
1088             my $self = shift;
1089             my $ident = shift;
1090              
1091             my $instructions = $self->walk_postorder(
1092             'remove_block',
1093             {
1094             ident => $ident,
1095             __THE_TREE__ => $self,
1096             }
1097             );
1098              
1099             if ( $instructions->[0] and defined $instructions->[1] ) {
1100             return $instructions;
1101             }
1102             else {
1103             return [];
1104             }
1105             }
1106              
1107             sub move_block {
1108             my $self = shift;
1109             my $params = shift;
1110              
1111             $self->walk_postorder( 'block_move', $params );
1112             }
1113              
1114             sub create_subblock {
1115             my $self = shift;
1116             my $params = shift;
1117              
1118             my $result = $self->walk_postorder( 'add_subblock', $params );
1119              
1120             if ( @{ $result } == 0 ) {
1121             die "Couldn't add subblock '$params->{new_child}{name}' "
1122             . "to $params->{parent}{type} '$params->{parent}{ident}'\n";
1123             }
1124              
1125             return $result->[0];
1126             }
1127              
1128             sub type_change {
1129             my $self = shift;
1130             my $params = shift;
1131              
1132             $self->walk_postorder( 'change_type', $params );
1133             }
1134              
1135             sub field_became_date {
1136             my $self = shift;
1137             my $params = shift;
1138              
1139             my @retvals;
1140              
1141             my $table_name = $self->walk_postorder(
1142             'get_table_name_from_field_ident',
1143             $params
1144             )->[0];
1145              
1146             my $result = $self->walk_postorder(
1147             'use_date_plugin', { table => $table_name, }
1148             );
1149              
1150             push @retvals, @{ $result };
1151              
1152             # make sure field's type is text
1153             $self->walk_postorder(
1154             'change_field_statement',
1155             {
1156             type => 'field',
1157             ident => $params->{ ident },
1158             keyword => 'html_form_type',
1159             new_value => 'text',
1160             },
1161             );
1162              
1163             push @retvals, $params->{ ident } . '::html_form_type', 'text';
1164              
1165             # based on the triggering event, update the other possible cause
1166             # ... if is became date, set date select text
1167             if ( $params->{ trigger } eq 'date' ) {
1168             $self->walk_postorder(
1169             'change_field_statement',
1170             {
1171             type => 'field',
1172             ident => $params->{ ident },
1173             keyword => 'date_select_text',
1174             new_value => 'Select Date',
1175             },
1176             );
1177             push @retvals,
1178             $params->{ ident } . '::date_select_text',
1179             'Select Date';
1180             }
1181             # ... if date select text got a value, make the field type date
1182             else {
1183             $self->walk_postorder(
1184             'change_field_statement',
1185             {
1186             type => 'field',
1187             ident => $params->{ ident },
1188             keyword => 'is',
1189             new_value => 'date',
1190             },
1191             );
1192             push @retvals, $params->{ ident } . '::is', 'date';
1193             }
1194              
1195             return \@retvals;
1196             }
1197              
1198             sub table_reset_bool {
1199             my $self = shift;
1200             my $params = shift;
1201              
1202             return $self->walk_postorder( 'table_reset_bool', $params );
1203             }
1204              
1205             sub show_idents {
1206             my $self = shift;
1207              
1208             $self->walk_postorder( 'show_idents' );
1209             }
1210              
1211             package # application
1212             application;
1213             use strict; use warnings;
1214              
1215             sub get_blocks {
1216             my $self = shift;
1217              
1218             return $self->walk_postorder( 'app_block_hashes' );
1219             }
1220              
1221             sub get_name {
1222             my $self = shift;
1223              
1224             return $self->{__NAME__};
1225             }
1226              
1227             sub set_name {
1228             my $self = shift;
1229             my $new_name = shift;
1230              
1231             $self->{__NAME__} = $new_name;
1232             }
1233              
1234             sub get_app_statement {
1235             my $self = shift;
1236             my $keyword = shift;
1237              
1238             my $answer = $self->walk_postorder( 'get_statement', $keyword );
1239              
1240             return $answer;
1241             }
1242              
1243             sub set_app_statement {
1244             my $self = shift;
1245             my $keyword = shift;
1246             my $value = shift;
1247              
1248             my $success = $self->walk_postorder(
1249             'set_statement', { keyword => $keyword, value => $value }
1250             );
1251              
1252             unless ( defined $success->[0] ) { # no existing statement, make one
1253             $self->{__BODY__}->add_last_statement( $keyword, $value );
1254             }
1255             }
1256              
1257             sub set_app_statement_pairs {
1258             my $self = shift;
1259             my $params = shift;
1260              
1261             my ( undef, $doc_hash ) = Bigtop::Keywords->get_docs_for(
1262             'app', $params->{keyword}
1263             );
1264              
1265             $params->{ pair_required } = $doc_hash->{ pair_required };
1266              
1267             my $success = $self->walk_postorder( 'set_statement_pairs', $params );
1268              
1269             unless ( defined $success->[0] ) { # make a new statement
1270             $self->{__BODY__}->add_last_statement_pair( $params );
1271             }
1272             }
1273              
1274             sub remove_app_statement {
1275             my $self = shift;
1276             my $keyword = shift;
1277              
1278             $self->walk_postorder( 'remove_statement', $keyword );
1279             }
1280              
1281             sub set_config_statement {
1282             my $self = shift;
1283             my $ident = shift;
1284             my $keyword = shift;
1285             my $value = shift;
1286             my $accessor = shift;
1287              
1288             my $success = $self->walk_postorder(
1289             'update_config_statement',
1290             {
1291             ident => $ident,
1292             keyword => $keyword,
1293             value => $value,
1294             }
1295             );
1296              
1297             unless ( defined $success->[0] ) { # no such statement
1298             $self->{__BODY__}->add_last_config_statement(
1299             $ident, $keyword, $value, $accessor
1300             );
1301             }
1302             }
1303              
1304             sub get_config_statement {
1305             my $self = shift;
1306             my $config_type_name = shift;
1307             my $keyword = shift;
1308              
1309             return $self->walk_postorder(
1310             'get_config_value',
1311             {
1312             config_type_name => $config_type_name,
1313             keyword => $keyword,
1314             }
1315             );
1316             }
1317              
1318             sub get_config_ident {
1319             my $self = shift;
1320             my $config_block_name = shift;
1321              
1322             my $idents = $self->walk_postorder(
1323             'get_config_idents', $config_block_name
1324             );
1325              
1326             return $idents->[0];
1327             }
1328              
1329             sub set_config_statement_status {
1330             my $self = shift;
1331             my $ident = shift;
1332             my $keyword = shift;
1333             my $value = shift;
1334              
1335             $self->walk_postorder(
1336             'config_statement_status',
1337             {
1338             ident => $ident,
1339             keyword => $keyword,
1340             value => $value,
1341             }
1342             );
1343             }
1344              
1345             sub delete_config_statement {
1346             my $self = shift;
1347             my $ident = shift;
1348             my $keyword = shift;
1349              
1350             $self->walk_postorder(
1351             'remove_config_statement',
1352             {
1353             ident => $ident,
1354             keyword => $keyword,
1355             }
1356             );
1357             }
1358              
1359             sub get_config {
1360             my $self = shift;
1361              
1362             my $statements = $self->walk_postorder( 'get_config_statements' );
1363              
1364             return $statements;
1365             }
1366              
1367             sub show_idents {
1368             my $self = shift;
1369             my $child_output = shift;
1370              
1371             require Data::Dumper;
1372             warn Data::Dumper::Dumper( $child_output );
1373              
1374             return;
1375             }
1376              
1377             sub walk_postorder {
1378             my $self = shift;
1379             my $action = shift;
1380             my $data = shift;
1381              
1382             my $output = $self->{__BODY__}->walk_postorder( $action, $data, $self );
1383              
1384             if ( $self->can( $action ) ) {
1385             $output = $self->$action( $output, $data, undef );
1386             }
1387              
1388             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
1389             }
1390              
1391             package # app_body
1392             app_body;
1393             use strict; use warnings;
1394              
1395             use base 'application_ancestor';
1396              
1397             sub walk_postorder {
1398             my $self = shift;
1399             my $action = shift;
1400             my $data = shift;
1401             my $parent = shift;
1402              
1403             my $output = [];
1404              
1405             foreach my $block ( @{ $self->{'block(s?)'} } ) {
1406             my $child_output = $block->walk_postorder( $action, $data, $self );
1407              
1408             push @{ $output }, @{ $child_output } if $child_output;
1409             }
1410              
1411             if ( $self->can( $action ) ) {
1412             $output = $self->$action( $output, $data, $parent );
1413             }
1414              
1415             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
1416             }
1417              
1418             sub add_block {
1419             my $self = shift;
1420             shift;
1421             my $data = shift;
1422              
1423             my $new_block = block->new_block( $self, $data );
1424              
1425             push @{ $self->{ 'block(s?)' } }, $new_block;
1426              
1427             return [ $new_block ];
1428             }
1429              
1430             sub remove_block {
1431             my $self = shift;
1432             my $child_output = shift;
1433             my $data = shift;
1434             my $doomed_ident = $data->{ ident };
1435              
1436             my $blocks = $self->{ 'block(s?)' };
1437             my $doomed_index = get_block_index( $blocks, $doomed_ident );
1438              
1439             return $child_output if ( $doomed_index == -1 ); # must be for a subblock
1440              
1441             splice @{ $blocks }, $doomed_index, 1;
1442              
1443             return [ 1 ];
1444             }
1445              
1446             sub block_move {
1447             my $self = shift;
1448             shift;
1449             my $data = shift;
1450             my $blocks = $self->{ 'block(s?)' };
1451              
1452             my $mover_index = get_block_index( $blocks, $data->{mover} );
1453             die "No such block: $data->{mover}\n" if ( $mover_index == -1 );
1454             my $moving_block = splice @{ $blocks }, $mover_index, 1;
1455              
1456             my $pivot_index = get_block_index( $blocks, $data->{pivot} );
1457              
1458             if ( $pivot_index == -1 ) {
1459             splice @{ $blocks }, $mover_index, 0, $moving_block;
1460              
1461             die "No such pivot block: $data->{pivot}\n";
1462             }
1463              
1464             if ( defined $data->{after} and $data->{after} ) {
1465             splice @{ $blocks }, $pivot_index + 1, 0, $moving_block;
1466             }
1467             else {
1468             splice @{ $blocks }, $pivot_index, 0, $moving_block;
1469             }
1470              
1471             return [ 1 ];
1472             }
1473              
1474             sub get_block_index {
1475             my $blocks = shift;
1476             my $target_ident = shift;
1477              
1478             my $target_index = -1;
1479             my $count = 0;
1480              
1481             BLOCK:
1482             foreach my $block ( @{ $blocks } ) {
1483             next BLOCK if defined $block->{app_statement};
1484              
1485             if ( $block->matches( $target_ident ) ) {
1486             $target_index = $count;
1487             last BLOCK;
1488             }
1489             }
1490             continue {
1491             $count++;
1492             }
1493              
1494             return $target_index;
1495             }
1496              
1497             sub remove_statement {
1498             my $self = shift;
1499             shift;
1500             my $keyword = shift;
1501              
1502             my $doomed_child = -1;
1503             my $count = 0;
1504              
1505             BLOCK:
1506             foreach my $block ( @{ $self->{'block(s?)'} } ) {
1507             next BLOCK unless defined $block->{app_statement};
1508              
1509             my $child_keyword = $block->{app_statement}->get_keyword();
1510             if ( $keyword eq $child_keyword ) {
1511             $doomed_child = $count;
1512             last BLOCK;
1513             }
1514             }
1515             continue {
1516             $count++;
1517             }
1518              
1519             if ( $doomed_child >= 0 ) {
1520             # This probably leaks memory because children have parent pointers.
1521             # But the parent is me and I'm the app_body, so maybe not.
1522             splice @{ $self->{'block(s?)'} }, $doomed_child, 1;
1523             }
1524             # else, nothing to see here, move along quietly
1525              
1526             return [ 1 ];
1527             }
1528              
1529             sub add_last_config_statement {
1530             my $self = shift;
1531             my $ident = shift;
1532             my $keyword = shift;
1533             my $value = shift;
1534             my $accessor = shift;
1535              
1536             my $success = $self->walk_postorder(
1537             'add_config_statement',
1538             {
1539             ident => $ident,
1540             keyword => $keyword,
1541             value => $value,
1542             accessor => $accessor,
1543             }
1544             );
1545              
1546             # if there is not a config block, make one and try again
1547             unless ( defined $success->[0] ) {
1548             my $statement = app_config_statement->new(
1549             $keyword,
1550             $value,
1551             $accessor,
1552             );
1553              
1554             my $block = app_config_block->new(
1555             {
1556             parent => $self,
1557             statements => [ $statement ],
1558             }
1559             );
1560              
1561             $statement->{__PARENT__} = $block;
1562              
1563             push @{ $self->{ 'block(s?)' } }, $block;
1564             }
1565             }
1566              
1567             sub add_last_statement {
1568             my $self = shift;
1569             my $keyword = shift;
1570             my $value = shift;
1571              
1572             my @values = split /\]\[/, $value;
1573             my $new_statement = block->new_statement( $self, $keyword, \@values );
1574              
1575             my $index = $self->last_statement_index();
1576              
1577             if ( $index >= 0 ) {
1578             splice @{ $self->{ 'block(s?)' } }, $index + 1, 0, $new_statement;
1579             }
1580             else { # We're so excited, this is our first child!!!
1581             $self->{ 'block(s?)' } = [ $new_statement ];
1582             }
1583              
1584             # Untested, but should update the lookup hash, in case anyone cares
1585             my $lookup = $self->{__PARENT__}->{lookup};
1586              
1587             $lookup->{app_statements}{ $keyword } = arg_list->new( \@values );
1588             }
1589              
1590             sub add_last_statement_pair {
1591             my $self = shift;
1592             my $params = shift;
1593              
1594             my $new_statement = block->new_statement_pair( $self, $params );
1595              
1596             my $index = $self->last_statement_index();
1597              
1598             if ( $index >= 0 ) {
1599             splice @{ $self->{ 'block(s?)' } }, $index, 0, $new_statement;
1600             }
1601             else { # We're so excited, this is our first child!!!
1602             $self->{ 'block(s?)' } = [ $new_statement ];
1603             }
1604              
1605             # Untested, but should update the lookup hash, in case anyone cares
1606             my $lookup = $self->{__PARENT__}->{lookup};
1607              
1608             $lookup->{app_statements}{ $params->{keyword} } = arg_list->new(
1609             $params->{ new_value },
1610             $params->{ pair_required },
1611             );
1612             }
1613              
1614             sub last_statement_index {
1615             my $self = shift;
1616              
1617             my $index = -1;
1618             my $count = 0;
1619             foreach my $block ( @{ $self->{ 'block(s?)' } } ) {
1620             if ( defined $block->{app_statement}
1621             or
1622             defined $block->{app_config_block}
1623             ) {
1624             $index = $count;
1625             }
1626             $count++;
1627             }
1628              
1629             return $index;
1630             }
1631              
1632             sub build_lookup_hash {
1633             my $self = shift;
1634             my $child_output = shift;
1635             my $data = shift;
1636              
1637             my %output;
1638              
1639             foreach my $element ( @{ $child_output } ) {
1640             if ( $element->{__TYPE__} eq 'join_tables' ) {
1641             my $output_type = $element->{__TYPE__};
1642             my $name = $element->{__DATA__}[0];
1643             push @{ $output{ $output_type }{ $name } },
1644             $element->{__DATA__}[1];
1645              
1646             $name = $element->{__DATA__}[2];
1647             push @{ $output{ $output_type }{ $name } },
1648             $element->{__DATA__}[3];
1649             }
1650             else {
1651             my $output_type = $element->{__TYPE__};
1652             my $name = $element->{__DATA__}[0];
1653             $output{ $output_type }{ $name } = $element->{__DATA__}[1];
1654             }
1655              
1656             }
1657              
1658             return [ %output ];
1659             }
1660              
1661             package # block
1662             block;
1663             use strict; use warnings;
1664              
1665             use base 'application_ancestor';
1666              
1667             sub new_statement {
1668             my $class = shift;
1669             my $parent = shift;
1670             my $keyword = shift;
1671             my $values = shift;
1672              
1673             my $self = {
1674             __RULE__ => 'block',
1675             __PARENT__ => $parent,
1676             };
1677              
1678             $self->{app_statement} = app_statement->new( $self, $keyword, $values ),
1679              
1680             return bless $self, $class;
1681             }
1682              
1683             sub new_statement_pair {
1684             my $class = shift;
1685             my $parent = shift;
1686             my $params = shift;
1687              
1688             my $self = {
1689             __RULE__ => 'block',
1690             __PARENT__ => $parent,
1691             };
1692              
1693             $self->{app_statement} = app_statement->new_pair( $self, $params );
1694              
1695             return bless $self, $class;
1696             }
1697              
1698             my %block_name_for = (
1699             table => 'table_block',
1700             sequence => 'seq_block',
1701             controller => 'controller_block',
1702             literal => 'literal_block',
1703             join_table => 'join_table',
1704             schema => 'schema_block',
1705             config => 'app_config_block',
1706             );
1707              
1708             sub new_block {
1709             my $class = shift;
1710             my $parent = shift;
1711             my $data = shift;
1712              
1713             my $self = {
1714             __RULE__ => 'block',
1715             __PARENT__ => $parent,
1716             };
1717              
1718             bless $self, $class;
1719              
1720             my $constructing_class = $block_name_for{ $data->{type} };
1721              
1722             $self->{ $constructing_class } = $constructing_class->new_block(
1723             $self, $data
1724             );
1725              
1726             return $self;
1727             }
1728              
1729             sub matches {
1730             my $self = shift;
1731             my $ident = shift;
1732              
1733             my @ident_block_types = qw(
1734             controller_block
1735             sql_block
1736             literal_block
1737             table_block
1738             seq_block
1739             schema_block
1740             join_table
1741             );
1742              
1743             my @keys = keys %{ $self };
1744              
1745             TYPE:
1746             foreach my $block_type_name ( @ident_block_types ) {
1747             next TYPE unless defined $self->{ $block_type_name };
1748             return 1 if ( $self->{ $block_type_name }{__IDENT__} eq $ident );
1749             }
1750             }
1751              
1752             sub get_ident {
1753             my $self = shift;
1754              
1755             foreach my $child_block ( keys %{ $self } ) {
1756             next unless ref $self->{ $child_block };
1757             return $self->{ $child_block }->get_ident();
1758             }
1759             }
1760              
1761             sub walk_postorder {
1762             my $self = shift;
1763             my $action = shift;
1764             my $data = shift;
1765             my $parent = shift;
1766              
1767             my $output = [];
1768              
1769             foreach my $block_type ( keys %$self ) {
1770             next unless (
1771             $block_type =~ /_block$/
1772             or
1773             $block_type =~ /_statement$/
1774             or
1775             $block_type eq 'join_table'
1776             );
1777              
1778             my $child_output = $self->{$block_type}->walk_postorder(
1779             $action, $data, $self
1780             );
1781              
1782             push @{ $output }, @{ $child_output } if $child_output;
1783             }
1784              
1785             if ( $self->can( $action ) ) {
1786             $output = $self->$action( $output, $data, $parent );
1787             }
1788              
1789             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
1790             }
1791              
1792             sub build_lookup_hash {
1793             my $self = shift;
1794             my $child_output = shift;
1795             my $data = shift;
1796              
1797             return $child_output;
1798             }
1799              
1800             package # app_statment
1801             app_statement;
1802             use strict; use warnings;
1803              
1804             use base 'application_ancestor';
1805              
1806             sub new {
1807             my $class = shift;
1808             my $parent = shift;
1809             my $keyword = shift;
1810             my $values = shift;
1811              
1812             my $self = {
1813             __PARENT__ => $parent,
1814             __KEYWORD__ => $keyword,
1815             __ARGS__ => arg_list->new( $values ),
1816             };
1817              
1818             return bless $self, $class;
1819             }
1820              
1821             sub new_pair {
1822             my $class = shift;
1823             my $parent = shift;
1824             my $params = shift;
1825              
1826             my $self = {
1827             __PARENT__ => $parent,
1828             __KEYWORD__ => $params->{ keyword },
1829             __ARGS__ => arg_list->new(
1830             $params->{ new_value },
1831             $params->{ pair_required },
1832             ),
1833             };
1834              
1835             return bless $self, $class;
1836             }
1837              
1838             sub get_keyword {
1839             my $self = shift;
1840              
1841             return $self->{__KEYWORD__};
1842             }
1843              
1844             sub set_statement {
1845             my $self = shift;
1846             shift;
1847             my $data = shift;
1848              
1849             return unless ( $data->{keyword} eq $self->{__KEYWORD__} );
1850              
1851             $self->{__ARGS__}->set_args_from( $data->{value}, $data->{pair_required} );
1852              
1853             return [ 1 ];
1854             }
1855              
1856             sub set_statement_pairs {
1857             my $self = shift;
1858             shift;
1859             my $data = shift;
1860              
1861             return unless ( $data->{keyword} eq $self->{__KEYWORD__} );
1862              
1863             $self->{__ARGS__}->set_args_from(
1864             $data->{new_value},
1865             $data->{pair_required},
1866             );
1867              
1868             return [ 1 ];
1869             }
1870              
1871             sub get_statement {
1872             my $self = shift;
1873             shift;
1874             my $data = shift;
1875              
1876             return unless ( $data eq $self->{__KEYWORD__} );
1877              
1878             return $self->{__ARGS__}->get_unquoted_args;
1879              
1880             }
1881              
1882             sub output_location {
1883             my $self = shift;
1884              
1885             return unless $self->{__KEYWORD__} eq 'location';
1886              
1887             my $location = $self->{__ARGS__}[0];
1888              
1889             return [ $location ];
1890             }
1891              
1892             sub walk_postorder {
1893             my $self = shift;
1894             my $action = shift;
1895             my $data = shift;
1896             my $parent = shift;
1897              
1898             if ( $self->can( $action ) ) {
1899             return $self->$action( undef, $data, $parent );
1900             }
1901             else {
1902             return;
1903             }
1904             }
1905              
1906             sub build_lookup_hash {
1907             my $self = shift;
1908             my $child_output = shift;
1909             my $data = shift;
1910              
1911             return [
1912             {
1913             '__TYPE__' => 'app_statements',
1914             '__DATA__' => [
1915             $self->{__KEYWORD__} => $self->{__ARGS__}
1916             ]
1917             }
1918             ];
1919             }
1920              
1921             package # literal_block
1922             literal_block;
1923             use strict; use warnings;
1924              
1925             use base 'application_ancestor';
1926              
1927             sub new_block {
1928             my $class = shift;
1929             my $parent = shift;
1930             my $data = shift;
1931              
1932             my $self = {
1933             __PARENT__ => $parent,
1934             __IDENT__ => Bigtop::Parser->get_ident(),
1935             __BACKEND__ => $data->{name} || 'None',
1936             __BODY__ => '',
1937             };
1938              
1939             return bless $self, $class;
1940             }
1941              
1942             sub set_type {
1943             my $self = shift;
1944             my $new_type = shift;
1945              
1946             $self->{__BACKEND__} = $new_type;
1947             }
1948              
1949             sub set_value {
1950             my $self = shift;
1951             my $new_value = shift;
1952              
1953             $self->{__BODY__} = $new_value;
1954             }
1955              
1956             sub change_type {
1957             my $self = shift;
1958             shift;
1959             my $data = shift;
1960              
1961             return unless ( $self->get_ident eq $data->{ident} );
1962              
1963             $self->set_type( $data->{new_type} );
1964              
1965             return [ 1 ];
1966             }
1967              
1968             sub change_literal {
1969             my $self = shift;
1970             shift;
1971             my $data = shift;
1972              
1973             return unless ( $self->get_ident eq $data->{ident} );
1974              
1975             $self->set_value( $data->{new_value} );
1976              
1977             return;
1978             }
1979              
1980             sub app_block_hashes {
1981             my $self = shift;
1982              
1983             return [ {
1984             ident => $self->get_ident,
1985             type => 'literal',
1986             keyword => $self->{__BACKEND__},
1987             value => $self->{__BODY__},
1988             } ];
1989             }
1990              
1991             sub get_ident {
1992             my $self = shift;
1993             return $self->{__IDENT__};
1994             }
1995              
1996             sub get_backend {
1997             my $self = shift;
1998              
1999             return $self->{__BACKEND__};
2000             }
2001              
2002             sub show_idents {
2003             my $self = shift;
2004             my $child_output = shift;
2005              
2006             push @{ $child_output },
2007             [ 'literal', $self->{ __NAME__ }, $self->{ __IDENT__ } ];
2008              
2009             return $child_output;
2010             }
2011              
2012             sub walk_postorder {
2013             my $self = shift;
2014             my $action = shift;
2015             my $data = shift;
2016             my $parent = shift;
2017              
2018             if ( $self->can( $action ) ) {
2019             return $self->$action( undef, $data, $parent );
2020             }
2021             else {
2022             return;
2023             }
2024             }
2025              
2026             sub make_output {
2027             my $self = shift;
2028             my $backend = shift;
2029             my $want_hash = shift;
2030              
2031             if ( $backend eq $self->{__BACKEND__} ) {
2032             my $output = $self->{__BODY__};
2033              
2034             $output =~ s/\Z/\n/ if ( $output !~ /\s\Z/ );
2035              
2036             return $want_hash ? [ { $backend => $output } ] : [ $output ];
2037             }
2038             else {
2039             return;
2040             }
2041             }
2042              
2043             package # table_block
2044             table_block;
2045             use strict; use warnings;
2046              
2047             use base 'application_ancestor';
2048              
2049             use Bigtop::ScriptHelp;
2050              
2051             sub new_block {
2052             my $class = shift;
2053             my $parent = shift;
2054             my $data = shift;
2055              
2056             my $self = {
2057             __IDENT__ => Bigtop::Parser->get_ident(),
2058             __NAME__ => $data->{name},
2059             __TYPE__ => 'tables',
2060             __BODY__ => [],
2061             };
2062              
2063             bless $self, $class;
2064              
2065             if ( defined $data->{ columns } ) {
2066             $self->_create_these_fields( $data->{ columns } );
2067             }
2068             else {
2069             $self->_create_default_fields();
2070             }
2071              
2072             if ( defined $data->{sequence} ) {
2073             my $seq_stmnt = table_element_block->new_statement(
2074             $self,
2075             'sequence',
2076             $data->{sequence},
2077             );
2078             push @{ $self->{__BODY__} }, $seq_stmnt;
2079             }
2080              
2081             $self->{__PARENT__} = $parent;
2082              
2083             return $self;
2084             }
2085              
2086             sub _create_default_fields {
2087             my $self = shift;
2088              
2089             my $id_field = table_element_block->new_field(
2090             $self, 'id'
2091             );
2092              
2093             $id_field->add_field_statement(
2094             {
2095             ident => $id_field->get_ident,
2096             keyword => 'is',
2097             new_value => 'int4][primary_key][auto',
2098             },
2099             );
2100              
2101             push @{ $self->{__BODY__} }, $id_field;
2102              
2103             my %values = (
2104             is => 'varchar',
2105             html_form_type => 'text'
2106             );
2107              
2108             foreach my $field_name qw( ident description ) {
2109              
2110             $values{ label } = Bigtop::ScriptHelp->default_label( $field_name );
2111              
2112             my $field = table_element_block->new_field(
2113             $self, $field_name
2114             );
2115              
2116             foreach my $statement qw( is label html_form_type ) {
2117             $field->add_field_statement(
2118             {
2119             ident => $field->get_ident,
2120             keyword => $statement,
2121             new_value => $values{ $statement },
2122             },
2123             );
2124             }
2125             push @{ $self->{__BODY__} }, $field;
2126             }
2127              
2128             foreach my $date_field qw( created modified ) {
2129             my $field = table_element_block->new_field(
2130             $self, $date_field
2131             );
2132             $field->add_field_statement(
2133             {
2134             ident => $field->get_ident,
2135             keyword => 'is',
2136             new_value => 'datetime',
2137             },
2138             );
2139             push @{ $self->{__BODY__} }, $field;
2140             }
2141             }
2142              
2143             sub _create_these_fields {
2144             my $self = shift;
2145             my $fields = shift;
2146              
2147             my %non_entry = (
2148             id => 1,
2149             created => 1,
2150             modified => 1,
2151             );
2152              
2153             foreach my $init_field ( @{ $fields } ) {
2154              
2155             if ( $init_field->{ default } ) {
2156             push @{ $init_field->{ types } },
2157             "DEFAULT '$init_field->{ default }'";
2158             }
2159              
2160             my $type_string = join '][', @{ $init_field->{ types } };
2161              
2162             my $field = table_element_block->new_field(
2163             $self, $init_field->{ name }
2164             );
2165              
2166             $field->add_field_statement(
2167             {
2168             ident => $field->get_ident,
2169             keyword => 'is',
2170             new_value => $type_string,
2171             },
2172             );
2173              
2174             unless ( $non_entry{ $init_field->{ name } } ) {
2175              
2176             my $label = Bigtop::ScriptHelp->default_label(
2177             $init_field->{ name }
2178             );
2179              
2180             $field->add_field_statement(
2181             {
2182             ident => $field->get_ident,
2183             keyword => 'label',
2184             new_value => $label,
2185             },
2186             );
2187              
2188             $field->add_field_statement(
2189             {
2190             ident => $field->get_ident,
2191             keyword => 'html_form_type',
2192             new_value => 'text',
2193             },
2194             );
2195              
2196             if ( defined $init_field->{ optional } ) {
2197             $field->add_field_statement(
2198             {
2199             ident => $field->get_ident,
2200             keyword => 'html_form_optional',
2201             new_value => $init_field->{ optional },
2202             },
2203             );
2204             }
2205             if ( $init_field->{ default } ) {
2206             $field->add_field_statement(
2207             {
2208             ident => $field->get_ident,
2209             keyword => 'html_form_default_value',
2210             new_value => $init_field->{ default },
2211             },
2212             );
2213             }
2214             }
2215              
2216             push @{ $self->{__BODY__} }, $field;
2217             }
2218             }
2219              
2220             sub add_subblock {
2221             my $self = shift;
2222             shift;
2223             my $data = shift;
2224              
2225             return unless ( $data->{parent}{type} eq 'table' );
2226             return unless ( $data->{parent}{ident} eq $self->get_ident );
2227             return unless ( $data->{new_child}{type} eq 'field' );
2228              
2229             my $new_field = table_element_block->new_field(
2230             $self, $data->{new_child}{name}
2231             );
2232              
2233             push @{ $self->{__BODY__} }, $new_field;
2234              
2235             return [ $new_field ];
2236             }
2237              
2238             sub remove_block {
2239             my $self = shift;
2240             shift;
2241             my $data = shift;
2242             my $doomed_ident = $data->{ ident };
2243              
2244             my $doomed_index = -1;
2245             my $count = 0;
2246              
2247             my $children = $self->{__BODY__};
2248              
2249             CHILD:
2250             foreach my $child ( @{ $children } ) {
2251             my $child_ident = $child->get_ident;
2252              
2253             next CHILD unless defined $child_ident;
2254              
2255             if ( $child_ident eq $doomed_ident ) {
2256             $doomed_index = $count;
2257             }
2258             }
2259             continue {
2260             $count++;
2261             }
2262              
2263             return if ( $doomed_index == -1 );
2264              
2265             my $deceased = splice @{ $children }, $doomed_index, 1;
2266              
2267             # do things if the we get this far
2268             my @retval;
2269              
2270             # remove name from foreign_display as needed
2271             my $result = $self->walk_postorder(
2272             'update_foreign_display',
2273             {
2274             old_value => $deceased->{__NAME__},
2275             new_value => '',
2276             ident => $self->{__IDENT__},
2277             }
2278             );
2279              
2280             push @retval, @{ $result } if ( ref( $result ) eq 'ARRAY' );
2281              
2282             # remove from controller form fields or all_fields_but
2283             # remove from controller cols (and col_labels)
2284              
2285             my $tree = $data->{ __THE_TREE__ };
2286              
2287             $result = $tree->walk_postorder( 'field_removed',
2288             {
2289             table_name => $self->get_name(),
2290             dead_field_name => $deceased->{__NAME__},
2291             }
2292             );
2293              
2294             push @retval, @{ $result } if ( ref( $result ) eq 'ARRAY' );
2295              
2296             return \@retval;
2297             }
2298              
2299             sub app_block_hashes {
2300             my $self = shift;
2301             my $child_output = shift;
2302              
2303             my $body = {
2304             statements => {},
2305             fields => [],
2306             };
2307              
2308             foreach my $child_item ( @{ $child_output } ) {
2309             if ( $child_item->{ type } eq 'statement' ) {
2310             if ( $child_item->{ keyword } eq 'data' ) {
2311             push @{ $body->{ statements }{ data } },
2312             $child_item->{ value };
2313             }
2314             else {
2315             $body->{ statements }{ $child_item->{ keyword } } =
2316             $child_item->{ value };
2317             }
2318             }
2319             else {
2320             push @{ $body->{ fields } }, $child_item;
2321             }
2322             }
2323              
2324             return [ {
2325             type => 'table',
2326             body => $body,
2327             name => $self->get_name,
2328             ident => $self->get_ident,
2329             } ];
2330             }
2331              
2332             sub change_name_table {
2333             my $self = shift;
2334             shift;
2335             my $data = shift;
2336              
2337             return unless $self->get_ident eq $data->{ident};
2338              
2339             my $old_name = $self->get_name();
2340             $self->set_name( $data->{new_value} );
2341              
2342             return $data->{__THE_TREE__}->walk_postorder( 'table_name_changed',
2343             {
2344             old_name => $old_name,
2345             new_name => $data->{ new_value }
2346             }
2347             );
2348             }
2349              
2350             sub get_ident {
2351             my $self = shift;
2352              
2353             return $self->{__IDENT__};
2354             }
2355              
2356             sub get_name {
2357             my $self = shift;
2358              
2359             return $self->{__NAME__};
2360             }
2361              
2362             sub all_table_names {
2363             my $self = shift;
2364              
2365             return [ $self->{__NAME__} ];
2366             }
2367              
2368             sub set_name {
2369             my $self = shift;
2370             my $new_name = shift;
2371              
2372             $self->{__NAME__} = $new_name;
2373              
2374             # update lookup hash?
2375             }
2376              
2377             sub show_idents {
2378             my $self = shift;
2379             my $child_output = shift;
2380              
2381             push @{ $child_output },
2382             [ $self->{ __TYPE__ }, $self->{ __NAME__ }, $self->{ __IDENT__ } ];
2383              
2384             return $child_output;
2385             }
2386              
2387             sub walk_postorder {
2388             my $self = shift;
2389             my $action = shift;
2390             my $data = shift;
2391             my $parent = shift;
2392              
2393             my $output = [];
2394              
2395             foreach my $body_element ( @{ $self->{__BODY__} } ) {
2396             my $child_output = $body_element->walk_postorder(
2397             $action, $data, $self
2398             );
2399              
2400             push @{ $output }, @{ $child_output } if $child_output;
2401             }
2402              
2403             if ( $self->can( $action ) ) {
2404             $output = $self->$action( $output, $data, $parent );
2405             }
2406              
2407             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
2408             }
2409              
2410             sub build_lookup_hash {
2411             my $self = shift;
2412             my $child_output = shift;
2413             my $data = shift;
2414              
2415             my %output;
2416              
2417             foreach my $element ( @{ $child_output } ) {
2418             my $output_type = $element->{__TYPE__};
2419              
2420             my $name = $element->{__DATA__}[0];
2421              
2422             if ( $output_type eq 'data' ) {
2423             push @{ $output{ data }{ $name } }, $element->{__DATA__}[1];
2424             }
2425             else {
2426             $output{ $output_type }{ $name } = $element->{__DATA__}[1];
2427             }
2428             }
2429              
2430             $output{ __IDENT__ } = $self->{ __IDENT__ };
2431              
2432             my $retval = [
2433             {
2434             __TYPE__ => $self->{__TYPE__},
2435             __DATA__ => [ $self->get_name() => \%output ],
2436             }
2437             ];
2438              
2439             return [
2440             {
2441             __TYPE__ => $self->{__TYPE__},
2442             __DATA__ => [ $self->get_name() => \%output ],
2443             }
2444             ];
2445             }
2446              
2447             sub change_table_statement {
2448             my $self = shift;
2449             shift;
2450             my $data = shift;
2451              
2452             return unless ( $self->{__TYPE__} eq 'tables' );
2453             return unless ( $self->get_ident eq $data->{ident} );
2454              
2455             my $success = $self->walk_postorder( 'change_table_keyword_value', $data );
2456              
2457             unless ( defined $success->[0] ) { # make new statement
2458             $self->add_table_statement( $data );
2459             }
2460              
2461             return [ 1 ];
2462             }
2463              
2464             sub add_table_statement {
2465             my $self = shift;
2466             my $data = shift;
2467              
2468             my $new_statement = table_element_block->new_statement(
2469             $self,
2470             $data->{keyword},
2471             $data->{new_value},
2472             );
2473              
2474             my $blocks = $self->{ __BODY__ };
2475             push @{ $blocks }, $new_statement;
2476             }
2477              
2478             sub remove_table_statement {
2479             my $self = shift;
2480             shift;
2481             my $data = shift;
2482              
2483             return unless ( $self->{__TYPE__} eq 'tables' );
2484             return unless ( $self->get_ident eq $data->{ident} );
2485              
2486             my $doomed_child = -1;
2487             my $count = 0;
2488              
2489             BLOCK:
2490             foreach my $block ( @{ $self->{__BODY__} } ) {
2491             next BLOCK unless $block->{__TYPE__} eq $data->{keyword};
2492              
2493             $doomed_child = $count;
2494             last BLOCK;
2495             }
2496             continue {
2497             $count++;
2498             }
2499              
2500             if ( $doomed_child >= 0 ) {
2501             # This probably leaks memory because children have parent pointers.
2502             # But the parent is me and I'm the app_body, so maybe not.
2503             splice @{ $self->{__BODY__} },
2504             $doomed_child,
2505             1;
2506             }
2507             # else, nothing to see here, move along quietly
2508              
2509             return [ 1 ];
2510             }
2511              
2512             sub change_data_statement {
2513             my $self = shift;
2514             my $child_output = shift;
2515             my $data = shift;
2516              
2517             return unless ( $self->{__IDENT__} eq $data->{ table } );
2518              
2519             my %field_names = @{ $self->walk_postorder( 'get_field_names' ) };
2520             my $name_to_change = $field_names{ $data->{ field } };
2521              
2522             my $target = $child_output->[ $data->{ st_number } - 1 ];
2523              
2524             if ( defined $target ) {
2525             my $found = 0;
2526             my $remove_it = -1;
2527             my $count = -1;
2528             ARG:
2529             foreach my $arg ( @{ $target->{__ARGS__} } ) {
2530             $count++;
2531              
2532             next unless defined $arg->{ $name_to_change };
2533              
2534             if ( defined $data->{ value } ) {
2535             $arg->{ $name_to_change } = $data->{ value };
2536             $found++;
2537             last ARG;
2538             }
2539             else {
2540             $remove_it = $count;
2541             }
2542             }
2543             if ( $remove_it >= 0 ) {
2544             splice @{ $target->{__ARGS__} }, $remove_it, 1;
2545              
2546             if ( @{ $target->{__ARGS__} } == 0 ) { # no more keys, kill it
2547             my $doomed_child = -1;
2548             my $count = -1;
2549             CHILD:
2550             foreach my $child ( @{ $self->{__BODY__} } ) {
2551             $count++;
2552             if ( $child eq $target ) {
2553             $doomed_child = $count;
2554             last CHILD;
2555             }
2556             }
2557             if ( $doomed_child >= 0 ) {
2558             splice @{ $self->{__BODY__} }, $doomed_child, 1;
2559             }
2560             }
2561             }
2562             elsif ( not $found ) {
2563             push @{ $target->{__ARGS__} },
2564             { $name_to_change => $data->{ value } };
2565             }
2566             }
2567             else {
2568             $self->add_table_statement(
2569             {
2570             ident => $self->get_ident,
2571             keyword => 'data',
2572             new_value => {
2573             keys => $name_to_change,
2574             values => $data->{value},
2575             }
2576             },
2577             );
2578             }
2579              
2580             return $self->walk_postorder( 'app_block_hashes' );
2581             }
2582              
2583             sub table_reset_bool {
2584             my $self = shift;
2585             shift;
2586             my $data = shift;
2587              
2588             return unless $self->{ __IDENT__} eq $data->{ ident };
2589              
2590             return $self->walk_postorder( 'field_reset_bool', $data );
2591             }
2592              
2593             sub get_table_statement {
2594             my $self = shift;
2595             shift;
2596             my $data = shift;
2597              
2598             return unless ( $self->{__TYPE__} eq 'tables' );
2599             return unless ( $self->get_ident eq $data->{ident} );
2600              
2601             BLOCK:
2602             foreach my $block ( @{ $self->{__BODY__} } ) {
2603             next BLOCK unless $block->{__TYPE__} eq $data->{keyword};
2604              
2605             return [ $block->{__ARGS__}->get_unquoted_args ];
2606             }
2607              
2608             return;
2609             }
2610              
2611             package # seq_block
2612             seq_block;
2613             use strict; use warnings;
2614              
2615             use base 'application_ancestor';
2616              
2617             sub new_block {
2618             my $class = shift;
2619             my $parent = shift;
2620             my $data = shift;
2621              
2622             my $self = {
2623             __IDENT__ => Bigtop::Parser->get_ident(),
2624             __NAME__ => $data->{name},
2625             __TYPE__ => 'sequences',
2626             __BODY__ => [],
2627             __PARENT__ => $parent,
2628             };
2629              
2630             return bless $self, $class;
2631             }
2632              
2633             sub app_block_hashes {
2634             my $self = shift;
2635             my $child_output = shift;
2636              
2637             return [ {
2638             type => 'sequence',
2639             body => undef,
2640             name => $self->get_name,
2641             ident => $self->get_ident,
2642             } ];
2643             }
2644              
2645             sub get_ident {
2646             my $self = shift;
2647              
2648             return $self->{__IDENT__};
2649             }
2650              
2651             sub get_name {
2652             my $self = shift;
2653              
2654             return $self->{__NAME__};
2655             }
2656              
2657             sub set_name {
2658             my $self = shift;
2659             my $new_name = shift;
2660              
2661             $self->{__NAME__} = $new_name;
2662              
2663             # update lookup hash?
2664             }
2665              
2666             sub change_name_sequence {
2667             my $self = shift;
2668             shift;
2669             my $params = shift;
2670              
2671             return unless $self->get_ident eq $params->{ident};
2672              
2673             $self->set_name( $params->{new_value} );
2674              
2675             return [ 1 ];
2676             }
2677              
2678             sub walk_postorder {
2679             my $self = shift;
2680             my $action = shift;
2681             my $data = shift;
2682             my $parent = shift;
2683              
2684             my $output = [];
2685              
2686             # This might be needed if sequence blocks ever have statements.
2687             #
2688             # foreach my $seq_statement ( @{ $self->{__BODY__} } ) {
2689             # my $child_output = $seq_statement->walk_postorder(
2690             # $action, $data, $self
2691             # );
2692             #
2693             # push @{ $output }, @{ $child_output } if $child_output;
2694             # }
2695              
2696             if ( $self->can( $action ) ) {
2697             $output = $self->$action( $output, $data, $parent );
2698             }
2699              
2700             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
2701             }
2702              
2703             sub build_lookup_hash {
2704             my $self = shift;
2705             my $child_output = shift;
2706             my $data = shift;
2707              
2708             return [
2709             {
2710             __TYPE__ => $self->{__TYPE__},
2711             __DATA__ => [ $self->{__NAME__} => $self->{__IDENT__} ],
2712             }
2713             ];
2714             }
2715              
2716             sub show_idents {
2717             my $self = shift;
2718              
2719             return [ $self->{ __TYPE__ }, $self->{ __NAME__ }, $self->{ __IDENT__ } ];
2720             }
2721              
2722             package # schema_block
2723             schema_block;
2724             use strict; use warnings;
2725              
2726             use base 'application_ancestor';
2727              
2728             sub new_block {
2729             my $class = shift;
2730             my $parent = shift;
2731             my $data = shift;
2732              
2733             my $self = {
2734             __IDENT__ => Bigtop::Parser->get_ident(),
2735             __NAME__ => $data->{name},
2736             __PARENT__ => $parent,
2737             };
2738              
2739             return bless $self, $class;
2740             }
2741              
2742             sub app_block_hashes {
2743             my $self = shift;
2744             my $child_output = shift;
2745              
2746             return [ {
2747             type => 'schema',
2748             body => undef,
2749             name => $self->get_name,
2750             ident => $self->get_ident,
2751             } ];
2752             }
2753              
2754             sub get_ident {
2755             my $self = shift;
2756              
2757             return $self->{__IDENT__};
2758             }
2759              
2760             sub set_name {
2761             my $self = shift;
2762             my $new_name = shift;
2763              
2764             $self->{__NAME__} = $new_name;
2765             }
2766              
2767             sub get_name {
2768             my $self = shift;
2769              
2770             return $self->{__NAME__};
2771             }
2772              
2773             sub change_name_schema {
2774             my $self = shift;
2775             shift;
2776             my $params = shift;
2777              
2778             return unless $self->get_ident eq $params->{ident};
2779              
2780             $self->set_name( $params->{new_value} );
2781              
2782             return [ 1 ];
2783             }
2784              
2785             sub walk_postorder {
2786             my $self = shift;
2787             my $action = shift;
2788             my $data = shift;
2789             my $parent = shift;
2790              
2791             my $output = [];
2792              
2793             if ( $self->can( $action ) ) {
2794             $output = $self->$action( $output, $data, $parent );
2795             }
2796              
2797             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
2798             }
2799              
2800             sub build_lookup_hash {
2801             my $self = shift;
2802              
2803             return [
2804             {
2805             __TYPE__ => 'schema',
2806             __DATA__ => [ $self->{__NAME__} => $self->{__IDENT__} ],
2807             }
2808             ];
2809             }
2810              
2811             sub show_idents {
2812             my $self = shift;
2813              
2814             return [ 'schema', $self->{__NAME__}, $self->{__IDENT__} ];
2815             }
2816              
2817             package # sequence_statement
2818             sequence_statement;
2819             use strict; use warnings;
2820              
2821             use base 'application_ancestor';
2822              
2823             sub walk_postorder {
2824             my $self = shift;
2825             my $action = shift;
2826             my $data = shift;
2827             my $parent = shift;
2828              
2829             if ( $self->can( $action ) ) {
2830             return $self->$action( undef, $data, $parent );
2831             }
2832             else {
2833             return;
2834             }
2835             }
2836              
2837             sub build_lookup_hash {
2838             my $self = shift;
2839             my $child_output = shift;
2840             my $data = shift;
2841              
2842             return [
2843             {
2844             '__TYPE__' => 'sequences',
2845             '__DATA__' => [
2846             $self->{__NAME__} => $self->{__ARGS__},
2847             ]
2848             }
2849             ];
2850             }
2851              
2852             package # table_element_block
2853             table_element_block;
2854             use strict; use warnings;
2855              
2856             use base 'application_ancestor';
2857              
2858             sub new_statement {
2859             my $class = shift;
2860             my $parent = shift;
2861             my $keyword = shift;
2862             my $values = shift;
2863              
2864             my $self = {
2865             __PARENT__ => $parent,
2866             __BODY__ => $keyword,
2867             __TYPE__ => $keyword,
2868             __ARGS__ => arg_list->new( $values ),
2869             };
2870              
2871             return bless $self, $class;
2872             }
2873              
2874             sub new_field {
2875             my $class = shift;
2876             my $parent = shift;
2877             my $name = shift;
2878              
2879             my $self = {
2880             __PARENT__ => $parent,
2881             __TYPE__ => 'field',
2882             __IDENT__ => Bigtop::Parser->get_ident(),
2883             __NAME__ => $name,
2884             __BODY__ => [],
2885             };
2886              
2887             return bless $self, $class;
2888             }
2889              
2890             sub app_block_hashes {
2891             my $self = shift;
2892             my $child_output = shift;
2893              
2894             my %statements;
2895              
2896             foreach my $child_item ( @{ $child_output } ) {
2897             $statements{ $child_item->{ keyword } } = $child_item->{ values };
2898             }
2899              
2900             if ( $self->{__TYPE__} eq 'field' ) {
2901             return [ {
2902             type => 'field',
2903             name => $self->get_name,
2904             ident => $self->get_ident,
2905             statements => \%statements,
2906             } ];
2907             }
2908             else {
2909             return [ {
2910             ident => $self->get_ident,
2911             type => 'statement',
2912             keyword => $self->{__BODY__},
2913             value => $self->{__ARGS__},
2914             } ];
2915             }
2916             }
2917              
2918             sub get_name {
2919             my $self = shift;
2920              
2921             return $self->{__NAME__};
2922             }
2923              
2924             sub all_field_names {
2925             my $self = shift;
2926             shift;
2927             my $desired_table = shift;
2928              
2929             return unless ( $self->get_table_name eq $desired_table );
2930              
2931             return [ $self->get_name ];
2932             }
2933              
2934             sub set_name {
2935             my $self = shift;
2936             my $new_name = shift;
2937              
2938             $self->{__NAME__} = $new_name;
2939             }
2940              
2941             sub get_ident {
2942             my $self = shift;
2943              
2944             return $self->{__IDENT__};
2945             }
2946              
2947             sub get_table_name {
2948             my $self = shift;
2949              
2950             # does this still work for join_tables?
2951             return $self->{__PARENT__}{__NAME__};
2952             }
2953              
2954             sub get_table_ident {
2955             my $self = shift;
2956              
2957             return $self->{__PARENT__}{__IDENT__};
2958             }
2959              
2960             sub show_idents {
2961             my $self = shift;
2962             my $child_output = shift;
2963              
2964             return unless $self->{ __TYPE__ } eq 'field';
2965              
2966             push @{ $child_output },
2967             [ 'field', $self->{ __NAME__ }, $self->{ __IDENT__ } ];
2968              
2969             return $child_output;
2970             }
2971              
2972             sub walk_postorder {
2973             my $self = shift;
2974             my $action = shift;
2975             my $data = shift;
2976             my $parent = shift;
2977              
2978             my $output;
2979              
2980             if ( $self->{__TYPE__} eq 'field' ) {
2981             foreach my $field_stmnt ( @{ $self->{__BODY__} } ) {
2982             my $child_output = $field_stmnt->walk_postorder(
2983             $action, $data, $self
2984             );
2985            
2986             push @{ $output }, @{ $child_output } if $child_output;
2987             }
2988             }
2989              
2990             if ( $self->can( $action ) ) {
2991             $output = $self->$action( $output, $data, $parent );
2992             }
2993              
2994             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
2995             }
2996              
2997             sub build_lookup_hash {
2998             my $self = shift;
2999             my $child_output = shift;
3000             my $data = shift;
3001              
3002             my %output;
3003              
3004             if ( $child_output ) {
3005             my %sub_output;
3006              
3007             foreach my $element ( @{ $child_output } ) {
3008             my $output_type = $element->{__TYPE__};
3009              
3010             my $name = $element->{__DATA__}[0];
3011              
3012             $sub_output{ $output_type }{ $name } = $element->{__DATA__}[1];
3013             }
3014              
3015             $sub_output{ __IDENT__ } = $self->{ __IDENT__ };
3016              
3017             %output = (
3018             '__TYPE__' => 'fields',
3019             '__DATA__' => [
3020             $self->{__NAME__} => \%sub_output,
3021             ],
3022             );
3023             }
3024             # for non-field statements
3025             else {
3026             %output = (
3027             '__TYPE__' => $self->{__BODY__},
3028             '__DATA__' => [
3029             __ARGS__ => $self->{__ARGS__},
3030             ],
3031             );
3032             }
3033              
3034             return [ \%output ];
3035             }
3036              
3037             sub change_table_keyword_value {
3038             my $self = shift;
3039             shift;
3040             my $data = shift;
3041              
3042             return if ( defined $self->get_name ); # only fields have names
3043              
3044             return unless ( $self->{__BODY__} eq $data->{keyword} );
3045              
3046             $self->{__ARGS__}->set_args_from(
3047             $data->{new_value},
3048             $data->{pair_required},
3049             );
3050              
3051             return [ 1 ];
3052             }
3053              
3054             sub change_field_statement {
3055             my $self = shift;
3056             shift;
3057             my $data = shift;
3058              
3059             return unless ( $self->{__TYPE__} eq 'field' );
3060             return unless ( $self->get_ident eq $data->{ident} );
3061              
3062             my $success = $self->walk_postorder( 'change_field_keyword_value', $data );
3063              
3064             unless ( defined $success->[0] ) { # make new statement
3065              
3066             $success = [ $self->add_field_statement( $data ) ];
3067             }
3068              
3069             # This array needs to be two levels deep.
3070             return [ $success ];
3071             }
3072              
3073             sub get_field_statement {
3074             my $self = shift;
3075             my $child_output = shift;
3076             my $data = shift;
3077              
3078             return unless ( $self->{__TYPE__} eq 'field' );
3079             return unless ( $self->get_ident eq $data->{ident} );
3080              
3081             return $child_output;
3082             }
3083              
3084             sub add_field_statement {
3085             my $self = shift;
3086             my $data = shift;
3087              
3088             my $new_statement = field_statement->new_statement(
3089             {
3090             parent => $self,
3091             keyword => $data->{keyword},
3092             new_value => $data->{new_value},
3093             pair_required => $data->{pair_required} || 0,
3094             }
3095             );
3096              
3097             my $blocks = $self->{ __BODY__ };
3098             push @{ $blocks }, $new_statement;
3099              
3100             if ( $data->{ keyword } eq 'is' ) {
3101             my %values = map { $_ => 1 } split /\]\[/, $data->{ new_value };
3102             return 'date' if ( $values{ date } );
3103             }
3104             elsif ( $data->{ keyword } eq 'date_select_text' ) {
3105             return 'date_select_text';
3106             }
3107              
3108             return 1;
3109             }
3110              
3111             sub remove_field_statement {
3112             my $self = shift;
3113             shift;
3114             my $data = shift;
3115              
3116             return unless ( $self->{__TYPE__} eq 'field' );
3117             return unless ( $self->get_ident eq $data->{ident} );
3118              
3119             my $statements = $self->{ __BODY__ };
3120             my $doomed_index = get_statement_index( $statements, $data->{keyword} );
3121              
3122             if ( $doomed_index >= 0 ) {
3123             splice @{ $statements }, $doomed_index, 1;
3124             return [ 1 ];
3125             }
3126             else {
3127             return [ 0 ];
3128             }
3129             }
3130              
3131             sub get_statement_index {
3132             my $statements = shift;
3133             my $target_name = shift;
3134              
3135             my $target_index = -1;
3136             my $count = 0;
3137              
3138             STATEMENT:
3139             foreach my $statement ( @{ $statements } ) {
3140             if ( $statement->get_name eq $target_name ) {
3141             $target_index = $count;
3142             last STATEMENT;
3143             }
3144             }
3145             continue {
3146             $count++;
3147             }
3148              
3149             return $target_index;
3150             }
3151              
3152             sub change_name_field {
3153             my $self = shift;
3154             shift;
3155             my $data = shift;
3156              
3157             return unless ( defined $self->get_ident ); # only fields can change names
3158              
3159             return unless $self->get_ident eq $data->{ident};
3160              
3161             my @retval;
3162              
3163             my $old_name = $self->get_name(); # who were we fka
3164             $self->set_name( $data->{ new_value } );
3165              
3166             # update our label, if the old one was the default label
3167             my $result = $self->walk_postorder( 'update_label',
3168             { old_name => $old_name, new_name => $data->{ new_value } }
3169             );
3170              
3171             push @retval, @{ $result } if ( ref( $result ) eq 'ARRAY' );
3172              
3173             $data->{ old_value } = $old_name;
3174             $result = $self->{ __PARENT__ }->walk_postorder(
3175             'update_foreign_display', $data
3176             );
3177              
3178             push @retval, @{ $result } if ( ref( $result ) eq 'ARRAY' );
3179              
3180             my $tree = $data->{ __THE_TREE__ };
3181              
3182             $result = $tree->walk_postorder( 'field_name_changed',
3183             {
3184             table_name => $self->get_table_name(),
3185             old_field_name => $old_name,
3186             new_field_name => $data->{ new_value },
3187             }
3188             );
3189              
3190             push @retval, @{ $result } if ( ref( $result ) eq 'ARRAY' );
3191              
3192             return \@retval;
3193             }
3194              
3195             sub change_data_statement {
3196             my $self = shift;
3197             shift;
3198             my $data = shift;
3199              
3200             return if ( defined $self->{__IDENT__} );
3201             return unless ( $self->{__TYPE__} eq 'data' );
3202              
3203             return [ $self ];
3204             }
3205              
3206             sub get_field_names {
3207             my $self = shift;
3208             shift;
3209             my $data = shift;
3210              
3211             return unless ( defined $self->{__IDENT__} );
3212              
3213             return [ $self->{__IDENT__} => $self->{__NAME__} ];
3214             }
3215              
3216             # if a renamed field is in foreign_display, update it
3217             sub update_foreign_display {
3218             my $self = shift;
3219             shift;
3220             my $data = shift;
3221              
3222             return unless $self->{ __TYPE__ } eq 'foreign_display';
3223              
3224             my $display = $self->{ __ARGS__ }->get_first_arg;
3225             my $old_display = $display;
3226              
3227             if ( $data->{ new_value } ) {
3228             $display =~ s/%$data->{ old_value }/%$data->{ new_value }/g;
3229             }
3230             else {
3231             $display =~ s/%$data->{ old_value }//g;
3232             }
3233              
3234             if ( $display =~ /^\s*$/ ) {
3235             $display = '';
3236             $self->{__PARENT__}->remove_table_statement(
3237             undef,
3238             {
3239             ident => $data->{ ident },
3240             keyword => 'foreign_display',
3241             }
3242             );
3243             }
3244             else {
3245             $self->{ __ARGS__ }->set_args_from( $display );
3246             }
3247              
3248             if ( $display eq $old_display ) {
3249             return;
3250             }
3251             else {
3252             return [ $self->get_table_ident() . '::foreign_display' => $display ];
3253             }
3254             }
3255              
3256             sub get_table_name_from_field_ident {
3257             my $self = shift;
3258             shift;
3259             my $data = shift;
3260              
3261             return unless $self->{ __TYPE__ } eq 'field';
3262             return unless $self->{ __IDENT__ } eq $data->{ ident };
3263              
3264             return [ $self->get_table_name ];
3265             }
3266              
3267             sub field_reset_bool {
3268             my $self = shift;
3269             my $child_output = shift;
3270             my $data = shift;
3271              
3272             return unless $self->{ __TYPE__ } eq 'field';
3273             return if $self->{ __NAME__ } eq 'id';
3274              
3275             unless ( $child_output->[0] ) {
3276             $self->add_field_statement(
3277             {
3278             keyword => $data->{keyword},
3279             new_value => $data->{new_value},
3280             }
3281             );
3282             }
3283              
3284             return [ $self->{ __IDENT__ } ];
3285             }
3286              
3287             package # field_statement
3288             field_statement;
3289             use strict; use warnings;
3290              
3291             use base 'application_ancestor';
3292              
3293             sub new_statement {
3294             my $class = shift;
3295             my $params = shift;
3296              
3297             my $self = {
3298             __PARENT__ => $params->{ parent },
3299             __KEYWORD__ => $params->{ keyword },
3300             __DEF__ => field_statement_def->new(
3301             $params->{ new_value },
3302             $params->{ pair_required },
3303             ),
3304             };
3305              
3306             $self->{__DEF__}{__PARENT__} = $self;
3307              
3308             return bless $self, $class;
3309             }
3310              
3311             sub app_block_hashes {
3312             my $self = shift;
3313             my $child_output = shift;
3314              
3315             return [ {
3316             keyword => $self->get_name,
3317             values => $self->get_values,
3318             } ];
3319             }
3320              
3321             sub get_table_name {
3322             my $self = shift;
3323              
3324             # table_elemnt_block table_block
3325             return $self->{__PARENT__}{__PARENT__}{__NAME__};
3326             }
3327              
3328             sub get_field_ident {
3329             my $self = shift;
3330              
3331             return $self->{__PARENT__}{__IDENT__};
3332             }
3333              
3334             sub get_field_name {
3335             my $self = shift;
3336              
3337             return $self->{__PARENT__}{__NAME__};
3338             }
3339              
3340             sub get_name {
3341             my $self = shift;
3342              
3343             return $self->{__KEYWORD__};
3344             }
3345              
3346             sub get_values {
3347             my $self = shift;
3348              
3349             return $self->{__DEF__}{__ARGS__};
3350             }
3351              
3352             sub change_field_keyword_value {
3353             my $self = shift;
3354             shift;
3355             my $data = shift;
3356              
3357             return unless ( $data->{type} eq 'field' );
3358             return unless ( $self->{__KEYWORD__} eq $data->{keyword} );
3359              
3360             $self->{__DEF__}{__ARGS__}->set_args_from(
3361             $data->{new_value},
3362             $data->{pair_required},
3363             );
3364              
3365             # see if we changed the SQL type to date
3366             my %values = map { $_ => 1 } split /\]\[/, $data->{ new_value };
3367             if ( $data->{ keyword } eq 'is' and $values{ date } ) {
3368             return [ 'date' ];
3369             }
3370             elsif ( $data->{ keyword } eq 'date_select_text'
3371             and
3372             $data->{ new_value } )
3373             {
3374             return [ 'date_select_text' ];
3375             }
3376              
3377             return [ 1 ];
3378             }
3379              
3380             sub get_field_statement {
3381             my $self = shift;
3382             shift;
3383             my $data = shift;
3384              
3385             return unless ( $data->{ keyword } eq $self->{ __KEYWORD__ } );
3386              
3387             return [ $self->{ __DEF__ }{ __ARGS__ } ];
3388             }
3389              
3390             # If the old label was the default, the label will be changed to default
3391             # for new name.
3392             sub update_label {
3393             my $self = shift;
3394             my $child_output = shift;
3395             my $data = shift;
3396              
3397             return unless $self->{ __KEYWORD__ } eq 'label';
3398              
3399             my $field_ident = $self->get_field_ident();
3400              
3401             my $old_label = $child_output->[0];
3402             my $old_default_label = Bigtop::ScriptHelp->default_label(
3403             $data->{ old_name }
3404             );
3405              
3406             if ( $old_label eq $old_default_label ) {
3407             my $new_label = Bigtop::ScriptHelp->default_label(
3408             $data->{ new_name }
3409             );
3410             $self->{__DEF__}{__ARGS__}->set_args_from( $new_label );
3411              
3412             return [ $field_ident . '::label' => $new_label ];
3413             }
3414              
3415             return;
3416             }
3417              
3418             sub table_name_changed {
3419             my $self = shift;
3420             shift;
3421             my $data = shift;
3422              
3423             return unless $self->{ __KEYWORD__ } eq 'refers_to';
3424              
3425             my $current_foreigner = $self->{ __DEF__ }{ __ARGS__ }->get_first_arg;
3426              
3427             if ( $current_foreigner eq $data->{ old_name } ) {
3428             $self->{ __DEF__}{ __ARGS__ }->set_args_from( $data->{ new_name } );
3429              
3430             return [ $self->get_field_ident . '::refers_to', $data->{ new_name } ];
3431             }
3432              
3433             return;
3434             }
3435              
3436             sub field_reset_bool {
3437             my $self = shift;
3438             shift;
3439             my $data = shift;
3440              
3441             return unless $self->{ __KEYWORD__ } eq $data->{ keyword };
3442              
3443             $self->{ __DEF__ }{ __ARGS__ }[0] = $data->{ new_value };
3444              
3445             return [ 1 ];
3446             }
3447              
3448             sub walk_postorder {
3449             my $self = shift;
3450             my $action = shift;
3451             my $data = shift;
3452             my $parent = shift;
3453              
3454             my $output;
3455            
3456             if ( $self->{__DEF__}->can( 'walk_postorder' ) ) {
3457             $output = $self->{__DEF__}->walk_postorder( $action, $data, $self );
3458             }
3459              
3460             if ( $self->can( $action ) ) {
3461             $output = $self->$action( $output, $data, $parent );
3462             }
3463              
3464             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
3465             }
3466              
3467             sub build_lookup_hash {
3468             my $self = shift;
3469             my $child_output = shift;
3470             my $data = shift;
3471              
3472             return [
3473             {
3474             '__TYPE__' => $self->{__KEYWORD__},
3475             '__DATA__' => [ @{ $child_output } ],
3476             }
3477             ];
3478             }
3479              
3480             package # field_statement_def
3481             field_statement_def;
3482             use strict; use warnings;
3483              
3484             use base 'application_ancestor';
3485              
3486             sub new {
3487             my $class = shift;
3488             my $values = shift;
3489             my $pair_required = shift;
3490              
3491             my $self = {
3492             __ARGS__ => arg_list->new( $values, $pair_required ),
3493             };
3494              
3495             return bless $self, $class;
3496             }
3497              
3498             sub update_label {
3499             my $self = shift;
3500              
3501             return [ $self->{ __ARGS__ }->get_first_arg ];
3502             }
3503              
3504             sub walk_postorder {
3505             my $self = shift;
3506             my $action = shift;
3507             my $data = shift;
3508             my $parent = shift;
3509              
3510             if ( $self->can( $action ) ) {
3511             return $self->$action( undef, $data, $parent );
3512             }
3513             else {
3514             return;
3515             }
3516             }
3517              
3518             sub build_lookup_hash {
3519             my $self = shift;
3520             my $child_output = shift;
3521             my $data = shift;
3522              
3523             return [ 'args' => $self->{__ARGS__} ];
3524             }
3525              
3526             package # extra_sql_block
3527             extra_sql_block;
3528             use strict; use warnings;
3529              
3530             use base 'application_ancestor';
3531              
3532             sub walk_postorder {
3533             my $self = shift;
3534             my $action = shift;
3535             my $data = shift;
3536             my $parent = shift;
3537              
3538             my $output;
3539              
3540             # if we add more extra_sql types, we might need this:
3541             #if ( $self->{__TYPE__} eq 'extra_sql' ) {
3542             foreach my $stmnt ( @{ $self->{__BODY__} } ) {
3543             my $child_output = $stmnt->walk_postorder(
3544             $action, $data, $self
3545             );
3546            
3547             push @{ $output }, @{ $child_output } if $child_output;
3548             }
3549             #}
3550              
3551             if ( $self->can( $action ) ) {
3552             $output = $self->$action( $output, $data, $parent );
3553             }
3554              
3555             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
3556             }
3557              
3558             sub build_lookup_hash {
3559             my $self = shift;
3560             my $child_output = shift;
3561             my $data = shift;
3562              
3563             my %output;
3564              
3565             if ( $child_output ) {
3566             my %sub_output;
3567              
3568             foreach my $element ( @{ $child_output } ) {
3569             my $output_type = $element->{__TYPE__};
3570             my $name = $element->{__DATA__}[0];
3571             $sub_output{ $output_type }{ $name } = $element->{__DATA__}[1];
3572             }
3573              
3574             $sub_output{ __IDENT__ } = $self->{ __IDENT__ };
3575              
3576             %output = (
3577             '__TYPE__' => 'extra_sqls',
3578             '__DATA__' => [
3579             $self->{__NAME__} => \%sub_output,
3580             ],
3581             );
3582             }
3583             return [ \%output ];
3584             }
3585              
3586             package # extra_sql_statement
3587             extra_sql_statement;
3588             use strict; use warnings;
3589              
3590             use base 'application_ancestor';
3591              
3592             sub walk_postorder {
3593             my $self = shift;
3594             my $action = shift;
3595             my $data = shift;
3596             my $parent = shift;
3597              
3598             my $output;
3599            
3600             if ( $self->{__DEF__}->can( 'walk_postorder' ) ) {
3601             $output = $self->{__DEF__}->walk_postorder( $action, $data, $self );
3602             }
3603              
3604             if ( $self->can( $action ) ) {
3605             $output = $self->$action( $output, $data, $parent );
3606             }
3607              
3608             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
3609             }
3610              
3611             sub build_lookup_hash {
3612             my $self = shift;
3613             my $child_output = shift;
3614             my $data = shift;
3615              
3616             return [
3617             {
3618             '__TYPE__' => $self->{__KEYWORD__},
3619             '__DATA__' => [ @{ $child_output } ],
3620             }
3621             ];
3622             }
3623              
3624             package # extra_sql_statement_def
3625             extra_sql_statement_def;
3626             use strict; use warnings;
3627              
3628             use base 'application_ancestor';
3629              
3630             sub walk_postorder {
3631             my $self = shift;
3632             my $action = shift;
3633             my $data = shift;
3634             my $parent = shift;
3635              
3636             if ( $self->can( $action ) ) {
3637             return $self->$action( undef, $data, $parent );
3638             }
3639             else {
3640             return;
3641             }
3642             }
3643              
3644             sub build_lookup_hash {
3645             my $self = shift;
3646             my $child_output = shift;
3647             my $data = shift;
3648              
3649             return [ 'args' => $self->{__ARGS__} ];
3650             }
3651              
3652             package # join_table
3653             join_table;
3654             use strict; use warnings;
3655              
3656             use base 'application_ancestor';
3657              
3658             sub new_block {
3659             my $class = shift;
3660             my $parent = shift;
3661             my $data = shift;
3662              
3663             my $self;
3664              
3665             $self = {
3666             __IDENT__ => Bigtop::Parser->get_ident(),
3667             __NAME__ => $data->{name},
3668             __BODY__ => [],
3669             };
3670              
3671             $self->{__PARENT__} = $parent;
3672              
3673             return bless $self, $class;
3674             }
3675              
3676             sub change_join_table_statement {
3677             my $self = shift;
3678             shift;
3679             my $data = shift;
3680              
3681             return unless ( $self->get_ident eq $data->{ident} );
3682              
3683             my $success = $self->walk_postorder(
3684             'change_join_table_statement_value', $data
3685             );
3686              
3687             unless ( defined $success->[0] ) { # make new statement
3688              
3689             $self->add_join_table_statement( $data );
3690             }
3691              
3692             return [ 1 ];
3693             }
3694              
3695             sub add_join_table_statement {
3696             my $self = shift;
3697             my $data = shift;
3698              
3699             my $new_statement = join_table_statement->new(
3700             $self, $data->{ keyword }, $data->{ new_value },
3701             );
3702              
3703             my $blocks = $self->{ __BODY__ };
3704             push @{ $blocks }, $new_statement;
3705             }
3706              
3707             sub remove_join_table_statement {
3708             my $self = shift;
3709             shift;
3710             my $data = shift;
3711              
3712             return unless ( $self->get_ident eq $data->{ident} );
3713              
3714             my $doomed_child = -1;
3715             my $count = 0;
3716              
3717             my $blocks = $self->{__BODY__};
3718              
3719             BLOCK:
3720             foreach my $block ( @{ $blocks } ) {
3721             next BLOCK unless $block->{__KEYWORD__} eq $data->{keyword};
3722              
3723             $doomed_child = $count;
3724             last BLOCK;
3725             }
3726             continue {
3727             $count++;
3728             }
3729              
3730             if ( $doomed_child >= 0 ) {
3731             splice @{ $blocks }, $doomed_child, 1;
3732             }
3733             # else, nothing to see here, move along quietly
3734              
3735             return [ 1 ];
3736             }
3737              
3738             sub walk_postorder {
3739             my $self = shift;
3740             my $action = shift;
3741             my $data = shift;
3742             my $parent = shift;
3743              
3744             my $output = [];
3745              
3746             foreach my $field_stmnt ( @{ $self->{__BODY__} } ) {
3747             my $child_output = $field_stmnt->walk_postorder(
3748             $action, $data, $self
3749             );
3750              
3751             push @{ $output }, @{ $child_output } if $child_output;
3752             }
3753              
3754             if ( $self->can( $action ) ) {
3755             $output = $self->$action( $output, $data, $parent );
3756             }
3757              
3758             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
3759             }
3760              
3761             sub build_lookup_hash {
3762             my $self = shift;
3763             my $child_output = shift;
3764             my $data = shift;
3765              
3766             my %child_hash;
3767              
3768             while ( my $output_type = shift @{ $child_output } ) {
3769             my $hash = shift @{ $child_output };
3770              
3771             if ( $output_type ne 'data' ) {
3772             if ( defined $child_hash{ $output_type } ) {
3773             die "join_table $self->{__NAME__} has multiple "
3774             . "$output_type statement.\n";
3775             }
3776             $child_hash{ $output_type } = $hash;
3777             }
3778             }
3779              
3780             if ( not defined $child_hash{ joins } ) {
3781             die "join_table $self->{__NAME__} has no joins statement.\n";
3782             }
3783              
3784             my ( $table1, $table2 ) = %{ $child_hash{ joins } };
3785              
3786             my ( $name1, $name2 );
3787             if ( defined $child_hash{ names } ) {
3788             ( $name1, $name2 ) = %{ $child_hash{ names } };
3789             }
3790             else {
3791             ( $name1, $name2 ) = ( "${table1}s", "${table2}s" );
3792             }
3793              
3794             return [
3795             {
3796             '__TYPE__' => 'join_tables',
3797             '__DATA__' => [
3798             $table1 => {
3799             joins => { $table2 => $self->{__NAME__} },
3800             name => $name2,
3801             },
3802             $table2 => {
3803             joins => { $table1 => $self->{__NAME__} },
3804             name => $name1,
3805             },
3806             __IDENT__ => $self->{ __IDENT__ },
3807             ],
3808             }
3809             ];
3810             }
3811              
3812             sub get_ident {
3813             my $self = shift;
3814             return $self->{__IDENT__};
3815             }
3816              
3817             sub show_idents {
3818             my $self = shift;
3819             my $child_output = shift;
3820              
3821             push @{ $child_output },
3822             [ 'join_table', $self->{ __NAME__ }, $self->{ __IDENT__ } ];
3823              
3824             return $child_output;
3825             }
3826              
3827             sub app_block_hashes {
3828             my $self = shift;
3829             my $child_output = shift;
3830              
3831             my $body = {
3832             statements => {},
3833             };
3834              
3835             foreach my $child_item ( @{ $child_output } ) {
3836             $body->{ statements }{ $child_item->{ keyword } } =
3837             $child_item->{ value };
3838             }
3839              
3840             return [ {
3841             ident => $self->get_ident,
3842             type => 'join_table',
3843             body => $body,
3844             name => $self->{__NAME__},
3845             } ];
3846             }
3847              
3848             package # join_table_statement
3849             join_table_statement;
3850             use strict; use warnings;
3851              
3852             use base 'application_ancestor';
3853              
3854             sub new {
3855             my $class = shift;
3856             my $parent = shift;
3857             my $keyword = shift;
3858             my $values = shift;
3859              
3860             my $self = {
3861             __PARENT__ => $parent,
3862             __KEYWORD__ => $keyword,
3863             __DEF__ => arg_list->new( $values ),
3864             };
3865              
3866             return bless $self, $class;
3867             }
3868              
3869             sub change_join_table_statement_value {
3870             my $self = shift;
3871             shift;
3872             my $data = shift;
3873              
3874             return unless ( $self->{__KEYWORD__} eq $data->{keyword} );
3875              
3876             $self->{__DEF__}->set_args_from(
3877             $data->{new_value},
3878             $data->{pair_required},
3879             );
3880              
3881             return [ 1 ];
3882             }
3883              
3884             sub get_join_table_name {
3885             my $self = shift;
3886              
3887             return $self->{ __PARENT__ }{ __NAME__ };
3888             }
3889              
3890             sub walk_postorder {
3891             my $self = shift;
3892             my $action = shift;
3893             my $data = shift;
3894             my $parent = shift;
3895              
3896             if ( $self->can( $action ) ) {
3897             return $self->$action( undef, $data, $parent );
3898             }
3899             else {
3900             return;
3901             }
3902             }
3903              
3904             sub build_lookup_hash {
3905             my $self = shift;
3906             my $child_output = shift;
3907             my $data = shift;
3908              
3909             return [ $self->{__KEYWORD__} => $self->{__DEF__}->get_first_arg() ];
3910             }
3911              
3912             sub app_block_hashes {
3913             my $self = shift;
3914             my $child_output = shift;
3915              
3916             return [ { keyword => $self->{__KEYWORD__}, value => $self->{__DEF__} } ];
3917             }
3918              
3919             package # controller_block
3920             controller_block;
3921             use strict; use warnings;
3922              
3923             use base 'application_ancestor';
3924              
3925             sub new_block {
3926             my $class = shift;
3927             my $parent = shift;
3928             my $data = shift;
3929              
3930             my $self = {
3931             __IDENT__ => Bigtop::Parser->get_ident(),
3932             __NAME__ => $data->{name},
3933             __TYPE__ => $data->{subtype},
3934             __BODY__ => []
3935             };
3936              
3937             $self->{__PARENT__} = $parent;
3938              
3939             bless $self, $class;
3940              
3941             # if we were given a table name, use it and do other nice things
3942             if ( $data->{ table } ) {
3943             $self->add_controller_statement(
3944             { keyword => 'controls_table',
3945             new_value => $data->{ table },
3946             }
3947             );
3948             $self->add_controller_statement(
3949             { keyword => 'rel_location',
3950             new_value => $data->{ rel_loc } || $data->{ table },
3951             }
3952             );
3953             $self->add_controller_statement(
3954             { keyword => 'text_description',
3955             new_value => $data->{ text_description } || $data->{ table },
3956             }
3957             );
3958             $self->add_controller_statement(
3959             { keyword => 'page_link_label',
3960             new_value => $data->{ page_link_label } || $data->{ name },
3961             }
3962             );
3963             }
3964              
3965             # now add some clever defaults if we're a CRUD or AutoCRUD
3966             if ( defined $data->{ subtype }
3967             and
3968             $data->{ subtype } =~ /CRUD/
3969             ) {
3970             my $table_name = $data->{ table } || lc $data->{name};
3971              
3972             # make the do_main method
3973             my $cols = $data->{ on_main_listing } || 'ident, description';
3974             $cols =~ s/, /][/g;
3975              
3976             my $main_arr = $self->add_subblock(
3977             undef,
3978             {
3979             parent => {
3980             type => 'controller',
3981             ident => $self->get_ident,
3982             },
3983             new_child => {
3984             type => 'method',
3985             sub_type => 'main_listing',
3986             name => 'do_main',
3987             },
3988             }
3989             );
3990             my $do_main = $main_arr->[0];
3991             my %values = (
3992             cols => $cols,
3993             header_options => 'Add',
3994             row_options => 'Edit][Delete',
3995             title => $data->{ page_link_label } || $self->{__NAME__},
3996             );
3997              
3998             foreach my $statement qw( cols header_options row_options title ) {
3999             $do_main->add_method_statement( {
4000             keyword => $statement,
4001             new_value => $values{ $statement },
4002             } );
4003             }
4004              
4005             # make the form method
4006             my $form_method_name;
4007             if ( $data->{ subtype } eq 'AutoCRUD' ) {
4008             $form_method_name = 'form';
4009             }
4010             else {
4011             $form_method_name = 'my_crud_form';
4012             }
4013             my $form_arr = $self->add_subblock(
4014             undef,
4015             {
4016             parent => {
4017             type => 'controller',
4018             ident => $self->get_ident,
4019             },
4020             new_child => {
4021             type => 'method',
4022             sub_type => $data->{ subtype } . '_form',
4023             name => $form_method_name,
4024             },
4025             }
4026             );
4027             my $form_method = $form_arr->[0];
4028              
4029             my $all_fields_but = $data->{ all_fields_but }
4030             || 'id, created, modified';
4031             $all_fields_but =~ s/, /][/g;
4032              
4033             $form_method->add_method_statement( {
4034             keyword => 'all_fields_but',
4035             new_value => $all_fields_but,
4036             } );
4037              
4038             $form_method->add_method_statement( {
4039             keyword => 'extra_keys',
4040             new_value => {
4041             keys => 'legend',
4042             values => q{$self->path_info =~ /edit/i ? q!Edit! : q!Add!}
4043             }
4044             } );
4045             }
4046             # base controllers get nav link methods by default
4047             elsif ( defined $data->{ subtype }
4048             and
4049             $data->{ subtype } eq 'base_controller'
4050             ) {
4051             # first a do_main with nav links for default main page
4052             my $main_arr = $self->add_subblock(
4053             undef,
4054             {
4055             parent => {
4056             type => 'controller',
4057             ident => $self->get_ident,
4058             },
4059             new_child => {
4060             type => 'method',
4061             sub_type => 'base_links',
4062             name => 'do_main',
4063             },
4064             }
4065             );
4066              
4067             my $do_main = $main_arr->[0];
4068              
4069             # then a site_links method for other controllers and their templates
4070             $self->add_subblock(
4071             undef,
4072             {
4073             parent => {
4074             type => 'controller',
4075             ident => $self->get_ident,
4076             },
4077             new_child => {
4078             type => 'method',
4079             sub_type => 'links',
4080             name => 'site_links',
4081             },
4082             }
4083             );
4084             }
4085              
4086             return $self;
4087             }
4088              
4089             sub add_subblock {
4090             my $self = shift;
4091             shift;
4092             my $params = shift;
4093              
4094             return unless ( $params->{parent}{type} eq 'controller' );
4095             return unless ( $params->{parent}{ident} eq $self->get_ident );
4096              
4097             if ( $params->{new_child}{type} eq 'method' ) {
4098             my $new_method = controller_method->new(
4099             $self, $params
4100             );
4101              
4102             push @{ $self->{__BODY__} }, $new_method;
4103              
4104             return [ $new_method ];
4105             }
4106             elsif ( $params->{new_child}{type} eq 'config' ) {
4107             my $new_config = controller_config_block->new( $self, $params );
4108              
4109             push @{ $self->{__BODY__} }, $new_config;
4110              
4111             return [ $new_config ];
4112             }
4113             }
4114              
4115             sub remove_block {
4116             my $self = shift;
4117             shift;
4118             my $data = shift;
4119             my $doomed_ident = $data->{ ident };
4120              
4121             my $doomed_index = -1;
4122             my $count = 0;
4123              
4124             my $children = $self->{__BODY__};
4125              
4126             CHILD:
4127             foreach my $child ( @{ $children } ) {
4128             next CHILD unless $child->can( 'get_ident' );
4129              
4130             if ( $child->get_ident eq $doomed_ident ) {
4131             $doomed_index = $count;
4132             }
4133             }
4134             continue {
4135             $count++;
4136             }
4137              
4138             return if ( $doomed_index == -1 );
4139              
4140             splice @{ $children }, $doomed_index, 1;
4141              
4142             return [ 1 ];
4143             }
4144              
4145             sub get_ident {
4146             my $self = shift;
4147             return $self->{__IDENT__};
4148             }
4149              
4150             sub get_name {
4151             my $self = shift;
4152             return $self->{__NAME__};
4153             }
4154              
4155             sub set_name {
4156             my $self = shift;
4157             $self->{__NAME__} = shift;
4158             }
4159              
4160             sub get_controller_type {
4161             my $self = shift;
4162              
4163             return $self->{__TYPE__} || 'stub';
4164             }
4165              
4166             sub set_type {
4167             my $self = shift;
4168             $self->{__TYPE__} = shift;
4169             }
4170              
4171             sub is_base_controller {
4172             my $self = shift;
4173              
4174             return (
4175             defined $self->{__TYPE__}
4176             and
4177             $self->{__TYPE__} eq 'base_controller'
4178             );
4179             }
4180              
4181             sub output_location {
4182             my $self = shift;
4183             my $child_output = shift;
4184              
4185             return unless $self->is_base_controller;
4186              
4187             return $child_output;
4188             }
4189              
4190             sub get_controlled_table {
4191             my $self = shift;
4192             }
4193              
4194             sub change_name_controller {
4195             my $self = shift;
4196             shift;
4197             my $data = shift;
4198              
4199             return unless $self->get_ident eq $data->{ident};
4200              
4201             $self->set_name( $data->{new_value} );
4202              
4203             return [ 1 ];
4204             }
4205              
4206             sub change_type {
4207             my $self = shift;
4208             shift;
4209             my $data = shift;
4210              
4211             return unless ( $self->get_ident eq $data->{ident} );
4212              
4213             $self->set_type( $data->{new_type} );
4214              
4215             return [ 1 ];
4216             }
4217              
4218             sub app_block_hashes {
4219             my $self = shift;
4220             my $child_output = shift;
4221              
4222             my $body = {
4223             statements => {},
4224             blocks => [],
4225             };
4226              
4227             foreach my $child_item ( @{ $child_output } ) {
4228             if ( $child_item->{ type } eq 'statement' ) {
4229             $body->{ statements }{ $child_item->{ keyword } } =
4230             $child_item->{ value };
4231             }
4232             else {
4233             push @{ $body->{ blocks } }, $child_item;
4234             }
4235             }
4236              
4237             my $controller_type = $self->get_controller_type || 'stub';
4238              
4239             return [ {
4240             ident => $self->get_ident,
4241             type => 'controller',
4242             body => $body,
4243             name => $self->get_name,
4244             controller_type => $controller_type,
4245             } ];
4246             }
4247              
4248             sub change_controller_statement {
4249             my $self = shift;
4250             shift;
4251             my $data = shift;
4252              
4253             return unless ( $self->get_ident eq $data->{ident} );
4254              
4255             my $success = $self->walk_postorder(
4256             'change_controller_keyword_value', $data
4257             );
4258              
4259             unless ( defined $success->[0] ) { # make new statement
4260             $self->add_controller_statement( $data );
4261             }
4262              
4263             return [ 1 ];
4264             }
4265              
4266             sub add_controller_statement {
4267             my $self = shift;
4268             my $data = shift;
4269              
4270             my $new_statement = controller_statement->new(
4271             $self, $data->{ keyword }, $data->{ new_value },
4272             );
4273              
4274             my $blocks = $self->{ __BODY__ };
4275             push @{ $blocks }, $new_statement;
4276             }
4277              
4278             sub remove_controller_statement {
4279             my $self = shift;
4280             shift;
4281             my $data = shift;
4282              
4283             return unless ( $self->get_ident eq $data->{ident} );
4284              
4285             my $doomed_child = -1;
4286             my $count = 0;
4287              
4288             my $blocks = $self->{__BODY__};
4289              
4290             BLOCK:
4291             foreach my $block ( @{ $blocks } ) {
4292             next BLOCK unless defined $block->{__KEYWORD__}; # skip methods
4293             next BLOCK unless $block->{__KEYWORD__} eq $data->{keyword};
4294              
4295             $doomed_child = $count;
4296             last BLOCK;
4297             }
4298             continue {
4299             $count++;
4300             }
4301              
4302             if ( $doomed_child >= 0 ) {
4303             # This probably leaks memory because children have parent pointers.
4304             # But the parent is me and I'm the app_body, so maybe not.
4305             splice @{ $blocks }, $doomed_child, 1;
4306             }
4307             # else, nothing to see here, move along quietly
4308              
4309             return [ 1 ];
4310             }
4311              
4312             sub get_controller_statement {
4313             my $self = shift;
4314             my $keyword = shift;
4315              
4316             my $blocks = $self->{__BODY__};
4317              
4318             BLOCK:
4319             foreach my $block ( @{ $blocks } ) {
4320             next BLOCK unless defined $block->{ __KEYWORD__ }; # no methods
4321             next BLOCK unless $block->{ __KEYWORD__ } eq $keyword;
4322              
4323             return $block;
4324             }
4325              
4326             return;
4327             }
4328              
4329             sub field_name_changed {
4330             my $self = shift;
4331             my $child_output = shift;
4332             my $data = shift;
4333              
4334             return unless defined $child_output->[0];
4335              
4336             return $self->walk_postorder( 'update_field_name', $data );
4337             }
4338              
4339             sub field_removed {
4340             my $self = shift;
4341             my $child_output = shift;
4342             my $data = shift;
4343              
4344             return unless defined $child_output->[0];
4345              
4346             return $self->walk_postorder( 'remove_field', $data );
4347             }
4348              
4349             sub show_idents {
4350             my $self = shift;
4351             my $child_output = shift;
4352              
4353             push @{ $child_output },
4354             [ 'controller', $self->{ __NAME__ }, $self->{ __IDENT__ } ];
4355              
4356             return $child_output;
4357             }
4358              
4359             sub get_controller_configs {
4360             my $self = shift;
4361             my $child_output = shift;
4362              
4363             my $name = $self->get_name();
4364              
4365             my %my_children;
4366             foreach my $child ( @{ $child_output } ) {
4367             $my_children{ $child->{ type } } = $child->{ statements };
4368             }
4369              
4370             return [ { controller => $name, configs => \%my_children } ];
4371             }
4372              
4373             sub walk_postorder {
4374             my $self = shift;
4375             my $action = shift;
4376             my $data = shift;
4377             my $parent = shift;
4378              
4379             my $output = [];
4380              
4381             foreach my $controller_stmnt ( @{ $self->{__BODY__} } ) {
4382             my $child_output = $controller_stmnt->walk_postorder(
4383             $action, $data, $self
4384             );
4385             push @{ $output }, @{ $child_output } if $child_output;
4386             }
4387              
4388             if ( $self->can( $action ) ) {
4389             $output = $self->$action( $output, $data, $parent );
4390             }
4391              
4392             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
4393             }
4394              
4395             sub build_lookup_hash {
4396             my $self = shift;
4397             my $child_output = shift;
4398             my $data = shift;
4399              
4400             my %output = ( type => $self->get_controller_type );
4401              
4402             foreach my $element ( @{ $child_output } ) {
4403             my $output_type = $element->{__TYPE__};
4404              
4405             my $name = $element->{__DATA__}[0];
4406              
4407             $output{ $output_type }{ $name } = $element->{__DATA__}[1];
4408             }
4409              
4410             return [
4411             {
4412             '__TYPE__' => 'controllers',
4413             '__DATA__' => [
4414             $self->{__NAME__} => {
4415             __IDENT__ => $self->{ __IDENT__ },
4416             %output
4417             }
4418             ],
4419             }
4420             ];
4421             }
4422              
4423             sub use_date_plugin {
4424             my $self = shift;
4425             shift;
4426             my $data = shift;
4427              
4428             my $it_is_I = $self->walk_postorder(
4429             'do_I_control', $data->{ table }
4430             )->[0];
4431              
4432             my @retval;
4433              
4434             if ( $it_is_I ) {
4435             # first, update my uses
4436             my $current_uses = $self->get_controller_statement( 'uses' );
4437              
4438             if ( not defined $current_uses ) {
4439             $self->add_controller_statement(
4440             {
4441             keyword => 'uses',
4442             new_value => 'Gantry::Plugins::Calendar',
4443             }
4444             );
4445             push @retval, $self->get_ident . '::uses',
4446             $self->get_controller_statement( 'uses' )->{ __ARGS__ };
4447             }
4448             else { # see if its already there
4449             my %current_modules = map { $_ => 1 }
4450             @{ $current_uses->{ __ARGS__ } };
4451              
4452             unless ( defined $current_modules{ 'Gantry::Plugins::Calendar' } )
4453             {
4454             push @{ $current_uses->{ __ARGS__ } },
4455             'Gantry::Plugins::Calendar';
4456             }
4457             push @retval,
4458             $self->get_ident . '::uses',
4459             $current_uses->{ __ARGS__ };
4460             }
4461              
4462             # then, tell update my form
4463             my $result = $self->walk_postorder(
4464             'add_date_popups', $data->{ table }
4465             );
4466              
4467             push @retval, @{ $result };
4468              
4469             return \@retval;
4470             }
4471              
4472             return;
4473             }
4474              
4475             package # controller_method
4476             controller_method;
4477             use strict; use warnings;
4478              
4479             use base 'application_ancestor';
4480              
4481             sub new {
4482             my $class = shift;
4483             my $parent = shift;
4484             my $params = shift;
4485              
4486             my $type = $params->{new_child}{sub_type} || 'stub';
4487              
4488             my $self = {
4489             __IDENT__ => Bigtop::Parser->get_ident(),
4490             __NAME__ => $params->{new_child}{name},
4491             __BODY__ => method_body->new(),
4492             __TYPE__ => $type,
4493             __PARENT__ => $parent,
4494             };
4495              
4496             $self->{__BODY__}{__PARENT__} = $self;
4497              
4498             return bless $self, $class;
4499             }
4500              
4501             sub get_ident {
4502             my $self = shift;
4503              
4504             return $self->{__IDENT__};
4505             }
4506              
4507             sub get_name {
4508             my $self = shift;
4509              
4510             return $self->{__NAME__};
4511             }
4512              
4513             sub set_name {
4514             my $self = shift;
4515             $self->{__NAME__} = shift;
4516             }
4517              
4518             sub set_type {
4519             my $self = shift;
4520             $self->{__TYPE__} = shift;
4521             }
4522              
4523             sub get_controller_ident {
4524             my $self = shift;
4525              
4526             return $self->{__PARENT__}{__PARENT__}->get_ident();
4527             }
4528              
4529             sub get_controller_name {
4530             my $self = shift;
4531              
4532             return $self->{__PARENT__}{__PARENT__}->get_name();
4533             }
4534              
4535             sub change_name_method {
4536             my $self = shift;
4537             shift;
4538             my $data = shift;
4539              
4540             return unless $self->get_ident eq $data->{ident};
4541              
4542             $self->set_name( $data->{ new_value } );
4543              
4544             return;
4545             }
4546              
4547             sub app_block_hashes {
4548             my $self = shift;
4549             my $child_output = shift;
4550              
4551             my %statements;
4552              
4553             foreach my $child_item ( @{ $child_output } ) {
4554             $statements{ $child_item->{ keyword } } = $child_item->{ values };
4555             }
4556              
4557             return [ {
4558             ident => $self->get_ident,
4559             type => 'method',
4560             name => $self->get_name,
4561             method_type => $self->{__TYPE__},
4562             statements => \%statements,
4563             } ];
4564             }
4565              
4566             sub change_method_statement {
4567             my $self = shift;
4568             shift;
4569             my $data = shift;
4570              
4571             return unless ( $data->{ident} eq $self->get_ident() );
4572              
4573             my $old_value = $self->walk_postorder( 'get_method_keyword_value', $data );
4574              
4575             my $success = $self->walk_postorder(
4576             'change_method_keyword_value', $data
4577             );
4578              
4579             unless ( defined $success->[0] ) {
4580             $self->add_method_statement( $data );
4581             }
4582              
4583             if ( $data->{ keyword } eq 'paged_conf' ) {
4584             my $current_value = $data->{ app }->get_config_statement(
4585             'base', $data->{ new_value }
4586             );
4587              
4588             unless ( defined $current_value->[0] and $current_value->[0] > 0 ) {
4589             my $config_ident = $data->{ app }->get_config_ident( 'base' );
4590              
4591             $data->{ app }->set_config_statement(
4592             $config_ident, $data->{ new_value }, 20
4593             );
4594             return [ [ $config_ident . '::' . $data->{ new_value }, 20 ] ];
4595             }
4596             }
4597              
4598             return [ 1 ];
4599             }
4600              
4601             sub add_method_statement {
4602             my $self = shift;
4603             my $data = shift;
4604              
4605             my $new_statement = method_statement->new(
4606             $self->{__BODY__},
4607             $data->{keyword},
4608             $data->{new_value},
4609             $data->{pair_required},
4610             );
4611              
4612             my $blocks = $self->{ __BODY__ }{ 'method_statement(s?)' };
4613             push @{ $blocks }, $new_statement;
4614             }
4615              
4616             sub remove_method_statement {
4617             my $self = shift;
4618             shift;
4619             my $data = shift;
4620              
4621             return unless ( $data->{ident} eq $self->get_ident() );
4622              
4623             my $doomed_child = -1;
4624             my $count = 0;
4625              
4626             my $statements = $self->{ __BODY__ }{'method_statement(s?)'};
4627              
4628             STATEMENT:
4629             foreach my $statement ( @{ $statements } ) {
4630             next STATEMENT unless $statement->{__KEYWORD__} eq $data->{keyword};
4631              
4632             $doomed_child = $count;
4633             last STATEMENT;
4634             }
4635             continue {
4636             $count++;
4637             }
4638              
4639             if ( $doomed_child >= 0 ) {
4640             # This probably leaks memory because children have parent pointers.
4641             # But the parent is me and I'm the app_body, so maybe not.
4642             splice @{ $statements }, $doomed_child, 1;
4643             }
4644             # else, nothing to see here, move along quietly
4645              
4646             return [ 1 ];
4647             }
4648              
4649             sub get_method_statement {
4650             my $self = shift;
4651             my $keyword = shift;
4652              
4653             my $statements = $self->{ __BODY__ }{'method_statement(s?)'};
4654              
4655             STATEMENT:
4656             foreach my $statement ( @{ $statements} ) {
4657             next STATEMENT unless $statement->{__KEYWORD__} eq $keyword;
4658             return $statement;
4659             }
4660             return;
4661             }
4662              
4663             sub change_type {
4664             my $self = shift;
4665             shift;
4666             my $data = shift;
4667              
4668             return unless ( $self->get_ident eq $data->{ident} );
4669              
4670             $self->set_type( $data->{new_type} );
4671              
4672             return [ 1 ];
4673             }
4674              
4675             sub add_date_popups {
4676             my $self = shift;
4677             shift;
4678             my $table = shift;
4679              
4680             return unless $self->{ __TYPE__ } =~ /form/;
4681              
4682             # First, make sure the form is named for the table (or has a name)
4683             my $form_statement = $self->get_method_statement( 'form_name' );
4684             my $form_name = $table;
4685              
4686             if ( defined $form_statement ) {
4687             $form_name = $form_statement->{ __ARGS__ }->get_first_arg();
4688             }
4689             else { # create a form_name statement
4690             $self->add_method_statement(
4691             {
4692             keyword => 'form_name',
4693             new_value => $table,
4694             }
4695             );
4696             }
4697              
4698             # Second, make sure that name is in javascript code for calendars.
4699             my $javascript_code = qq{\$self->calendar_month_js( '$table' )},
4700             my $keys_statement = $self->get_method_statement( 'extra_keys' );
4701             my $extra_keys;
4702              
4703             if ( defined $keys_statement ) {
4704             push @{ $keys_statement->{ __ARGS__ } },
4705             { javascript => $javascript_code };
4706              
4707             $extra_keys = $keys_statement->{ __ARGS__ };
4708             }
4709             else {
4710             $self->add_method_statement(
4711             {
4712             keyword => 'extra_keys',
4713             new_value => {
4714             'keys' => 'javascript',
4715             'values' => $javascript_code,
4716             },
4717             }
4718             );
4719              
4720             $extra_keys = $self->get_method_statement( 'extra_keys' )->{__ARGS__};
4721             }
4722              
4723             my $ident = $self->get_ident;
4724             return [
4725             $ident . '::form_name' => $table,
4726             $ident . '::extra_keys' => $extra_keys,
4727             ];
4728             }
4729              
4730             sub update_field_name {
4731             my $self = shift;
4732             my $child_output = shift;
4733             my $data = shift;
4734              
4735             my $count = 0;
4736             # remember that foreach aliases, this loop alters child output
4737             foreach my $key_or_val ( @{ $child_output } ) {
4738             if ( $count % 2 == 0 ) {
4739             $key_or_val = $self->{__IDENT__} . '::' . $key_or_val;
4740             }
4741             $count++;
4742             }
4743              
4744             return $child_output;
4745             }
4746              
4747             sub remove_field {
4748             my $self = shift;
4749             my $child_output = shift;
4750             my $data = shift;
4751              
4752             my $count = 0;
4753             # remember that foreach aliases, this loop alters child output
4754             foreach my $key_or_val ( @{ $child_output } ) {
4755             if ( $count % 2 == 0 ) {
4756             $key_or_val = $self->{__IDENT__} . '::' . $key_or_val;
4757             }
4758             $count++;
4759             }
4760              
4761             return $child_output;
4762             }
4763              
4764             sub show_idents {
4765             my $self = shift;
4766             my $child_output = shift;
4767              
4768             push @{ $child_output }, [
4769             'method',
4770             $self->{ __NAME__ },
4771             $self->{ __IDENT__ },
4772             'controller: ' . $self->get_controller_ident,
4773             ];
4774              
4775             return $child_output;
4776             }
4777              
4778             sub walk_postorder {
4779             my $self = shift;
4780             my $action = shift;
4781             my $data = shift;
4782             my $parent = shift;
4783              
4784             my $output = $self->{__BODY__}->walk_postorder( $action, $data, $self );
4785              
4786             if ( $self->can( $action ) ) {
4787             return $self->$action( $output, $data, $parent );
4788             }
4789              
4790             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
4791             }
4792              
4793             sub build_lookup_hash {
4794             my $self = shift;
4795             my $child_output = shift;
4796             my $data = shift;
4797              
4798             my $statements = {};
4799              
4800             if ( $child_output ) {
4801             $statements = { @{ $child_output } }
4802             }
4803              
4804             return [
4805             {
4806             '__TYPE__' => 'methods',
4807             '__DATA__' => [
4808             $self->{__NAME__} => {
4809             type => $self->{__TYPE__},
4810             statements => $statements,
4811             __IDENT__ => $self->{__IDENT__},
4812             },
4813             ],
4814             }
4815             ];
4816             }
4817              
4818             package # method_body
4819             method_body;
4820             use strict; use warnings;
4821              
4822             use base 'application_ancestor';
4823              
4824             sub new {
4825             my $class = shift;
4826              
4827             my $self = {
4828             __RULE__ => 'method_body',
4829             'method_statement(s?)' => [],
4830             };
4831              
4832             return bless $self, $class;
4833             }
4834              
4835             sub get_method_name {
4836             my $self = shift;
4837              
4838             return $self->{__PARENT__}{__NAME__};
4839             }
4840              
4841             sub get_controller_name {
4842             my $self = shift;
4843              
4844             return $self->{__PARENT__}{__PARENT__}->get_name();
4845             }
4846              
4847             sub get_table_name {
4848             my $self = shift;
4849             my $lookup = shift;
4850              
4851             my $controller = $self->get_controller_name();
4852             return $lookup->{controllers}{$controller}{statements}{controls_table}[0];
4853             }
4854              
4855             sub walk_postorder {
4856             my $self = shift;
4857             my $action = shift;
4858             my $data = shift;
4859             my $parent = shift;
4860              
4861             my $output = [];
4862              
4863             foreach my $child ( @{ $self->{'method_statement(s?)'} } ) {
4864             my $child_output = $child->walk_postorder( $action, $data, $self );
4865             push @{ $output }, @{ $child_output } if $child_output;
4866             }
4867              
4868             if ( $self->can( $action ) ) {
4869             $output = $self->$action( $output, $data, $parent );
4870             }
4871              
4872             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
4873             }
4874              
4875             package # method_statement
4876             method_statement;
4877             use strict; use warnings;
4878              
4879             use base 'application_ancestor';
4880              
4881             sub new {
4882             my $class = shift;
4883             my $parent = shift;
4884             my $keyword = shift;
4885             my $new_value = shift;
4886              
4887             my $self = {
4888             __PARENT__ => $parent,
4889             __KEYWORD__ => $keyword,
4890             __ARGS__ => arg_list->new( $new_value ),
4891             };
4892              
4893             return bless $self, $class;
4894             }
4895              
4896             sub change_method_keyword_value {
4897             my $self = shift;
4898             shift;
4899             my $data = shift;
4900              
4901             return unless ( $self->{__KEYWORD__} eq $data->{keyword} );
4902              
4903             $self->{__ARGS__}->set_args_from(
4904             $data->{new_value},
4905             $data->{pair_required},
4906             );
4907              
4908             return [ 1 ];
4909             }
4910              
4911             sub get_method_keyword_value {
4912             my $self = shift;
4913             shift;
4914             my $data = shift;
4915              
4916             return unless ( $self->{__KEYWORD__} eq $data->{keyword} );
4917              
4918             return $self->{__ARGS__};
4919             }
4920              
4921             sub app_block_hashes {
4922             my $self = shift;
4923              
4924             return [ {
4925             keyword => $self->{__KEYWORD__},
4926             values => $self->{__ARGS__},
4927             } ];
4928             }
4929              
4930             sub update_field_name {
4931             my $self = shift;
4932             shift;
4933             my $data = shift;
4934              
4935             unless ( $self->{ __KEYWORD__ } eq 'cols'
4936             or
4937             $self->{ __KEYWORD__ } eq 'all_fields_but'
4938             or
4939             $self->{ __KEYWORD__ } eq 'fields' )
4940             {
4941             return;
4942             }
4943              
4944             my $we_did_something = 0;
4945             foreach my $arg ( @{ $self->{ __ARGS__ } } ) {
4946             if ( $arg eq $data->{ old_field_name } ) {
4947             $arg = $data->{ new_field_name };
4948             $we_did_something++;
4949             }
4950             }
4951              
4952             if ( $we_did_something ) {
4953             return [ $self->{ __KEYWORD__ }, $self->{ __ARGS__ } ];
4954             }
4955             else {
4956             return;
4957             }
4958             }
4959              
4960             sub remove_field {
4961             my $self = shift;
4962             shift;
4963             my $data = shift;
4964              
4965             unless ( $self->{ __KEYWORD__ } eq 'cols'
4966             or
4967             $self->{ __KEYWORD__ } eq 'all_fields_but'
4968             or
4969             $self->{ __KEYWORD__ } eq 'fields' )
4970             {
4971             return;
4972             }
4973              
4974             # we need to remove the arg if it matches the name of the deceased field
4975             my @new_args;
4976              
4977             # first, build a list of remaining args
4978             my $someone_died = 0;
4979             ARG:
4980             foreach my $arg ( @{ $self->{__ARGS__} } ) {
4981             if ( $arg eq $data->{ dead_field_name } ) { $someone_died++; }
4982             else { push @new_args, $arg; }
4983             }
4984              
4985             return unless $someone_died;
4986              
4987             # second, install them in the object
4988             $self->{__ARGS__}->set_args_from( \@new_args );
4989              
4990             push @new_args, '';
4991              
4992             # third, return them as a full list for the statement
4993             return [ $self->{ __KEYWORD__ }, \@new_args ];
4994             }
4995              
4996             sub walk_postorder {
4997             my $self = shift;
4998             my $action = shift;
4999             my $data = shift;
5000             my $parent = shift;
5001              
5002             if ( $self->can( $action ) ) {
5003             return $self->$action( undef, $data, $parent );
5004             }
5005             else {
5006             return;
5007             }
5008             }
5009              
5010             sub build_lookup_hash {
5011             my $self = shift;
5012             my $child_output = shift;
5013             my $data = shift;
5014              
5015             return [ $self->{__KEYWORD__} => $self->{__ARGS__} ];
5016             }
5017              
5018             package # controller_literal_block
5019             controller_literal_block;
5020             use strict; use warnings;
5021              
5022             use base 'application_ancestor';
5023              
5024             sub get_backend {
5025             my $self = shift;
5026              
5027             return $self->{__BACKEND__};
5028             }
5029              
5030             sub walk_postorder {
5031             my $self = shift;
5032             my $action = shift;
5033             my $data = shift;
5034             my $parent = shift;
5035              
5036             if ( $self->can( $action ) ) {
5037             return $self->$action( undef, $data, $parent );
5038             }
5039             else {
5040             return;
5041             }
5042             }
5043              
5044             sub make_output {
5045             my $self = shift;
5046             my $backend = shift;
5047              
5048             if ( $backend eq $self->{__BACKEND__} ) {
5049             my $output = $self->{__BODY__};
5050              
5051             $output =~ s/\Z/\n/ if ( $output !~ /\s\Z/ );
5052              
5053             return [ $output ];
5054             }
5055             else {
5056             return;
5057             }
5058             }
5059              
5060             package # controller_statement
5061             controller_statement;
5062             use strict; use warnings;
5063              
5064             use base 'application_ancestor';
5065              
5066             sub new {
5067             my $class = shift;
5068             my $parent = shift;
5069             my $keyword = shift;
5070             my $values = shift;
5071              
5072             my $self = {
5073             __PARENT__ => $parent,
5074             __KEYWORD__ => $keyword,
5075             __ARGS__ => arg_list->new( $values ),
5076             };
5077              
5078             return bless $self, $class;
5079             }
5080              
5081             sub get_controller_ident {
5082             my $self = shift;
5083              
5084             return $self->{__PARENT__}{__IDENT__};
5085             }
5086              
5087             sub get_controller_name {
5088             my $self = shift;
5089              
5090             return $self->{__PARENT__}{__NAME__};
5091             }
5092              
5093             sub change_controller_keyword_value {
5094             my $self = shift;
5095             shift;
5096             my $data = shift;
5097              
5098             return unless ( $self->{__KEYWORD__} eq $data->{keyword} );
5099              
5100             $self->{__ARGS__}->set_args_from(
5101             $data->{new_value},
5102             $data->{pair_required},
5103             );
5104              
5105             return [ 1 ];
5106             }
5107              
5108             sub app_block_hashes {
5109             my $self = shift;
5110             my $child_output = shift;
5111              
5112             return [ {
5113             type => 'statement',
5114             keyword => $self->{__KEYWORD__},
5115             value => $self->{__ARGS__},
5116             } ];
5117             }
5118              
5119             sub table_name_changed {
5120             my $self = shift;
5121             shift;
5122             my $data = shift;
5123              
5124             return unless $self->{ __KEYWORD__ } eq 'controls_table';
5125              
5126             my $controlled_table = $self->{ __ARGS__ }->get_first_arg();
5127              
5128             if ( $controlled_table eq $data->{ old_name } ) {
5129             $self->{ __ARGS__ }->set_args_from( $data->{ new_name } );
5130              
5131             return [
5132             $self->get_controller_ident . '::controls_table',
5133             $data->{ new_name }
5134             ];
5135             }
5136              
5137             return;
5138             }
5139              
5140             sub field_name_changed {
5141             my $self = shift;
5142             shift;
5143             my $data = shift;
5144              
5145             return unless $self->{ __KEYWORD__ } eq 'controls_table';
5146             return unless $self->{ __ARGS__ }->get_first_arg()
5147             eq
5148             $data->{ table_name };
5149              
5150             # Leave this return value alone, an ancestor checks it to see if the
5151             # name change is for this controller or not.
5152             return [ 1 ];
5153             }
5154              
5155             # Yes, I know this is the same as the code above. They are in different
5156             # walk stacks.
5157              
5158             sub field_removed {
5159             my $self = shift;
5160             my $child_output = shift;
5161             my $data = shift;
5162              
5163             return unless $self->{ __KEYWORD__ } eq 'controls_table';
5164             return unless $self->{ __ARGS__ }->get_first_arg()
5165             eq
5166             $data->{ table_name };
5167              
5168             # Leave this return value alone, an ancestor checks it to see if the
5169             # name change is for this controller or not.
5170             return [ 1 ];
5171             }
5172              
5173             sub do_I_control {
5174             my $self = shift;
5175             shift;
5176             my $table = shift;
5177              
5178             return unless $self->{ __KEYWORD__ } eq 'controls_table';
5179             my $controlled_table = $self->{ __ARGS__ }->get_first_arg();
5180              
5181             if ( $controlled_table eq $table ) {
5182             return [ 1 ];
5183             }
5184             else {
5185             return;
5186             }
5187             }
5188              
5189             sub output_location {
5190             my $self = shift;
5191             my $child_output = shift;
5192              
5193             return unless $self->{__KEYWORD__} eq 'location';
5194              
5195             return $self->{__ARGS__};
5196             }
5197              
5198             sub walk_postorder {
5199             my $self = shift;
5200             my $action = shift;
5201             my $data = shift;
5202             my $parent = shift;
5203              
5204             if ( $self->can( $action ) ) {
5205             return $self->$action( undef, $data, $parent );
5206             }
5207             else {
5208             return;
5209             }
5210             }
5211              
5212             sub build_lookup_hash {
5213             my $self = shift;
5214             my $child_output = shift;
5215             my $data = shift;
5216              
5217             return [
5218             {
5219             '__TYPE__' => 'statements',
5220             '__DATA__' => [
5221             $self->{__KEYWORD__} => $self->{__ARGS__}
5222             ]
5223             }
5224             ];
5225             }
5226              
5227             package # app_config_block
5228             app_config_block;
5229             use strict; use warnings;
5230              
5231             use base 'application_ancestor';
5232              
5233             sub new {
5234             my $class = shift;
5235             my $params = shift;
5236              
5237             return bless {
5238             __IDENT__ => Bigtop::Parser->get_ident(),
5239             __PARENT__ => $params->{parent},
5240             __BODY__ => $params->{statements},
5241             __TYPE__ => $params->{type},
5242             }, $class;
5243             }
5244              
5245             sub new_block {
5246             my $class = shift;
5247             my $parent = shift;
5248             my $data = shift;
5249              
5250             return $class->new(
5251             {
5252             parent => $parent,
5253             statements => [],
5254             type => $data->{ name },
5255             }
5256             );
5257             }
5258              
5259             sub change_name_config {
5260             my $self = shift;
5261             shift;
5262             my $data = shift;
5263              
5264             return unless $self->{__IDENT__} eq $data->{ident};
5265              
5266             $self->set_name( $data->{ new_value } );
5267              
5268             return;
5269             }
5270              
5271             sub set_name {
5272             my $self = shift;
5273             $self->{__TYPE__} = shift;
5274             }
5275              
5276             sub add_config_statement {
5277             my $self = shift;
5278             shift;
5279             my $data = shift;
5280              
5281             return unless $data->{ ident } eq $self->{__IDENT__};
5282              
5283             my $new_statement = app_config_statement->new(
5284             $data->{ keyword },
5285             $data->{ value },
5286             $data->{ accessor },
5287             $self,
5288             );
5289              
5290             push @{ $self->{ __BODY__ } }, $new_statement;
5291              
5292             return [ 1 ];
5293             }
5294              
5295             sub remove_config_statement {
5296             my $self = shift;
5297             shift;
5298             my $data = shift;
5299             my $ident = $data->{ ident };
5300             my $keyword = $data->{ keyword };
5301              
5302             return unless $self->{__IDENT__} eq $ident;
5303              
5304             my $doomed_child = -1;
5305             my $count = 0;
5306              
5307             STATEMENT:
5308             foreach my $child ( @{ $self->{ __BODY__ } } ) {
5309             my $child_keyword = $child->get_keyword();
5310             if ( $keyword eq $child_keyword ) {
5311             $doomed_child = $count;
5312             last STATEMENT;
5313             }
5314             $count++;
5315             }
5316              
5317             if ( $doomed_child >= 0 ) {
5318             splice @{ $self->{ __BODY__ } }, $doomed_child, 1;
5319             }
5320              
5321             return [ 1 ];
5322             }
5323              
5324             sub get_app_configs {
5325             my $self = shift;
5326             my $child_output = shift;
5327              
5328             my $type = $self->{__TYPE__} || 'base';
5329              
5330             my %my_children;
5331             foreach my $child ( @{ $child_output } ) {
5332             $my_children{ $child->{ var } } = $child->{ val };
5333             }
5334              
5335             return [ { type => $type, statements => \%my_children } ];
5336             }
5337              
5338             sub get_app_config_types {
5339             my $self = shift;
5340              
5341             my $type = $self->{__TYPE__} || 'base';
5342              
5343             return [ $type ];
5344             }
5345              
5346             sub app_block_hashes {
5347             my $self = shift;
5348             my $child_output = shift;
5349              
5350             my @statements;
5351              
5352             foreach my $child_item ( @{ $child_output } ) {
5353             my $no_accessor = 0;
5354             my $value = $child_item->{ value };
5355             if ( ref( $value ) eq 'HASH' ) {
5356             ( $value, $no_accessor ) = %{ $value };
5357             }
5358              
5359             push @statements, {
5360             keyword => $child_item->{ keyword },
5361             value => $value,
5362             no_accessor => $no_accessor,
5363             };
5364             }
5365              
5366             return [ {
5367             ident => $self->{__IDENT__},
5368             type => 'config',
5369             name => $self->{__TYPE__} || 'base',
5370             statements => \@statements,
5371             } ];
5372             }
5373              
5374             sub get_ident {
5375             my $self = shift;
5376              
5377             return $self->{__IDENT__};
5378             }
5379              
5380             sub get_config_idents {
5381             my $self = shift;
5382             shift;
5383             my $block_name = shift;
5384              
5385             if ( ( not defined $self->{__TYPE__} and $block_name eq 'base' )
5386             or
5387             $self->{__TYPE__} eq $block_name
5388             ) {
5389             return [ $self->{__IDENT__} ];
5390             }
5391             else {
5392             return;
5393             }
5394             }
5395              
5396             sub show_idents {
5397             my $self = shift;
5398             my $child_output = shift;
5399              
5400             push @{ $child_output },
5401             [ 'config', $self->{ __NAME__ }, $self->{ __IDENT__ } ];
5402              
5403             return $child_output;
5404             }
5405              
5406             sub walk_postorder {
5407             my $self = shift;
5408             my $action = shift;
5409             my $data = shift;
5410             my $parent = shift;
5411              
5412             my $output = [];
5413              
5414             foreach my $child ( @{ $self->{ __BODY__ } } ) {
5415             my $child_output = $child->walk_postorder( $action, $data, $self );
5416             push @{ $output }, @{ $child_output } if $child_output;
5417             }
5418              
5419             if ( $self->can( $action ) ) {
5420             $output = $self->$action( $output, $data, $parent );
5421             }
5422              
5423             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
5424             }
5425              
5426             sub build_lookup_hash {
5427             my $self = shift;
5428             my $child_output = shift;
5429             my $data = shift;
5430              
5431             return $child_output;
5432             }
5433              
5434             package # controller_config_block
5435             controller_config_block;
5436             use strict; use warnings;
5437              
5438             use base 'application_ancestor';
5439              
5440             sub new {
5441             my $class = shift;
5442             my $parent = shift;
5443             my $params = shift;
5444              
5445             my $self = {
5446             __PARENT__ => $parent,
5447             __IDENT__ => Bigtop::Parser->get_ident(),
5448             __BODY__ => [],
5449             __TYPE__ => $params->{ new_child }{ name },
5450             };
5451              
5452             return bless $self, $class;
5453             }
5454              
5455             sub change_name_controller_config {
5456             my $self = shift;
5457             shift;
5458             my $data = shift;
5459              
5460             return unless $self->get_ident eq $data->{ident};
5461              
5462             $self->{__TYPE__} = $data->{new_value};
5463              
5464             return [ 1 ];
5465             }
5466              
5467             sub get_controller_name {
5468             my $self = shift;
5469              
5470             return $self->{__PARENT__}->get_name();
5471             }
5472              
5473             sub get_ident {
5474             my $self = shift;
5475              
5476             return $self->{__IDENT__};
5477             }
5478              
5479             sub get_controller_configs {
5480             my $self = shift;
5481             my $child_output = shift;
5482              
5483             my $type = $self->{__TYPE__} || 'base';
5484              
5485             my %my_children;
5486             foreach my $child ( @{ $child_output } ) {
5487             $my_children{ $child->{ var } } = $child->{ val };
5488             }
5489              
5490             return [ { type => $type, statements => \%my_children } ];
5491             }
5492              
5493             sub add_config_statement {
5494             my $self = shift;
5495             shift;
5496             my $data = shift;
5497              
5498             return unless $data->{ ident } eq $self->{__IDENT__};
5499              
5500             my $new_statement = controller_config_statement->new(
5501             $data->{ keyword },
5502             $data->{ value },
5503             $self,
5504             );
5505              
5506             push @{ $self->{__BODY__} }, $new_statement;
5507              
5508             return [ 1 ];
5509             }
5510              
5511             sub remove_config_statement {
5512             my $self = shift;
5513             shift;
5514             my $data = shift;
5515             my $ident = $data->{ ident };
5516             my $keyword = $data->{ keyword };
5517              
5518             return unless $self->{__IDENT__} eq $ident;
5519              
5520             my $doomed_child = -1;
5521             my $count = 0;
5522              
5523             STATEMENT:
5524             foreach my $child ( @{ $self->{__BODY__} } ) {
5525             my $child_keyword = $child->get_keyword();
5526             if ( $keyword eq $child_keyword ) {
5527             $doomed_child = $count;
5528             last STATEMENT;
5529             }
5530             $count++;
5531             }
5532              
5533             if ( $doomed_child >= 0 ) {
5534             splice @{ $self->{__BODY__} }, $doomed_child, 1;
5535             }
5536              
5537             return [ 1 ];
5538             }
5539              
5540             sub app_block_hashes {
5541             my $self = shift;
5542             my $child_output = shift;
5543              
5544             my @statements;
5545              
5546             foreach my $child_item ( @{ $child_output } ) {
5547             my $no_accessor = 0;
5548             my $value = $child_item->{ value };
5549             if ( ref( $value ) eq 'HASH' ) {
5550             ( $value, $no_accessor ) = %{ $value };
5551             }
5552              
5553             push @statements, {
5554             keyword => $child_item->{ keyword },
5555             value => $value,
5556             no_accessor => $no_accessor,
5557             };
5558             }
5559              
5560             return [ {
5561             ident => $self->{__IDENT__},
5562             type => 'config',
5563             name => $self->{__TYPE__} || 'base',
5564             statements => \@statements,
5565             } ];
5566             }
5567              
5568             sub walk_postorder {
5569             my $self = shift;
5570             my $action = shift;
5571             my $data = shift;
5572             my $parent = shift;
5573              
5574             my $output = [];
5575              
5576             foreach my $child ( @{ $self->{__BODY__} } ) {
5577             my $child_output = $child->walk_postorder( $action, $data, $self );
5578             push @{ $output }, @{ $child_output } if $child_output;
5579             }
5580              
5581             if ( $self->can( $action ) ) {
5582             $output = $self->$action( $output, $data, $parent );
5583             }
5584              
5585             ( ref( $output ) =~ /ARRAY/ ) ? return $output : return;
5586             }
5587              
5588             sub build_lookup_hash {
5589             my $self = shift;
5590             my $child_output = shift;
5591             my $data = shift;
5592              
5593             return $child_output;
5594             }
5595              
5596             package # app_config_statement
5597             app_config_statement;
5598             use strict; use warnings;
5599              
5600             use base 'application_ancestor';
5601              
5602             sub new {
5603             my $class = shift;
5604             my $keyword = shift;
5605             my $value = shift;
5606             my $accessor = shift;
5607             my $parent = shift;
5608              
5609             my $self = {
5610             __PARENT__ => $parent,
5611             __KEYWORD__ => $keyword,
5612             };
5613            
5614             if ( $accessor ) {
5615             $self->{__ARGS__} = arg_list->new( [ { $value => 'no_accessor' } ] );
5616             }
5617             else {
5618             $self->{__ARGS__} = arg_list->new( [ $value ] );
5619             }
5620              
5621             return bless $self, $class;
5622             }
5623              
5624             sub get_keyword {
5625             my $self = shift;
5626              
5627             return $self->{__KEYWORD__};
5628             }
5629              
5630             sub get_config_statements {
5631             my $self = shift;
5632              
5633             return [ $self->{__KEYWORD__} => $self->{__ARGS__} ];
5634             }
5635              
5636             sub update_config_statement {
5637             my $self = shift;
5638             shift;
5639             my $data = shift;
5640              
5641             return unless ( $data->{ ident } eq $self->{__PARENT__}->get_ident );
5642              
5643             return [] unless ( $data->{ keyword } eq $self->{ __KEYWORD__ } );
5644              
5645             my $arg = $self->{__ARGS__}->get_first_arg();
5646              
5647             if ( ref( $arg ) eq 'HASH' ) {
5648             my ( $value, $no_access ) = %{ $arg };
5649              
5650             $self->{__ARGS__} = arg_list->new(
5651             [ { $data->{value} => $no_access } ]
5652             );
5653             }
5654             else {
5655             $self->{__ARGS__} = arg_list->new(
5656             [ $data->{value} ]
5657             );
5658             }
5659              
5660             return [ 1 ];
5661             }
5662              
5663             sub get_config_value {
5664             my $self = shift;
5665             shift;
5666             my $data = shift;
5667              
5668             my $config_type_name = $data->{ config_type_name };
5669             my $keyword = $data->{ keyword };
5670              
5671             # warn "I want the config value for:\n";
5672             # use Data::Dumper; warn Dumper( $data );
5673             # warn 'my config type: ' . $self->get_config_type_name() . "\n";
5674             # warn "my keyword: $self->{__KEYWORD__}\n";
5675            
5676             return [] unless ( $config_type_name eq $self->get_config_type_name() );
5677             return [] unless ( $keyword eq $self->{ __KEYWORD__ } );
5678              
5679             return $self->{__ARGS__};
5680             }
5681              
5682             sub config_statement_status {
5683             my $self = shift;
5684             shift;
5685             my $data = shift;
5686              
5687             return unless ( $data->{ ident } eq $self->{__PARENT__}->get_ident );
5688              
5689             return [] unless ( $data->{ keyword } eq $self->{ __KEYWORD__ } );
5690              
5691             my $arg = $self->{__ARGS__}->get_args();
5692              
5693             if ( $data->{ value } ) { # add no_accessor flag
5694             $self->{__ARGS__} = arg_list->new(
5695             [ { $arg => 'no_accessor' } ]
5696             );
5697             }
5698             else { # remove flag
5699             $self->{__ARGS__} = arg_list->new(
5700             [ $arg ]
5701             );
5702             }
5703              
5704             return [];
5705             }
5706              
5707             sub get_app_configs {
5708             my $self = shift;
5709              
5710             my $var = $self->{__KEYWORD__};
5711             my $val = $self->{__ARGS__}->get_first_arg;
5712              
5713             if ( ref( $val ) eq 'HASH' ) {
5714             ( $val ) = keys %{ $val };
5715             }
5716              
5717             return [ { var => $var, val => $val } ];
5718             }
5719              
5720             sub app_block_hashes {
5721             my $self = shift;
5722              
5723             return [ {
5724             keyword => $self->{__KEYWORD__},
5725             value => $self->{__ARGS__}[0],
5726             } ];
5727             }
5728              
5729             sub get_config_type_name {
5730             my $self = shift;
5731              
5732             return $self->{__PARENT__}{__TYPE__} || 'base';
5733             }
5734              
5735             sub walk_postorder {
5736             my $self = shift;
5737             my $action = shift;
5738             my $data = shift;
5739             my $parent = shift;
5740              
5741             if ( $self->can( $action ) ) {
5742             return $self->$action( undef, $data, $parent );
5743             }
5744             else {
5745             return;
5746             }
5747             }
5748              
5749             sub build_lookup_hash {
5750             my $self = shift;
5751             my $child_output = shift;
5752             my $data = shift;
5753              
5754             return [
5755             {
5756             '__TYPE__' => 'configs',
5757             '__DATA__' => [
5758             $self->{__KEYWORD__} => $self->{__ARGS__}
5759             ]
5760             }
5761             ];
5762             }
5763              
5764             package # controller_config_statement
5765             controller_config_statement;
5766             use strict; use warnings;
5767              
5768             use base 'application_ancestor';
5769              
5770             sub new {
5771             my $class = shift;
5772             my $keyword = shift;
5773             my $value = shift;
5774             my $parent = shift;
5775              
5776             my $self = {
5777             __PARENT__ => $parent,
5778             __KEYWORD__ => $keyword,
5779             };
5780            
5781             $self->{__ARGS__} = arg_list->new( [ $value ] );
5782              
5783             return bless $self, $class;
5784             }
5785              
5786             sub get_keyword {
5787             my $self = shift;
5788              
5789             return $self->{__KEYWORD__};
5790             }
5791              
5792             sub get_controller_configs {
5793             my $self = shift;
5794              
5795             my $var = $self->{__KEYWORD__};
5796             my $val = $self->{__ARGS__}->get_first_arg;
5797              
5798             if ( ref( $val ) eq 'HASH' ) {
5799             ( $val ) = keys %{ $val };
5800             }
5801              
5802             return [ { var => $var, val => $val } ];
5803             }
5804              
5805             sub app_block_hashes {
5806             my $self = shift;
5807              
5808             return [ {
5809             keyword => $self->{__KEYWORD__},
5810             value => $self->{__ARGS__}[0],
5811             } ];
5812             }
5813              
5814             sub update_config_statement {
5815             my $self = shift;
5816             shift;
5817             my $data = shift;
5818              
5819             return unless ( $data->{ ident } eq $self->{__PARENT__}->{__IDENT__} );
5820              
5821             return [] unless ( $data->{ keyword } eq $self->{ __KEYWORD__ } );
5822              
5823             my $arg = $self->{__ARGS__}->get_first_arg();
5824              
5825             $self->{__ARGS__} = arg_list->new(
5826             [ $data->{value} ]
5827             );
5828              
5829             return [ 1 ];
5830             }
5831              
5832             sub walk_postorder {
5833             my $self = shift;
5834             my $action = shift;
5835             my $data = shift;
5836             my $parent = shift;
5837              
5838             if ( $self->can( $action ) ) {
5839             return $self->$action( undef, $data, $parent );
5840             }
5841             else {
5842             return;
5843             }
5844             }
5845              
5846             sub build_lookup_hash {
5847             my $self = shift;
5848             my $child_output = shift;
5849             my $data = shift;
5850              
5851             return [
5852             {
5853             '__TYPE__' => 'configs',
5854             '__DATA__' => [
5855             $self->{__KEYWORD__} => $self->{__ARGS__}
5856             ]
5857             }
5858             ];
5859             }
5860              
5861             package # arg_list
5862             arg_list;
5863             use strict; use warnings;
5864              
5865             sub new {
5866             my $class = shift;
5867             my $values = shift;
5868             my $pair_required = shift;
5869              
5870             return bless build_values( $values, $pair_required ), $class;
5871             }
5872              
5873             sub build_values {
5874             my $values = shift;
5875             my $pair_required = shift;
5876              
5877             if ( ref( $values ) eq 'ARRAY' ) {
5878             return $values;
5879             }
5880             elsif ( ref( $values ) eq 'HASH' ) {
5881             my $value_str = $values->{ values } || '';
5882             my @keys = split /\]\[/, $values->{ keys };
5883             my @values = split /\]\[/, $value_str;
5884              
5885             my @retvals;
5886              
5887             for ( my $i = 0; $i < @keys; $i++ ) {
5888             if ( $pair_required ) {
5889             my $push_value = defined ( $values[ $i ] )
5890             ? $values[ $i ]
5891             : '';
5892              
5893             push @retvals, { $keys[ $i ] => $push_value };
5894             }
5895             elsif ( defined $pair_required ) {
5896             if ( not defined $values[ $i ]
5897             or
5898             $values[ $i ] eq 'undefined'
5899             or
5900             not $values[ $i ]
5901             ) {
5902             push @retvals, $keys[ $i ];
5903             }
5904             else {
5905             push @retvals, { $keys[ $i ] => $values[ $i ] };
5906             }
5907             }
5908             else {
5909             if ( defined $values[ $i ] and $values[ $i ] ne 'undefined' ) {
5910             push @retvals, { $keys[ $i ] => $values[ $i ] };
5911             }
5912             else {
5913             push @retvals, $keys[ $i ];
5914             }
5915             }
5916             }
5917              
5918             return \@retvals;
5919             }
5920             else {
5921             my @values = split /\]\[/, $values;
5922              
5923             return \@values;
5924             }
5925             }
5926              
5927             sub get_first_arg {
5928             my $self = shift;
5929              
5930             return $self->[0];
5931             }
5932              
5933             sub get_args {
5934             my $self = shift;
5935              
5936             my @args;
5937              
5938             foreach my $arg ( @{ $self } ) {
5939             if ( ref( $arg ) =~ /HASH/ ) {
5940             my ( $name, $condition ) = %{ $arg };
5941             push @args, $name;
5942             }
5943             else {
5944             push @args, $arg;
5945             }
5946             }
5947              
5948             return join ', ', @args;
5949             }
5950              
5951             sub get_quoted_args {
5952             my $self = shift;
5953              
5954             my @args;
5955              
5956             foreach my $arg ( @{ $self } ) {
5957             if ( ref( $arg ) =~ /HASH/ ) {
5958             my ( $name, $condition ) = %{ $arg };
5959              
5960             unless ( $name =~ /^\w[\w\d_]*$/ ) {
5961             $name = "`$name`";
5962             }
5963              
5964             unless ( $condition =~ /^\w[\w\d_]*$/ ) {
5965             $condition = "`$condition`";
5966             }
5967              
5968             push @args, "$name => $condition";
5969             }
5970             else {
5971             my $value = $arg;
5972             if ( $value !~ /^\w[\w\d_:]*$/ ) {
5973             $value = "`$value`";
5974             }
5975             else {
5976             my @value_pieces = split /::/, $value;
5977             # if any of the pieces has a colon, quote the value
5978             VALUE_PIECE:
5979             foreach my $piece ( @value_pieces ) {
5980             if ( $piece =~ /:/ ) {
5981             $value = "`$value`";
5982             last VALUE_PIECE;
5983             }
5984             }
5985             }
5986              
5987             push @args, $value;
5988             }
5989             }
5990              
5991             return ( wantarray ) ? @args : join ', ', @args;
5992             }
5993              
5994             sub get_unquoted_args {
5995             my $self = shift;
5996              
5997             my @args;
5998              
5999             foreach my $arg ( @{ $self } ) {
6000             if ( ref( $arg ) =~ /HASH/ ) {
6001             my ( $name, $condition ) = %{ $arg };
6002              
6003             push @args, "$name => $condition";
6004             }
6005             else {
6006             push @args, $arg;
6007             }
6008             }
6009              
6010             return \@args;
6011             }
6012              
6013             sub set_args_from {
6014             my $self = shift;
6015             my $new_values = shift;
6016             my $pair_required = shift;
6017              
6018             pop @{ $self } while ( @{ $self } );
6019              
6020             my $paired_values = build_values( $new_values, $pair_required );
6021              
6022             push @{ $self }, @{ $paired_values };
6023             }
6024              
6025             sub one_hash {
6026             my $self = shift;
6027              
6028             my %args;
6029              
6030             foreach my $arg ( @{ $self } ) {
6031             if ( ref( $arg ) =~ /HASH/ ) {
6032             my ( $key, $value ) = %{ $arg };
6033             $args{ $key } = $value;
6034             }
6035             else {
6036             $args{ $arg } = undef;
6037             }
6038             }
6039              
6040             return \%args;
6041             }
6042              
6043             sub unbless_args {
6044             my $self = shift;
6045              
6046             my @args;
6047              
6048             foreach my $arg ( @{ $self } ) {
6049             push @args, $arg;
6050             }
6051              
6052             return \@args;
6053             }
6054              
6055             1;
6056              
6057             =head1 NAME
6058              
6059             Bigtop::Parser - the Parse::RecDescent grammar driven parser for bigtop files
6060              
6061             =head1 SYNOPSIS
6062              
6063             Make a file like this:
6064              
6065             config {
6066             base_dir `/home/username`;
6067             Type1 Backend {}
6068             Type2 Backend {}
6069             Type3 Backend {}
6070             }
6071             app App::Name {
6072             table name { }
6073             controller SomeController {}
6074             }
6075              
6076             Then run this command:
6077              
6078             bigtop my.bigtop all
6079              
6080             =head1 DESCRIPTION
6081              
6082             This module is really only designed to be used by the bigtop and tentmaker
6083             scripts. It provides access to the grammar which understands bigtop files
6084             and turns them into syntax trees. It provides various utility functions
6085             for bigtop, tentmaker, backends, and similar tools you might write.
6086              
6087             If you just want to use bigtop, you should look in C
6088             where all the docs are outlined.
6089              
6090             Reading further is an indication that you are interested in working on Bigtop
6091             and not just in using it to serve your needs.
6092              
6093             =head1 METHODS
6094              
6095             In this section, the methods are grouped, so that similar ones appear together.
6096              
6097             =head2 METHODS which drive generation for scripts
6098              
6099             =over 4
6100              
6101             =item gen_from_file
6102              
6103             The bigtop script calls this method.
6104              
6105             Returns: the app name and the name of the build directory.
6106              
6107             You can call this as a class method passing it the name of the bigtop
6108             file to read and a list of the things to build.
6109              
6110             The method is actually quite simple. It merely reads the file, then
6111             calls gen_from_string.
6112              
6113             =item gen_from_string
6114              
6115             The bigtop script calls this method when --new is used.
6116              
6117             Returns: the app name and the name of the build directory.
6118              
6119             This method orchestrates the build. It is called internally by gen_from_file.
6120             Call it as a class method. Pass it a hash with these keys:
6121              
6122             bigtop_string => the bigtop source code
6123             bigtop_file => the name of the bigtop file, if you know it
6124             create => are you in create mode? if so make this true
6125             build_list => [ what to build ]
6126             flags => command line args given to your script
6127              
6128             bigtop_file is used by Bigtop::Init::Std to copy the bigtop file from
6129             its original location into the docs subdirectory of the build directory.
6130             If the file name is not defined, it skips that step.
6131              
6132             If you set create to any true value, you will be in create mode and bigtop
6133             will make the build directory as a subdirectory of the current directory.
6134             Otherwise, it will make sure you are in a directory which looks like a build
6135             directory before building.
6136              
6137             The list of things to build can include any backend type listed in the
6138             config block and/or the word 'all'. 'all' will be replaced with a list
6139             of all the backend types in the config section (in the order they appear
6140             there), as if they had been passed in.
6141              
6142             It is legal to mention the same backend more than once. For instance, you
6143             could call gen_from_string directly
6144              
6145             Bigtop::Parser->gen_from_string(
6146             {
6147             bigtop_string => $bigtop_string,
6148             bigtop_file => 'file.bigtop',
6149             create => $create,
6150             build_list => [ 'Init', 'Control', 'Init' ]
6151             }
6152             );
6153              
6154             or equivalently, and more typically, you could call gen_from_file:
6155              
6156             Bigtop::Parser->gen_from_file(
6157             'file.bigtop', $create, 'Init', 'Control', 'Init'
6158             );
6159              
6160             Either of these might be useful, if the first Init sets up directories that
6161             the Control backend needs, but the generated output from Control should
6162             influence the contents of file which Init finally builds. Check your backends
6163             for details.
6164              
6165             The flags are given to Init Std as text, so they may be preserved for
6166             posterity in the Changes file.
6167              
6168             gen_from_string internals
6169              
6170             gen_from_string works like this. First, it attempts to parse the config
6171             section of the bigtop string. If that works, it iterates through each
6172             backend mentioned there building a list of modules to require. This
6173             includes looking in backend blocks for template statements. Their values
6174             must be template files relative to the directory from which bigtop
6175             was invoked.
6176              
6177             Once the list is built, it calls its own import method to require them.
6178             This allows each backend to register its keywords. If any keyword
6179             used in the app section is not registered, a fatal parse error results.
6180              
6181             Once the backends are all required, gen_from_string parses the whole
6182             bigtop string into an abstract syntax tree (AST). Then it iterates
6183             through the build list calling gen_Type on each element's backend.
6184             So this:
6185              
6186             config {
6187             Init Std {}
6188             SQL Postgres { template `postgres.tt`; }
6189             }
6190             app ...
6191              
6192             Bigtop::Parser->gen_from_string(
6193             $bigtop_string, 'file.bigtop', 'Init', 'SQL'
6194             );
6195              
6196             Results first in the loading of Bigtop::Init::Std and Bigtop::SQL::Postgres,
6197             then in calling gen_Init on Init::Std and gen_SQL on SQL::Postgres. During
6198             the loading, setup_template is called with postgres.tt on SQL::Postgres.
6199              
6200             gen_* methods are called as class methods. They receive the build directory,
6201             the AST, and the name of the bigtop_file (which could be undef).
6202             Backends can do whatever they like from there. Typically, they put
6203             files onto the disk. Those files might be web server conf files,
6204             sql to build the database, control modules, templates for viewing, models,
6205             etc.
6206              
6207             =back
6208              
6209             =head2 METHODS which invoke the grammar
6210              
6211             =over 4
6212              
6213             =item parse_config_string
6214              
6215             Called as a class method (usually by gen_from_string), this method receives
6216             the bigtop input as a string. It attempts to parse only the config section
6217             which it returns as an AST. Syntax errors in the config section are
6218             fatal. Errors in the app section are not noticed.
6219              
6220             =item parse_file
6221              
6222             Call this as a class method, passing it the file name to read. It reads
6223             the file into memory, then calls parse_string, returning whatever it
6224             returns.
6225              
6226             =item parse_string
6227              
6228             Call this as a class method, passing it the bigtop string to parse.
6229             It calls the grammar to turn the input into an AST, which it returns.
6230              
6231             =back
6232              
6233             =head2 METHODS which control which simple statement keywords are legal
6234              
6235             =over 4
6236              
6237             =item add_valid_keywords
6238              
6239             The grammar of a bigtop file is structured, but the legal keywords in
6240             its simple statements are defined by the backends (excepts that the config
6241             keywords are defined by this module, see Config Keywords below for those).
6242              
6243             Acutally, all the keywords that any module will use should be defined
6244             in C so tentmaker can display them. Then the backend
6245             (or its type) should pull the keyword definitions it wants from
6246             C.
6247              
6248             If you are writing a backend, you should use the base module for your
6249             backend type. This will register the standard keywords for that type.
6250             For example, suppose you are writing Bigtop::Backend::SQL::neWdB. It
6251             should be enough to say:
6252              
6253             use Bigtop::SQL;
6254              
6255             in your module.
6256              
6257             If you need to add additional keywords that are specific to your backend,
6258             put them in a begin block like this:
6259              
6260             BEGIN {
6261             Bigtop::Parser->add_valid_keywords(
6262             Bigtop::Keywords->get_docs_for(
6263             $type,
6264             qw( your keywords here),
6265             )
6266             );
6267             }
6268              
6269             Here $type is the name of the surrounding block in which this keyword
6270             will make a valid statement. For example, if $type above is 'app' then
6271             this would be legal:
6272              
6273             app App::Name {
6274             your value;
6275             }
6276              
6277             The type must be one of these levels:
6278              
6279             =over 4
6280              
6281             =item config
6282              
6283             =item app
6284              
6285             =item app_literal
6286              
6287             =item table
6288              
6289             =item join_table
6290              
6291             =item field
6292              
6293             =item controller
6294              
6295             =item controller_literal
6296              
6297             =item method
6298              
6299             =back
6300              
6301             These correspond to the block types in the grammar. Note, that there
6302             are also sequence blocks, but they are deprecated and never allowed statements.
6303             Further, the various literals are blocks in the grammar (they have block
6304             idents and can have defined keywords), but they don't have brace delimiters.
6305             Instead, they have a single backquoted string.
6306              
6307             =item is_valid_keyword
6308              
6309             Call this as a class method, passing it a type of keyword and a word that
6310             might be a valid keyword of that type.
6311              
6312             Returns true if the keyword is valid, false otherwise.
6313              
6314             =item get_valid_keywords
6315              
6316             Call this as a class method passing it the type of keywords you want.
6317              
6318             Returns a list of all registered keywords, of the requested type, in
6319             string sorted order.
6320              
6321             The two preceding methogs are really for internal use in the grammar.
6322              
6323             =back
6324              
6325             =head2 METHODS which work on the AST
6326              
6327             There are quite a few other methods not documented here (shame on me).
6328             Most of those support tentmaker manipulations of the tree, but there
6329             are also some convenience accessors.
6330              
6331             =over 4
6332              
6333             =item walk_postorder
6334              
6335             Walks the AST for you, calling you back when it's time to build something.
6336              
6337             The most common skeleton for gen_Backend is:
6338              
6339             use Bigtop;
6340             use Bigtop::Backend;
6341              
6342             sub gen_Backend {
6343             my $class = shift;
6344             my $build_dir = shift;
6345             my $tree = shift;
6346              
6347             # walk the tree
6348             my $something = $tree->walk_postoder( 'output_something' );
6349             my $something_str = join '', @{ $something };
6350              
6351             # write the file
6352             Bigtop::write_file( $build_dir, $something_string );
6353             }
6354              
6355             This walks the tree from the root. The walking is postorder meaning that
6356             all children are visited before the current node. Each walk_postorder
6357             returns an array reference (which is why we have to join the result
6358             in the above skeleton). After the children have been visited, the
6359             callback (C in the example) is called with their output
6360             array reference. You can also pass an additional scalar (which is usually
6361             a hash reference) to walk_postorder. It will be passed along to all
6362             the child walk_postorders and to the callbacks.
6363              
6364             With this module walking the tree, all you must do is provide the appropriate
6365             callbacks. Put one at each level of the tree that interests you.
6366              
6367             For example, if you are generating SQL, you need to put callbacks in
6368             at least the following packages:
6369              
6370             table_block
6371             table_element_block
6372             field_statement
6373              
6374             This does require some knowledge of the tree. Please consult bigtop.grammar,
6375             in the lib/Bigtop subdirectory of Bigtop's build directory,
6376             for the possible packages (or grep for package on this file).
6377             There are also several chapters of the Gantry book devoted to explaining
6378             how to use the AST to build backends.
6379              
6380             The callbacks are called as methods on the current tree node. They receive
6381             the output array reference from their children and the data scalar that
6382             was passed to walk_postorder (if one was passed in the top level call).
6383             So, a typical callback method might look like this:
6384              
6385             sub output_something {
6386             my $self = shift;
6387             my $child_output = shift;
6388             my $data = shift;
6389             ...
6390             return [ $output ];
6391             }
6392              
6393             Remember that they must return an array reference. If you need something
6394             fancy, you might do this:
6395              
6396             return [ [ type1_output => $toutput, type2_output => $other_out ] ];
6397              
6398             Then the parent package's callback will receive that and must tease
6399             apart the the two types. Note that I have nested arrays here. This prevents
6400             two children from overwriting each other's output if you are ever tempted
6401             to try saving the return list directly to a hash (think recursion).
6402              
6403             (walk_postorder also passes the current node to each child after the
6404             data scalar. This is the child's parent, which is really only useful
6405             during parent building inside the grammar. The parent comes
6406             after the data scalar in both walk_postorder and in the callback.
6407             Most backends will just peek in $self->{__PARENT__} which is gauranteed
6408             to have the parent once the grammar finishes with the AST.)
6409              
6410             =item set_parent
6411              
6412             This method is the callback used by the grammar to make sure that all nodes
6413             know who their daddy is. You shouldn't call it, but looking at it shows
6414             what the simplest callback might look like. Note that there is only one
6415             of these and it lives in the application_ancestor package, which is not
6416             one of the packages defined in the grammar. But, this module makes
6417             sure that all the grammar defined packages inherit from it.
6418              
6419             =item build_lookup_hash
6420              
6421             This method builds the lookup hash you can use to find data about other
6422             parts of the tree, without walking to it.
6423              
6424             The AST actually has three keys: configuration, application, and lookup.
6425             The first two are built in the normal way from the input file. They
6426             are genuine ASTs in their own right. The lookup key is not. It does
6427             not preserve order. But it does make it easier to retrieve things.
6428              
6429             For example, suppose that you are in the method_body package attempting
6430             to verify that requested fields for this method are defined in the
6431             table for this controller. You could walk the tree, but the lookup hash
6432             makes it easier:
6433              
6434             unless (
6435             defined $tree->{lookup}{tables}{$table_name}{fields}{$field_name}
6436             ) {
6437             die "No such column $field_name\n";
6438             }
6439              
6440             The easiest way to know what is available is to dump the lookup hash.
6441             But the pattern is basically this. At the top level there are fixed keywords
6442             for the app level block types: tables, sequences, controllers. The next
6443             level is the name of a block. Under that, there is a fixed keyword for
6444             each subblock type, etc.
6445              
6446             =back
6447              
6448             =head2 METHODS for use in walk_postorder callbacks
6449              
6450             =over 4
6451              
6452             =item dumpme
6453              
6454             Use this method instead of directly calling Data::Dumper::Dump.
6455              
6456             While you could dump $self, that's rather messy. The problem is the parent
6457             nodes. Their presence means a simple dump will always show the whole app
6458             AST. This method carefully removes the parent, dumps the node, and restores
6459             the parent, reducing clutter and leaving everything in tact. The closer
6460             to a leaf you get, the better it works.
6461              
6462             =item get_appname
6463              
6464             Call this on the full AST. It returns the name of the application.
6465              
6466             =item get_config
6467              
6468             Call this on the full AST. It returns the config subtree.
6469              
6470             =item get_controller_name
6471              
6472             Call this, from the method_body package, on the AST node ($self in the
6473             callback). Returns the name of the controller for this method. This
6474             is useful for error reporting.
6475              
6476             =item get_method_name
6477              
6478             Call this, from the method_body package, on the AST node ($self in the
6479             callback). Returns the name of this method. Useful for error reporting.
6480              
6481             =item get_name
6482              
6483             While this should work everywhere, it doesn't. Some packages have it.
6484             If yours does, call it. Otherwise peek in $self->{__NAME__}. But,
6485             remember that not everything has a name.
6486              
6487             =item get_table_name
6488              
6489             Call this, from the method_body package, on the AST node ($self in the
6490             callback). Returns the name of the table this controller controls.
6491             Useful for error reporting.
6492              
6493             =back
6494              
6495             =head2 METHODS used internally
6496              
6497             =over 4
6498              
6499             =item import
6500              
6501             You probably don't need to call this. But, if you do, pass it a list
6502             of backends to import like this:
6503              
6504             use Bigtop::Parser qw( Type=Backend=template.tt );
6505              
6506             This will load Bigtop::Type::Backend and tell it to use template.tt.
6507             You can accomplish the same thing by directly calling import as a class
6508             method:
6509              
6510             Bigtop::Parser->import( 'Type=Backend=template.tt' );
6511              
6512             =item fatal_error_two_lines
6513              
6514             This method is used by the grammar to report fatal parse error in the input.
6515             It actually gives 50 characters of trailing context, not two lines, but
6516             the name stuck.
6517              
6518             =item fatal_keyword_error
6519              
6520             This method is used by the grammer to report on unregistered (often misspelled)
6521             keywords. It identifies the offending keyword and the line where it appeared
6522             in the input, gives the remainder of the line on which it was seen (which
6523             is sometimes only whitespace), and lists the legal choices (often wrapping
6524             them in an ugly fashion).
6525              
6526             =back
6527              
6528             =head1 Config KEYWORDS
6529              
6530             For simplicity, all config keywords are requested from C
6531             in this module. This is not necessarily ideal and is subject to change.
6532              
6533             =over 4
6534              
6535             =item base_dir
6536              
6537             Used only if you supply the --create flag to bigtop (or set create to true
6538             when calling gen_from_file or gen_from_string as class methods of this
6539             module).
6540              
6541             When in create mode, the build directory will be made as a subdirectory
6542             of the base_dir. For instance, I could use my home directory:
6543              
6544             base_dir `/home/username`;
6545              
6546             Note that you need the backquotes to hide the slashes. Also note, that
6547             you should use a path which looks good on your development system. In
6548             particular, this would work on the appropriate platform:
6549              
6550             base_dir `C:\path\to\build`;
6551              
6552             The default base_dir is the current directory from which bigtop is run.
6553              
6554             =item app_dir
6555              
6556             Used only if you supply the --create flag to bigtop (or set create to true
6557             when calling gen_from_file or gen_from_string as class methods of this
6558             module).
6559              
6560             When in create mode, the actual generated files will be placed into
6561             base_dir/app_dir (where the slash is correctly replaced with your OS
6562             path separator). If you are in create mode, but don't supply an app_dir,
6563             a default is formed from the app name in the manner h2xs would use.
6564             Consider:
6565              
6566             config {
6567             base_dir `/home/username`;
6568             }
6569             app App::Name {
6570             }
6571              
6572             In this case the app_dir is App-Name. So the build directory is
6573              
6574             /home/username/App-Name
6575              
6576             By specifying your own app_dir statement, you have complete control
6577             of where the app is initially built. For example:
6578              
6579             config {
6580             base_dir `/home/username`;
6581             app_dir `myappdir`;
6582             }
6583             app App::Name { }
6584              
6585             Will build in /home/username/myappdir.
6586              
6587             When not using create mode, all files will be built under the current
6588             directory. If that directory doesn't look like an app build directory,
6589             a fatal error will result. Either move to the proper directory, or
6590             use create mode to avoid the error.
6591              
6592             =item engine
6593              
6594             This is passed directly to the C statement of the top level
6595             controller.
6596              
6597             Thus,
6598              
6599             engine MP13;
6600              
6601             becomes something like this:
6602              
6603             use Framework qw/ engine=MP13 /;
6604              
6605             in the base level controller. Both Catalyst and Gantry expect this
6606             syntax.
6607              
6608             The available engines depend on what the framework supports. The one
6609             in the example is mod_perl 1.3 in the syntax of Catalyst and Gantry.
6610              
6611             =item template_engine
6612              
6613             Similar to engine, this specifies the template engine. Choices almost always
6614             include TT, but might also include Mason or other templaters depending on
6615             what your framework supports..
6616              
6617             =back
6618              
6619             =head1 Other KEYWORDS
6620              
6621             =over 4
6622              
6623             =item literal
6624              
6625             This keyword applies to many backends at the app level and at some other
6626             levels. This keyword is special, because it expects a type keyword
6627             immediately before its values. For example:
6628              
6629             literal SQL `CREATE...`;
6630              
6631             It always instructs someone (the backend of type SQL in the example) to
6632             directly insert the backquoted string into its output, without so much as
6633             adjusting whitespace.
6634              
6635             Backend types that should obey this statement are:
6636              
6637             SQL - for backends of type SQL
6638             Location - for backends constructing apache confs or the like
6639              
6640             The literal Location statement may also be used at the controller level.
6641              
6642             =item no_gen
6643              
6644             Applies to backend blocks in the config block, app blocks, controller
6645             blocks, and method blocks.
6646              
6647             gen_from_string enforces the app level no_gen. If it has a true value
6648             only a warning is printed, nothing is generated. None of the backends
6649             are called.
6650              
6651             gen_from_string also enforces no_gen on entire backends, if their config
6652             block has a true no_gen value.
6653              
6654             The Control backend of your choice is responsible for enforcing no_gen
6655             at the controller and method levels.
6656              
6657             =item not_for
6658              
6659             Applies to tables and fields (although the latter only worked for Models
6660             at the time of this writing).
6661              
6662             Each backend is responsible for enforcing not_for. It should mean
6663             that the field or table is ignored by the named backend type. Thus
6664              
6665             table skip_model {
6666             not_for Model;
6667             }
6668              
6669             should generate as normal in SQL backends, but should be completely
6670             ignored for Models. The same should hold for fields marked not_for.
6671             But my SQL backends didn't do that when I wrote this, only the Models
6672             worked.
6673              
6674             =back
6675              
6676             =head1 METHODS
6677              
6678             =over 4
6679              
6680             =item get_keyword_docs
6681              
6682             Called by TentMaker, so it can display the backend comments to the user
6683             through their browser.
6684              
6685             Returns: a hash reference of keyword docs understood by tentmaker's
6686             templates.
6687              
6688             =item gen_mode
6689              
6690             Used internally.
6691              
6692             Get accessor for whether we are really generating, or just serving tentmaker.
6693             If we are not generating, there is no need to set up the templates for
6694             all the backends.
6695              
6696             =item set_gen_mode
6697              
6698             Used internally.
6699              
6700             Set accessor for whether we are really generating.
6701              
6702             =item get_ident
6703              
6704             Returns: the next available ident (as ident_n).
6705              
6706             =item get_parser
6707              
6708             Used internally.
6709              
6710             Accessor to ensure that only one parser is ever instantiated.
6711              
6712             =item load_backends
6713              
6714             Used internally.
6715              
6716             Responsible for loading all needed backends.
6717              
6718             =item preprocess
6719              
6720             Used internally.
6721              
6722             Strips comment lines.
6723              
6724             Returns: a hash keyed by line number, storing the comment on that line
6725             before it was stripped..
6726              
6727             =back
6728              
6729             =head1 AUTHOR
6730              
6731             Phil Crow
6732              
6733             =head1 COPYRIGHT and LICENSE
6734              
6735             Copyright (C) 2005-7 by Phil Crow
6736              
6737             This library is free software; you can redistribute it and/or modify
6738             it under the same terms as Perl itself, either Perl version 5.8.6 or,
6739             at your option, any later version of Perl 5 you may have available.
6740              
6741             =cut