Index: trunk/lib/Text/FormBuilder.pm
===================================================================
--- trunk/lib/Text/FormBuilder.pm	(revision 15)
+++ trunk/lib/Text/FormBuilder.pm	(revision 16)
@@ -4,6 +4,7 @@
 use warnings;
 
-our $VERSION = '0.03';
-
+our $VERSION = '0.04';
+
+use Carp;
 use Text::FormBuilder::Parser;
 use CGI::FormBuilder;
@@ -34,7 +35,13 @@
 sub parse_text {
     my ($self, $src) = @_;
+    
     # so it can be called as a class method
     $self = $self->new unless ref $self;
+    
     $self->{form_spec} = $self->{parser}->form_spec($src);
+    
+    # mark structures as not built (newly parsed text)
+    $self->{built} = 0;
+    
     return $self;
 }
@@ -42,5 +49,6 @@
 sub build {
     my ($self, %options) = @_;
-    
+
+    # save the build options so they can be used from write_module
     $self->{build_options} = { %options };
     
@@ -87,4 +95,5 @@
     }
 
+    # TODO: configurable threshold for this
     foreach (@{ $self->{form_spec}{fields} }) {
         $$_{ulist} = 1 if defined $$_{options} and @{ $$_{options} } >= 3;
@@ -114,4 +123,7 @@
     $self->{form}->field(%{ $_ }) foreach @{ $self->{form_spec}{fields} };
     
+    # mark structures as built
+    $self->{built} = 1;
+    
     return $self;
 }
@@ -119,4 +131,9 @@
 sub write {
     my ($self, $outfile) = @_;
+    
+    # automatically call build if needed to
+    # allow the new->parse->write shortcut
+    $self->build unless $self->{built};
+    
     if ($outfile) {
         open FORM, "> $outfile";
@@ -129,19 +146,32 @@
 
 sub write_module {
-    my ($self, $package) = @_;
-
-    use Data::Dumper;
+    my ($self, $package, $use_tidy) = @_;
+
+    croak 'Expecting a package name' unless $package;
+    
+    # automatically call build if needed to
+    # allow the new->parse->write shortcut
+    $self->build unless $self->{built};
+    
+    # conditionally use Data::Dumper
+    eval 'use Data::Dumper;';
+    die "Can't write module; need Data::Dumper. $@" if $@;
+    
     # don't dump $VARn names
     $Data::Dumper::Terse = 1;
     
-    my $title = $self->{form_spec}{title} || '';
-    my $author = $self->{form_spec}{author} || '';
+    my $title       = $self->{form_spec}{title} || '';
+    my $author      = $self->{form_spec}{author} || '';
     my $description = $self->{form_spec}{description} || '';
-    my $headings = Data::Dumper->Dump([$self->{form_spec}{headings}],['headings']);
-    my $fields = Data::Dumper->Dump([ [ map { $$_{name} } @{ $self->{form_spec}{fields} } ] ],['fields']);
-    
-    my $source = $self->{build_options}{form_only} ? $self->_form_template : $self->_template;
-    
-    my $options = keys %{ $self->{build_options} } > 0 ? Data::Dumper->Dump([$self->{build_options}],['*options']) : '';
+    
+    my $headings    = Data::Dumper->Dump([$self->{form_spec}{headings}],['headings']);
+    my $fields      = Data::Dumper->Dump([ [ map { $$_{name} } @{ $self->{form_spec}{fields} } ] ],['fields']);
+    
+    my %options = %{ $self->{build_options} };
+    my $source = $options{form_only} ? $self->_form_template : $self->_template;
+    
+    delete $options{fomr_only};
+    
+    my $form_options = keys %options > 0 ? Data::Dumper->Dump([$self->{build_options}],['*options']) : '';
     
     my $field_setup = join(
@@ -179,5 +209,5 @@
             },
         },
-        $options
+        $form_options
     );
     
@@ -190,16 +220,29 @@
 1;
 END
+    
     my $outfile = (split(/::/, $package))[-1] . '.pm';
     
-    if ($outfile) {
+    if ($use_tidy) {
+        # clean up the generated code, if asked
+        eval 'use Perl::Tidy';
+        die "Can't tidy the code: $@" if $@;
+        Perl::Tidy::perltidy(source => \$module, destination => $outfile);
+    } else {
+        # otherwise, just print as is
         open FORM, "> $outfile";
         print FORM $module;
         close FORM;
-    } else {
-        print $module;
-    }
-}
-
-sub form { shift->{form} }
+    }
+}
+
+sub form {
+    my $self = shift;
+    
+    # automatically call build if needed to
+    # allow the new->parse->write shortcut
+    $self->build unless $self->{built};
+
+    return $self->{form};
+}
 
 sub _form_template {
@@ -245,5 +288,5 @@
 <hr />
 <div id="footer">
-  <p id="creator">Made with <a href="http://formbuilder.org/">CGI::FormBuilder</a> version <% $CGI::FormBuilder::VERSION %>.</p>
+  <p id="creator">Made with <a href="http://formbuilder.org/">CGI::FormBuilder</a> version <% CGI::FormBuilder->VERSION %>.</p>
 </div>
 </body>
@@ -267,13 +310,15 @@
 =head1 NAME
 
-Text::FormBuilder - Parser for a minilanguage describing web forms
+Text::FormBuilder - Parser for a minilanguage for generating web forms
 
 =head1 SYNOPSIS
 
+    use Text::FormBuilder;
+    
     my $parser = Text::FormBuilder->new;
     $parser->parse($src_file);
     
-    # returns a new CGI::FormBuilder object with the fields
-    # from the input form spec
+    # returns a new CGI::FormBuilder object with
+    # the fields from the input form spec
     my $form = $parser->form;
 
@@ -287,7 +332,11 @@
     
     # or as a class method
-    my $parser = Txt::FormBuilder->parse($src_file);
+    my $parser = Text::FormBuilder->parse($src_file);
 
 =head2 parse_text
+
+    $parser->parse_text($src);
+
+Parse the given C<$src> text. Returns the parse object.
 
 =head2 build
@@ -310,9 +359,20 @@
 defaults that this module uses.
 
+The C<form>, C<write>, and C<write_module> methods will all call
+C<build> with no options for you if you do not do so explicitly.
+This allows you to say things like this:
+
+    my $form = Text::FormBuilder->new->parse('formspec.txt')->form;
+
+However, if you need to specify options to C<build>, you must call it
+explictly after C<parse>.
+
 =head2 form
 
     my $form = $parser->form;
 
-Returns the L<CGI::FormBuilder> object.
+Returns the L<CGI::FormBuilder> object. Remember that you can modify
+this object directly, in order to (for example) dynamically populate
+dropdown lists or change input types at runtime.
 
 =head2 write
@@ -327,21 +387,27 @@
 =head2 write_module
 
-    $parser->write_module($package);
+    $parser->write_module($package, $use_tidy);
 
 Takes a package name, and writes out a new module that can be used by your
 CGI script to render the form. This way, you only need CGI::FormBuilder on
 your server, and you don't have to parse the form spec each time you want 
-to display your form.
+to display your form. The generated module has one function (not exported)
+called C<get_form>, that takes a CGI object as its only argument, and returns
+a CGI::FormBuilder object.
+
+First, you parse the formspec and write the module, which you can do as a one-liner:
+
+    $ perl -MText::FormBuilder -e"Text::FormBuilder->parse('formspec.txt')->write_module('MyForm')"
+
+And then, in your CGI script, use the new module:
 
     #!/usr/bin/perl -w
     use strict;
     
-    # your CGI script
-    
     use CGI;
     use MyForm;
     
     my $q = CGI->new;
-    my $form = MyForm::form($q);
+    my $form = MyForm::get_form($q);
     
     # do the standard CGI::FormBuilder stuff
@@ -352,9 +418,11 @@
         print $form->render;
     }
-    
+
+If you pass a true value as the second argument to C<write_module>, the parser
+will run L<Perl::Tidy> on the generated code before writing the module file.
 
 =head2 dump
 
-Uses L<YAML> to print out a human-readable representaiton of the parsed
+Uses L<YAML> to print out a human-readable representation of the parsed
 form spec.
 
@@ -367,5 +435,10 @@
     !author ...
     
+    !description {
+        ...
+    }
+    
     !pattern name /regular expression/
+    
     !list name {
         option1[display string],
@@ -394,4 +467,6 @@
 =item C<!author>
 
+=item C<!description>
+
 =item C<!head>
 
@@ -417,7 +492,9 @@
 =head1 TODO
 
-=head2 Langauge
-
-Directive for a descriptive or explanatory paragraph about the form
+C<!include> directive to include external formspec files
+
+Field groups all on one line in the generated form
+
+Tests!
 
 =head1 SEE ALSO
@@ -425,3 +502,14 @@
 L<CGI::FormBuilder>
 
+=head1 AUTHOR
+
+Peter Eichman <peichman@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright E<copy>2004 by Peter Eichman.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
 =cut
Index: trunk/lib/Text/FormBuilder/grammar
===================================================================
--- trunk/lib/Text/FormBuilder/grammar	(revision 15)
+++ trunk/lib/Text/FormBuilder/grammar	(revision 16)
@@ -14,6 +14,6 @@
     }
 
-list_def: '!list' list_name (static_list | dynamic_list)
-    { $lists{$item{list_name}} = [ @options ]; @options = () }
+list_def: '!list' var_name (static_list | dynamic_list)
+    { $lists{$item{var_name}} = [ @options ]; @options = () }
 
 static_list: '{' option(s /,\s*/) /,?/ '}'
@@ -29,6 +29,4 @@
     }
 
-list_name: /[A-Z_]+/
-
 description_def: '!description' <perl_codeblock>
     { warn "[Text::FormBuilder] Description redefined at input text line $thisline\n" if defined $description;
@@ -38,5 +36,5 @@
     }
 
-line: <skip:'[ \t]*'> ( title | author | pattern_def | heading | field | comment | blank ) "\n"
+line: <skip:'[ \t]*'> ( title | author | pattern_def | heading | unknown_directive | field | comment | blank ) "\n"
 
 title: '!title' /.*/
@@ -47,8 +45,7 @@
     { $author = $item[2] }
 
-pattern_def: '!pattern' pattern_name pattern
-    { $patterns{$item{pattern_name}} = $item{pattern} }
+pattern_def: '!pattern' var_name pattern
+    { $patterns{$item{var_name}} = $item{pattern} }
 
-pattern_name: /[A-Z_]+/
 pattern: /.*/
 
@@ -88,4 +85,6 @@
 name: identifier
 
+var_name: /[A-Z_]+/
+
 field_size: '[' ( row_col | size ) ']'
 
@@ -123,2 +122,6 @@
 
 identifier: /\w+/
+
+# skip unknown directives with a warning
+unknown_directive: /\!\S*/ /.*/
+    { warn "[Text::Formbuilder] Skipping unknown directive '$item[1]' at input text line $thisline\n"; }
