Bigtop

 view release on metacpan or  search on metacpan

lib/Bigtop/Docs/Modules.pod  view on Meta::CPAN


what_do_you_make should return an array reference describing the things
your backend writes on the disk.  Each array element is also an array
reference with two entries.  First is the name of something made by
the module, second is a brief description of what that piece has in it.
These appear as documentation in the tentmaker application.

 sub backend_block_keywords {
     return [
           { keyword => 'no_gen',
             label   => 'No Gen',
             descr   => 'Skip everything for this backend',
             type    => 'boolean' },

           { keyword => 'template',
             label   => 'Alternate Template',
             descr   => 'A custom TT template.',
             type    => 'text' },
           ];
 }

backend_block_keywords is similar to what_do_you_make.  It lists all
the valid keywords which can go in the backend's block in the config
section at the top of the Bigtop file.  These appear in order in the
far right column of the Backends tab of tentmaker.  The above keys are
required, if you need a default use the C<default> key.  If the type is
boolean, spell out C<true> or C<false> as the default value (these
are going to HTML and/or Javascript as strings).  If you don't specify a
default, you get false (unchecked) for booleans and blank for strings.

=head3 The generating sub

 sub gen_SQL {
     shift;
     my $base_dir = shift;
     my $tree     = shift;

The bigtop script will call gen_SQL (via gen_from_sting) when the
user has this backend in their config section and invokes bigtop with
SQL or all in the list of build items.

The class name is not needed, so I shifted it into the ether.

The $base_dir is where the output goes.

The $tree is the full AST (see above for details).

     # walk tree generating sql
     my $lookup       = $tree->{application}{lookup};
     my $sql          = $tree->walk_postorder( 'output_sql_lite', $lookup );
     my $sql_output   = join '', @{ $sql };

The lookup subtree of the application subtree provides easier access to
the data in the tree (though it doesn't have all the connectors the AST
has for parsing use, in particular it uses hashes exclusively, so it never
intentionally preserves order).

I let Bigtop::Parser's walk_postorder do the visiting of tree nodes
for me.  It will call 'output_sql_lite' on each of them.  I implement that
on the packages my SQL generator cares about below.  I pass the lookup
hash to walk_postorder so it will be available to the callbacks.

Note that the name of the walk_postorder action needs to be unique among
all Bigtop::Backend::* modules.  This prevents subroutine redefinitions
(and their warnings) when multiple SQL backends are in use.  It also
makes tentmaker run more quietly in all cases.  Choose names with some
tag relating to your backend to avoid namespace collisions.

The output of walk_postorder is always an array reference.  I join it
together and store it in $sql_output.

     # write the schema.postgres
     my $docs_dir     = File::Spec->catdir( $base_dir, 'docs' );
     mkdir $docs_dir;

By the convention of our shop, the schema.sqlite file lives in the docs
directory of the generated distribution.  Here, I make that directory
(if that fails we'll hear loud screaming shortly).

All that remains is to put the output into the file:

     my $sql_file     = File::Spec->catfile( $docs_dir, 'schema.sqlite' );

     open my $SQL, '>', $sql_file or die "Couldn't write $sql_file: $!\n";

     print $SQL $sql_output;

     close $SQL or die "Couldn't close $sql_file: $!\n";
 }

So, the whole generation method is only 22 lines.  Except for the specific
use of 'sqlite' or 'lite', this method is the same for the other SQL backends.
Of course, there is still a lot left for me to do.

=head3 Output appearance control

Like most backends, this one uses Inline::TT to control the appearance
of the output.  If users don't like the appearance, they have only
to copy the template into another file, edit it to suit them, and
tell the module by including a template statement in the config block for
the backend:

    config {
        SQL Postgres { template `my.tt`; }
        # ...
    }

Here is my default template:

   our $template_is_setup = 0;
   our $default_template_text = <<'EO_TT_blocks';
   [% BLOCK sql_block %]
   CREATE [% keyword %] [% name %][% child_output %]
   
   [% END %]
   
   [% BLOCK table_body %]
    (
   [% FOREACH child_element IN child_output %]
   [% child_element +%][% UNLESS loop.last %],[% END %]
   

lib/Bigtop/Docs/Modules.pod  view on Meta::CPAN

each of which may be used repeatedly while generating output:

=over 4

=item sql_block

Wraps the body of an SQL CREATE statement with 'CREATE name'.

=item table_body

Wraps all of the column definitions in each CREATE TABLE statement with
parentheses.

=item table_element_block

Makes the column definition statements for CREATE TABLE bodies.

=item field_statement

Concatenates the individual definition clauses for a column definition
statement.

=item insert_statement

Makes the INSERT statements which correspond to data statements in the
Bigtop file.

=item three_way

Makes the CREATE TABLE block for implicit tables which join other tables.
These come from join_table blocks in the Bigtop file which in turn
come from a<->b in ASCII art passed to bigtop or tentmaker at the command line.

=back

To make the template operative, requires implementing setup_template:

    sub setup_template {
        my $class         = shift;
        my $template_text = shift || $default_template_text;

        return if ( $template_is_setup );

        Inline->bind(
                TT                  => $template_text,
                POST_CHOMP          => 1,
                TRIM_LEADING_SPACE  => 0,
                TRIM_TRAILING_SPACE => 0,
                );

        $template_is_setup = 1;
    }

The parser calls this (if the package can respond to it) prior to
calling gen_SQL.  If the user has supplied an alernate template, it
is passed to setup_template.

To avoid bad template binding, $template_is_setup keeps track of whether
we've been here before.

Inline's bind method creates subs in the current name space for callbacks
to use.  Note that if $template_text is a file name, that file will be
bound correctly.

I've tried to abstract out this code so all backends can share it, but
the nature of Inline bindings makes that difficult, so I gave up.

=head3 Real work

All that remains is the real work.  We need to implement output_sql in
about half a dozen packages.

=head4 table_block

 package # table_block
     table_block;
 use strict; use warnings;

 sub output_sql_lite {
     my $self         = shift;
     my $child_output = shift;
  
     return if ( $self->_skip_this_block );
  
     my %output;
     foreach my $statement ( @{ $child_output } ) {
         my ( $type, $output ) = @{ $statement };
         push @{ $output{ $type } }, $output;
     }
  
     my $child_out_str = Bigtop::Backend::SQL::SQLite::table_body(
         { child_output => $output{table_body} }
     );
  
     if ( defined $output{insert_statements} ) {
         $child_out_str .= "\n" . join "\n", @{ $output{insert_statements} };
     }
  
     my $output = Bigtop::Backend::SQL::SQLite::sql_block(
         {
             keyword      => $self->get_create_keyword(),
             child_output => $child_out_str,
             name         => $self->get_name(),
         }
     );
  
     return [ $output ];
 }


As all callbacks do, this one receives the current tree node as its
invocant and the output of its children as parameters.  (It also
receives the data passed to walk_postorder, but this method doesn't need it.)

The child output comes from the walk_postorder method of this package.
It is always an array reference.  In this case, that array has one or more
subarrays.  Each of those has two elements: type and text.  The types
are: table_body and insert_statements.  The table_body elements must
go inside the body of the CREATE TABLE statement.  The insert_statements
must be placed after the CREATE TABLE statement.  There is one of those
for each data statement in the table block of the Bigtop file.

While the child output is always an array reference, its contents are up
to you.  We'll see how I formed the child output for this package below,
when we walk into the child packages.  Usually I only put arrays in it,
to avoid confusion.

Once the child output is divided into two pieces, I first call the
table_body template BLOCK.  Then I join the insert statements together
as strings.  Finally, I call the sql_block template BLOCK.  I've used
the get_name provided by the sql_block package in Bigtop::Parser.
The get_create_keyword sub is defined in Bigtop::Backend::SQL.  The same
method (with different output) is defined there for the seq_block package
used by Bigtop::Backend::SQL::Postgres to build sequence statements.

Note that I am careful to give my output back in an array, even though
I have only one item.  This is required by all walk_postorder
actions.

=head4 table_element_block

 package # table_element_block
     table_element_block;
 use strict; use warnings;

 sub output_sql_lite {
     my $self         = shift;
     my $child_output = shift;
 
     if ( defined $child_output) {

         my $child_out_str = join "\n", @{ $child_output };

         my $output = Bigtop::Backend::SQL::SQLite::table_element_block(
             { name => $self->get_name(), child_output => $child_out_str }
         );

         return [ [ table_body => $output ] ];
     }

There are two kinds of children for the table_element_block: fields
(which are themselves blocks) and statements (where are leaves in the AST).
So, if there is child output, we can safely assume that this node is a field
block.  In which case, we join the child output, send it to the
table_element_block TT BLOCK and return the output, being careful
to note that it belongs in the table_body.

Note that I always return arrays, even if I want to return a hash.  This
avoids rare but nasty bugs when the returned values pass through packages
which aren't responding to the current action.



( run in 0.909 second using v1.01-cache-2.11-cpan-6aa56a78535 )