source: text-formbuilder/trunk/lib/Text/FormBuilder.pm @ 17

Last change on this file since 17 was 16, checked in by peter, 20 years ago

catch, warn about, and skip unknown directives (lines beginning with !)
optionally use PerlTidy to clean up generated module code
updated version number and docs
write, write_module, and form automatically call build if necessary

File size: 12.8 KB
RevLine 
[1]1package Text::FormBuilder;
2
3use strict;
4use warnings;
5
[16]6our $VERSION = '0.04';
[1]7
[16]8use Carp;
[1]9use Text::FormBuilder::Parser;
10use CGI::FormBuilder;
11
12sub new {
13    my $invocant = shift;
14    my $class = ref $invocant || $invocant;
15    my $self = {
16        parser => Text::FormBuilder::Parser->new,
17    };
18    return bless $self, $class;
19}
20
21sub parse {
22    my ($self, $filename) = @_;
23   
24    # so it can be called as a class method
25    $self = $self->new unless ref $self;
26   
27    local $/ = undef;
28    open SRC, "< $filename";
29    my $src = <SRC>;
30    close SRC;
31   
32    return $self->parse_text($src);
33}
34
35sub parse_text {
36    my ($self, $src) = @_;
[16]37   
[1]38    # so it can be called as a class method
39    $self = $self->new unless ref $self;
[16]40   
[1]41    $self->{form_spec} = $self->{parser}->form_spec($src);
[16]42   
43    # mark structures as not built (newly parsed text)
44    $self->{built} = 0;
45   
[1]46    return $self;
47}
48
49sub build {
50    my ($self, %options) = @_;
[16]51
52    # save the build options so they can be used from write_module
[12]53    $self->{build_options} = { %options };
54   
55    # our custom %options:
56    # form_only: use only the form part of the template
57    my $form_only = $options{form_only};
58    delete $options{form_only};
59   
[1]60    # substitute in custom pattern definitions for field validation
61    if (my %patterns = %{ $self->{form_spec}{patterns} }) {
62        foreach (@{ $self->{form_spec}{fields} }) {
63            if ($$_{validate} and exists $patterns{$$_{validate}}) {
64                $$_{validate} = $patterns{$$_{validate}};
65            }
66        }
67    }
68   
[12]69    # remove extraneous undefined values
70    for my $field (@{ $self->{form_spec}{fields} }) {
71        defined $$field{$_} or delete $$field{$_} foreach keys %{ $field };
72    }
73   
[1]74    # so we don't get all fields required
75    foreach (@{ $self->{form_spec}{fields} }) {
76        delete $$_{validate} unless $$_{validate};
77    }
78   
79    # substitute in list names
80    if (my %lists = %{ $self->{form_spec}{lists} }) {
81        foreach (@{ $self->{form_spec}{fields} }) {
82            next unless $$_{list};
83           
84            $$_{list} =~ s/^\@//;   # strip leading @ from list var name
85           
86            # a hack so we don't get screwy reference errors
87            if (exists $lists{$$_{list}}) {
88                my @list;
89                push @list, { %$_ } foreach @{ $lists{$$_{list}} };
90                $$_{options} = \@list;
91            }
92        } continue {
93            delete $$_{list};
94        }
95    }
96
[16]97    # TODO: configurable threshold for this
[14]98    foreach (@{ $self->{form_spec}{fields} }) {
99        $$_{ulist} = 1 if defined $$_{options} and @{ $$_{options} } >= 3;
100    }
[1]101   
102    $self->{form} = CGI::FormBuilder->new(
103        method => 'GET',
104        javascript => 0,
105        keepextras => 1,
106        title => $self->{form_spec}{title},
107        fields => [ map { $$_{name} } @{ $self->{form_spec}{fields} } ],
108        template => {
109            type => 'Text',
110            engine => {
111                TYPE       => 'STRING',
[12]112                SOURCE     => $form_only ? $self->_form_template : $self->_template,
[11]113                DELIMITERS => [ qw(<% %>) ],
[1]114            },
115            data => {
[14]116                headings    => $self->{form_spec}{headings},
117                author      => $self->{form_spec}{author},
118                description => $self->{form_spec}{description},
[1]119            },
120        },
121        %options,
122    );
123    $self->{form}->field(%{ $_ }) foreach @{ $self->{form_spec}{fields} };
124   
[16]125    # mark structures as built
126    $self->{built} = 1;
127   
[1]128    return $self;
129}
130
131sub write {
132    my ($self, $outfile) = @_;
[16]133   
134    # automatically call build if needed to
135    # allow the new->parse->write shortcut
136    $self->build unless $self->{built};
137   
[1]138    if ($outfile) {
139        open FORM, "> $outfile";
140        print FORM $self->form->render;
141        close FORM;
142    } else {
143        print $self->form->render;
144    }
145}
146
[12]147sub write_module {
[16]148    my ($self, $package, $use_tidy) = @_;
[12]149
[16]150    croak 'Expecting a package name' unless $package;
151   
152    # automatically call build if needed to
153    # allow the new->parse->write shortcut
154    $self->build unless $self->{built};
155   
156    # conditionally use Data::Dumper
157    eval 'use Data::Dumper;';
158    die "Can't write module; need Data::Dumper. $@" if $@;
159   
[12]160    # don't dump $VARn names
161    $Data::Dumper::Terse = 1;
162   
[16]163    my $title       = $self->{form_spec}{title} || '';
164    my $author      = $self->{form_spec}{author} || '';
[14]165    my $description = $self->{form_spec}{description} || '';
[12]166   
[16]167    my $headings    = Data::Dumper->Dump([$self->{form_spec}{headings}],['headings']);
168    my $fields      = Data::Dumper->Dump([ [ map { $$_{name} } @{ $self->{form_spec}{fields} } ] ],['fields']);
[12]169   
[16]170    my %options = %{ $self->{build_options} };
171    my $source = $options{form_only} ? $self->_form_template : $self->_template;
[12]172   
[16]173    delete $options{fomr_only};
174   
175    my $form_options = keys %options > 0 ? Data::Dumper->Dump([$self->{build_options}],['*options']) : '';
176   
[12]177    my $field_setup = join(
178        "\n", 
179        map { '$cgi_form->field' . Data::Dumper->Dump([$_],['*field']) . ';' } @{ $self->{form_spec}{fields} }
180    );
181   
182    my $module = <<END;
183package $package;
184use strict;
185use warnings;
186
187use CGI::FormBuilder;
188
[15]189sub get_form {
[12]190    my \$cgi = shift;
191    my \$cgi_form = CGI::FormBuilder->new(
192        method => 'GET',
193        params => \$cgi,
194        javascript => 0,
195        keepextras => 1,
196        title => q[$title],
197        fields => $fields,
198        template => {
199            type => 'Text',
200            engine => {
201                TYPE       => 'STRING',
202                SOURCE     => q[$source],
203                DELIMITERS => [ qw(<% %>) ],
204            },
205            data => {
206                headings => $headings,
207                author   => q[$author],
[14]208                description => q[$description],
[12]209            },
210        },
[16]211        $form_options
[12]212    );
213   
214    $field_setup
215   
216    return \$cgi_form;
217}
218
219# module return
2201;
221END
[16]222   
[12]223    my $outfile = (split(/::/, $package))[-1] . '.pm';
224   
[16]225    if ($use_tidy) {
226        # clean up the generated code, if asked
227        eval 'use Perl::Tidy';
228        die "Can't tidy the code: $@" if $@;
229        Perl::Tidy::perltidy(source => \$module, destination => $outfile);
230    } else {
231        # otherwise, just print as is
[12]232        open FORM, "> $outfile";
233        print FORM $module;
234        close FORM;
235    }
236}
237
[16]238sub form {
239    my $self = shift;
240   
241    # automatically call build if needed to
242    # allow the new->parse->write shortcut
243    $self->build unless $self->{built};
[1]244
[16]245    return $self->{form};
246}
247
[12]248sub _form_template {
[15]249q[<% $description ? qq[<p id="description">$description</p>] : '' %>
250<% (grep { $_->{required} } @fields) ? qq[<p id="instructions">(Required fields are marked in <strong>bold</strong>.)</p>] : '' %>
[12]251<% $start %>
252<table>
253<% my $i; foreach(@fields) {
254    $OUT .= qq[  <tr><th class="sectionhead" colspan="2"><h2>$headings[$i]</h2></th></tr>\n] if $headings[$i];
[15]255    $OUT .= $$_{invalid} ? qq[  <tr class="invalid">] : qq[  <tr>];
[12]256    $OUT .= '<th class="label">' . ($$_{required} ? qq[<strong class="required">$$_{label}:</strong>] : "$$_{label}:") . '</th>';
[15]257    if ($$_{invalid}) {
258        $OUT .= qq[<td>$$_{field} $$_{comment} Missing or invalid value.</td></tr>\n];
259    } else {
260        $OUT .= qq[<td>$$_{field} $$_{comment}</td></tr>\n];
261    }
[12]262    $i++;
263} %>
264  <tr><th></th><td style="padding-top: 1em;"><% $submit %></td></tr>
265</table>
266<% $end %>
267];
268}
269
[1]270sub _template {
[12]271    my $self = shift;
272q[<html>
[1]273<head>
274  <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title>
275  <style type="text/css">
276    #author, #footer { font-style: italic; }
[11]277    th { text-align: left; }
278    th h2 { padding: .125em .5em; background: #eee; }
279    th.label { font-weight: normal; text-align: right; vertical-align: top; }
[14]280    td ul { list-style: none; padding-left: 0; margin-left: 0; }
[1]281  </style>
282</head>
283<body>
284
285<h1><% $title %></h1>
286<% $author ? qq[<p id="author">Created by $author</p>] : '' %>
[12]287] . $self->_form_template . q[
[1]288<hr />
289<div id="footer">
[16]290  <p id="creator">Made with <a href="http://formbuilder.org/">CGI::FormBuilder</a> version <% CGI::FormBuilder->VERSION %>.</p>
[1]291</div>
292</body>
293</html>
294];
295}
296
[7]297sub dump { 
298    eval "use YAML;";
299    unless ($@) {
300        print YAML::Dump(shift->{form_spec});
301    } else {
302        warn "Can't dump form spec structure: $@";
303    }
304}
[1]305
306
307# module return
3081;
309
310=head1 NAME
311
[16]312Text::FormBuilder - Parser for a minilanguage for generating web forms
[1]313
314=head1 SYNOPSIS
315
[16]316    use Text::FormBuilder;
317   
[1]318    my $parser = Text::FormBuilder->new;
319    $parser->parse($src_file);
320   
[16]321    # returns a new CGI::FormBuilder object with
322    # the fields from the input form spec
[7]323    my $form = $parser->form;
[1]324
325=head1 DESCRIPTION
326
327=head2 new
328
329=head2 parse
330
[7]331    $parser->parse($src_file);
332   
333    # or as a class method
[16]334    my $parser = Text::FormBuilder->parse($src_file);
[7]335
336=head2 parse_text
337
[16]338    $parser->parse_text($src);
339
340Parse the given C<$src> text. Returns the parse object.
341
[1]342=head2 build
343
[12]344    $parser->build(%options);
[7]345
[12]346Builds the CGI::FormBuilder object. Options directly used by C<build> are:
347
348=over
349
350=item form_only
351
352Only uses the form portion of the template, and omits the surrounding html,
353title, author, and the standard footer.
354
355=back
356
357All other options given to C<build> are passed on verbatim to the
358L<CGI::FormBuilder> constructor. Any options given here override the
359defaults that this module uses.
360
[16]361The C<form>, C<write>, and C<write_module> methods will all call
362C<build> with no options for you if you do not do so explicitly.
363This allows you to say things like this:
364
365    my $form = Text::FormBuilder->new->parse('formspec.txt')->form;
366
367However, if you need to specify options to C<build>, you must call it
368explictly after C<parse>.
369
[7]370=head2 form
371
372    my $form = $parser->form;
373
[16]374Returns the L<CGI::FormBuilder> object. Remember that you can modify
375this object directly, in order to (for example) dynamically populate
376dropdown lists or change input types at runtime.
[7]377
[1]378=head2 write
379
[7]380    $parser->write($out_file);
381    # or just print to STDOUT
382    $parser->write;
383
384Calls C<render> on the FormBuilder form, and either writes the resulting HTML
385to a file, or to STDOUT if no filename is given.
386
[12]387=head2 write_module
388
[16]389    $parser->write_module($package, $use_tidy);
[12]390
391Takes a package name, and writes out a new module that can be used by your
392CGI script to render the form. This way, you only need CGI::FormBuilder on
393your server, and you don't have to parse the form spec each time you want
[16]394to display your form. The generated module has one function (not exported)
395called C<get_form>, that takes a CGI object as its only argument, and returns
396a CGI::FormBuilder object.
[12]397
[16]398First, you parse the formspec and write the module, which you can do as a one-liner:
399
400    $ perl -MText::FormBuilder -e"Text::FormBuilder->parse('formspec.txt')->write_module('MyForm')"
401
402And then, in your CGI script, use the new module:
403
[12]404    #!/usr/bin/perl -w
405    use strict;
406   
407    use CGI;
408    use MyForm;
409   
410    my $q = CGI->new;
[16]411    my $form = MyForm::get_form($q);
[12]412   
413    # do the standard CGI::FormBuilder stuff
414    if ($form->submitted && $form->validate) {
415        # process results
416    } else {
417        print $q->header;
418        print $form->render;
419    }
420
[16]421If you pass a true value as the second argument to C<write_module>, the parser
422will run L<Perl::Tidy> on the generated code before writing the module file.
423
[7]424=head2 dump
425
[16]426Uses L<YAML> to print out a human-readable representation of the parsed
[7]427form spec.
428
[1]429=head1 LANGUAGE
430
[12]431    field_name[size]|descriptive label[hint]:type=default{option1[display string],option2[display string],...}//validate
[1]432   
433    !title ...
434   
[12]435    !author ...
436   
[16]437    !description {
438        ...
439    }
440   
[1]441    !pattern name /regular expression/
[16]442   
[1]443    !list name {
[7]444        option1[display string],
445        option2[display string],
[1]446        ...
447    }
[12]448   
449    !list name &{ CODE }
450   
451    !head ...
[1]452
453=head2 Directives
454
455=over
456
457=item C<!pattern>
458
[12]459Defines a validation pattern.
460
[1]461=item C<!list>
462
[12]463Defines a list for use in a C<radio>, C<checkbox>, or C<select> field.
464
[1]465=item C<!title>
466
[7]467=item C<!author>
468
[16]469=item C<!description>
470
[12]471=item C<!head>
472
473Inserts a heading between two fields. There can only be one heading between
474any two fields; the parser will warn you if you try to put two headings right
475next to each other.
476
[1]477=back
478
479=head2 Fields
480
481Form fields are each described on a single line.
482
483If you have a list of options that is too long to fit comfortably on one line,
484consider using the C<!list> directive.
485
486=head2 Comments
487
488    # comment ...
489
490Any line beginning with a C<#> is considered a comment.
491
[7]492=head1 TODO
493
[16]494C<!include> directive to include external formspec files
[7]495
[16]496Field groups all on one line in the generated form
[7]497
[16]498Tests!
499
[1]500=head1 SEE ALSO
501
502L<CGI::FormBuilder>
503
[16]504=head1 AUTHOR
505
506Peter Eichman <peichman@cpan.org>
507
508=head1 COPYRIGHT AND LICENSE
509
510Copyright E<copy>2004 by Peter Eichman.
511
512This program is free software; you can redistribute it and/or
513modify it under the same terms as Perl itself.
514
[1]515=cut
Note: See TracBrowser for help on using the repository browser.