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

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

added !group and !field directive to the language to allow for horizontal groups of input fields
moving to a new representation of the parsed formspec based on lines as opposed to fields

File size: 18.8 KB
RevLine 
[1]1package Text::FormBuilder;
2
3use strict;
4use warnings;
5
[19]6use vars qw($VERSION);
[1]7
[21]8$VERSION = '0.06';
[19]9
[16]10use Carp;
[1]11use Text::FormBuilder::Parser;
12use CGI::FormBuilder;
13
14sub new {
15    my $invocant = shift;
16    my $class = ref $invocant || $invocant;
17    my $self = {
18        parser => Text::FormBuilder::Parser->new,
19    };
20    return bless $self, $class;
21}
22
23sub parse {
[19]24    my ($self, $source) = @_;
25    if (ref $source && ref $source eq 'SCALAR') {
26        $self->parse_text($$source);
27    } else {
28        $self->parse_file($source);
29    }
30}
31
32sub parse_file {
[1]33    my ($self, $filename) = @_;
34   
35    # so it can be called as a class method
36    $self = $self->new unless ref $self;
37   
38    local $/ = undef;
39    open SRC, "< $filename";
40    my $src = <SRC>;
41    close SRC;
42   
43    return $self->parse_text($src);
44}
45
46sub parse_text {
47    my ($self, $src) = @_;
[16]48   
[1]49    # so it can be called as a class method
50    $self = $self->new unless ref $self;
[16]51   
[1]52    $self->{form_spec} = $self->{parser}->form_spec($src);
[16]53   
54    # mark structures as not built (newly parsed text)
55    $self->{built} = 0;
56   
[1]57    return $self;
58}
59
60sub build {
61    my ($self, %options) = @_;
[16]62
63    # save the build options so they can be used from write_module
[12]64    $self->{build_options} = { %options };
65   
66    # our custom %options:
67    # form_only: use only the form part of the template
68    my $form_only = $options{form_only};
69    delete $options{form_only};
70   
[1]71    # substitute in custom pattern definitions for field validation
[19]72    if (my %patterns = %{ $self->{form_spec}{patterns} || {} }) {
[1]73        foreach (@{ $self->{form_spec}{fields} }) {
74            if ($$_{validate} and exists $patterns{$$_{validate}}) {
75                $$_{validate} = $patterns{$$_{validate}};
76            }
77        }
78    }
79   
[12]80    # remove extraneous undefined values
81    for my $field (@{ $self->{form_spec}{fields} }) {
82        defined $$field{$_} or delete $$field{$_} foreach keys %{ $field };
83    }
84   
[1]85    # so we don't get all fields required
86    foreach (@{ $self->{form_spec}{fields} }) {
87        delete $$_{validate} unless $$_{validate};
88    }
[21]89
90    # expand groups
91    my %groups = %{ $self->{form_spec}{groups} };
92    foreach (grep { $$_[0] eq 'group' } @{ $self->{form_spec}{lines} }) {
93        $$_[1]{group} =~ s/^\%//;       # strip leading % from group var name
94       
95        if (exists $groups{$$_[1]{group}}) {
96            my @fields; # fields in the group
97            push @fields, { %$_ } foreach @{ $groups{$$_[1]{group}} };
98            for my $field (@fields) {
99                $$field{label} ||= ucfirst $$field{name};
100                $$field{name} = "$$_[1]{name}_$$field{name}";               
101            }
102            $_ = [ 'group', { label => $$_[1]{label} || ucfirst(join(' ',split('_',$$_[1]{name}))), group => \@fields } ];
103        }
104    }
[1]105   
[21]106    $self->{form_spec}{fields} = [];
107    for my $line (@{ $self->{form_spec}{lines} }) {
108        if ($$line[0] eq 'group') {
109            push @{ $self->{form_spec}{fields} }, $_ foreach @{ $$line[1]{group} };
110        } elsif ($$line[0] eq 'field') {
111            push @{ $self->{form_spec}{fields} }, $$line[1];
112        }
113    }
114   
115   
[1]116    # substitute in list names
[19]117    if (my %lists = %{ $self->{form_spec}{lists} || {} }) {
[1]118        foreach (@{ $self->{form_spec}{fields} }) {
119            next unless $$_{list};
120           
121            $$_{list} =~ s/^\@//;   # strip leading @ from list var name
122           
123            # a hack so we don't get screwy reference errors
124            if (exists $lists{$$_{list}}) {
125                my @list;
126                push @list, { %$_ } foreach @{ $lists{$$_{list}} };
127                $$_{options} = \@list;
128            }
129        } continue {
130            delete $$_{list};
131        }
132    }
[21]133   
134   
135   
[1]136
[21]137   
138    # TODO: use lines instead of fields
139    # TODO: change template to do groups
140   
[16]141    # TODO: configurable threshold for this
[14]142    foreach (@{ $self->{form_spec}{fields} }) {
143        $$_{ulist} = 1 if defined $$_{options} and @{ $$_{options} } >= 3;
144    }
[1]145   
146    $self->{form} = CGI::FormBuilder->new(
147        method => 'GET',
148        javascript => 0,
149        keepextras => 1,
150        title => $self->{form_spec}{title},
[21]151        #fields => [ map { $$_{name} } @{ $self->{form_spec}{fields} } ],
[1]152        template => {
153            type => 'Text',
154            engine => {
155                TYPE       => 'STRING',
[12]156                SOURCE     => $form_only ? $self->_form_template : $self->_template,
[11]157                DELIMITERS => [ qw(<% %>) ],
[1]158            },
159            data => {
[21]160                lines       => $self->{form_spec}{lines},
[14]161                headings    => $self->{form_spec}{headings},
162                author      => $self->{form_spec}{author},
163                description => $self->{form_spec}{description},
[1]164            },
165        },
166        %options,
167    );
168    $self->{form}->field(%{ $_ }) foreach @{ $self->{form_spec}{fields} };
169   
[16]170    # mark structures as built
171    $self->{built} = 1;
172   
[21]173    # TEMP: dump @lines structure
174    use YAML;
175    warn YAML::Dump($self->{form_spec}->{lines}), "\n";
176   
[1]177    return $self;
178}
179
180sub write {
181    my ($self, $outfile) = @_;
[16]182   
183    # automatically call build if needed to
184    # allow the new->parse->write shortcut
185    $self->build unless $self->{built};
186   
[1]187    if ($outfile) {
188        open FORM, "> $outfile";
189        print FORM $self->form->render;
190        close FORM;
191    } else {
192        print $self->form->render;
193    }
194}
195
[12]196sub write_module {
[16]197    my ($self, $package, $use_tidy) = @_;
[12]198
[16]199    croak 'Expecting a package name' unless $package;
200   
201    # automatically call build if needed to
202    # allow the new->parse->write shortcut
203    $self->build unless $self->{built};
204   
205    # conditionally use Data::Dumper
206    eval 'use Data::Dumper;';
207    die "Can't write module; need Data::Dumper. $@" if $@;
208   
[12]209    # don't dump $VARn names
210    $Data::Dumper::Terse = 1;
211   
[16]212    my $title       = $self->{form_spec}{title} || '';
213    my $author      = $self->{form_spec}{author} || '';
[14]214    my $description = $self->{form_spec}{description} || '';
[12]215   
[16]216    my $headings    = Data::Dumper->Dump([$self->{form_spec}{headings}],['headings']);
217    my $fields      = Data::Dumper->Dump([ [ map { $$_{name} } @{ $self->{form_spec}{fields} } ] ],['fields']);
[12]218   
[16]219    my %options = %{ $self->{build_options} };
220    my $source = $options{form_only} ? $self->_form_template : $self->_template;
[12]221   
[21]222    delete $options{form_only};
[16]223   
224    my $form_options = keys %options > 0 ? Data::Dumper->Dump([$self->{build_options}],['*options']) : '';
225   
[12]226    my $field_setup = join(
227        "\n", 
228        map { '$cgi_form->field' . Data::Dumper->Dump([$_],['*field']) . ';' } @{ $self->{form_spec}{fields} }
229    );
230   
231    my $module = <<END;
232package $package;
233use strict;
234use warnings;
235
236use CGI::FormBuilder;
237
[15]238sub get_form {
[12]239    my \$cgi = shift;
240    my \$cgi_form = CGI::FormBuilder->new(
241        method => 'GET',
242        params => \$cgi,
243        javascript => 0,
244        keepextras => 1,
245        title => q[$title],
246        fields => $fields,
247        template => {
248            type => 'Text',
249            engine => {
250                TYPE       => 'STRING',
251                SOURCE     => q[$source],
252                DELIMITERS => [ qw(<% %>) ],
253            },
254            data => {
255                headings => $headings,
256                author   => q[$author],
[14]257                description => q[$description],
[12]258            },
259        },
[16]260        $form_options
[12]261    );
262   
263    $field_setup
264   
265    return \$cgi_form;
266}
267
268# module return
2691;
270END
[16]271   
[12]272    my $outfile = (split(/::/, $package))[-1] . '.pm';
273   
[16]274    if ($use_tidy) {
275        # clean up the generated code, if asked
276        eval 'use Perl::Tidy';
277        die "Can't tidy the code: $@" if $@;
278        Perl::Tidy::perltidy(source => \$module, destination => $outfile);
279    } else {
280        # otherwise, just print as is
[12]281        open FORM, "> $outfile";
282        print FORM $module;
283        close FORM;
284    }
285}
286
[16]287sub form {
288    my $self = shift;
289   
290    # automatically call build if needed to
291    # allow the new->parse->write shortcut
292    $self->build unless $self->{built};
[1]293
[16]294    return $self->{form};
295}
296
[12]297sub _form_template {
[15]298q[<% $description ? qq[<p id="description">$description</p>] : '' %>
299<% (grep { $_->{required} } @fields) ? qq[<p id="instructions">(Required fields are marked in <strong>bold</strong>.)</p>] : '' %>
[12]300<% $start %>
301<table>
[21]302
303<% for my $line (@lines) {
304
305    if ($$line[0] eq 'head') {
306        $OUT .= qq[  <tr><th class="sectionhead" colspan="2"><h2>$$line[1]</h2></th></tr>\n]
307    } elsif ($$line[0] eq 'field') {
308        #TODO: we only need the field names, not the full field spec in the lines strucutre
309        local $_ = $field{$$line[1]{name}};
310        $OUT .= $$_{invalid} ? qq[  <tr class="invalid">] : qq[  <tr>];
311        $OUT .= '<th class="label">' . ($$_{required} ? qq[<strong class="required">$$_{label}:</strong>] : "$$_{label}:") . '</th>';
312        if ($$_{invalid}) {
313            $OUT .= qq[<td>$$_{field} $$_{comment} Missing or invalid value.</td></tr>\n];
314        } else {
315            $OUT .= qq[<td>$$_{field} $$_{comment}</td></tr>\n];
316        }
317    } elsif ($$line[0] eq 'group') {
318        my @field_names = map { $$_{name} } @{ $$line[1]{group} };
319        my @group_fields = map { $field{$_} } @field_names;
320        $OUT .= (grep { $$_{invalid} } @group_fields) ? qq[  <tr class="invalid">\n] : qq[  <tr>\n];
321       
322       
323        #TODO: validated but not required fields
324        # in a form spec: //EMAIL?
325       
326        #TODO: this doesn't seem to be working; all groups are getting marked as required       
327        $OUT .= '    <th class="label">';
328        $OUT .= (grep { $$_{required} } @group_fields) ? qq[<strong class="required">$$line[1]{label}:</strong>] : "$$line[1]{label}:";
329        $OUT .= qq[</th>\n];
330       
331        $OUT .= qq[    <td>];
332        $OUT .= join(' ', map { qq[<small class="sublabel">$$_{label}</small> $$_{field} $$_{comment}] } @group_fields);
333        $OUT .= qq[    </td>\n];
334        $OUT .= qq[  </tr>\n];
335    }   
336   
337
[12]338} %>
339  <tr><th></th><td style="padding-top: 1em;"><% $submit %></td></tr>
340</table>
341<% $end %>
342];
343}
344
[1]345sub _template {
[12]346    my $self = shift;
347q[<html>
[1]348<head>
349  <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title>
350  <style type="text/css">
351    #author, #footer { font-style: italic; }
[11]352    th { text-align: left; }
353    th h2 { padding: .125em .5em; background: #eee; }
354    th.label { font-weight: normal; text-align: right; vertical-align: top; }
[14]355    td ul { list-style: none; padding-left: 0; margin-left: 0; }
[21]356    .sublabel { color: #999; }
[1]357  </style>
358</head>
359<body>
360
361<h1><% $title %></h1>
362<% $author ? qq[<p id="author">Created by $author</p>] : '' %>
[12]363] . $self->_form_template . q[
[1]364<hr />
365<div id="footer">
[16]366  <p id="creator">Made with <a href="http://formbuilder.org/">CGI::FormBuilder</a> version <% CGI::FormBuilder->VERSION %>.</p>
[1]367</div>
368</body>
369</html>
370];
371}
372
[7]373sub dump { 
374    eval "use YAML;";
375    unless ($@) {
376        print YAML::Dump(shift->{form_spec});
377    } else {
378        warn "Can't dump form spec structure: $@";
379    }
380}
[1]381
382
383# module return
3841;
385
386=head1 NAME
387
[21]388Text::FormBuilder - Create CGI::FormBuilder objects from simple text descriptions
[1]389
390=head1 SYNOPSIS
391
[16]392    use Text::FormBuilder;
393   
[1]394    my $parser = Text::FormBuilder->new;
395    $parser->parse($src_file);
396   
[16]397    # returns a new CGI::FormBuilder object with
398    # the fields from the input form spec
[7]399    my $form = $parser->form;
[19]400   
401    # write a My::Form module to Form.pm
402    $parser->write_module('My::Form');
[1]403
404=head1 DESCRIPTION
405
406=head2 new
407
408=head2 parse
409
[19]410    # parse a file
411    $parser->parse($filename);
[7]412   
[19]413    # or pass a scalar ref for parse a literal string
414    $parser->parse(\$string);
415
416Parse the file or string. Returns the parser object.
417
418=head2 parse_file
419
420    $parser->parse_file($src_file);
421   
[7]422    # or as a class method
[16]423    my $parser = Text::FormBuilder->parse($src_file);
[7]424
425=head2 parse_text
426
[16]427    $parser->parse_text($src);
428
[19]429Parse the given C<$src> text. Returns the parser object.
[16]430
[1]431=head2 build
432
[12]433    $parser->build(%options);
[7]434
[12]435Builds the CGI::FormBuilder object. Options directly used by C<build> are:
436
437=over
438
[19]439=item C<form_only>
[12]440
441Only uses the form portion of the template, and omits the surrounding html,
[19]442title, author, and the standard footer. This does, however, include the
443description as specified with the C<!description> directive.
[12]444
445=back
446
447All other options given to C<build> are passed on verbatim to the
448L<CGI::FormBuilder> constructor. Any options given here override the
449defaults that this module uses.
450
[16]451The C<form>, C<write>, and C<write_module> methods will all call
452C<build> with no options for you if you do not do so explicitly.
453This allows you to say things like this:
454
455    my $form = Text::FormBuilder->new->parse('formspec.txt')->form;
456
457However, if you need to specify options to C<build>, you must call it
458explictly after C<parse>.
459
[7]460=head2 form
461
462    my $form = $parser->form;
463
[16]464Returns the L<CGI::FormBuilder> object. Remember that you can modify
465this object directly, in order to (for example) dynamically populate
466dropdown lists or change input types at runtime.
[7]467
[1]468=head2 write
469
[7]470    $parser->write($out_file);
471    # or just print to STDOUT
472    $parser->write;
473
474Calls C<render> on the FormBuilder form, and either writes the resulting HTML
475to a file, or to STDOUT if no filename is given.
476
[12]477=head2 write_module
478
[16]479    $parser->write_module($package, $use_tidy);
[12]480
481Takes a package name, and writes out a new module that can be used by your
482CGI script to render the form. This way, you only need CGI::FormBuilder on
483your server, and you don't have to parse the form spec each time you want
[16]484to display your form. The generated module has one function (not exported)
485called C<get_form>, that takes a CGI object as its only argument, and returns
486a CGI::FormBuilder object.
[12]487
[16]488First, you parse the formspec and write the module, which you can do as a one-liner:
489
[19]490    $ perl -MText::FormBuilder -e"Text::FormBuilder->parse('formspec.txt')->write_module('My::Form')"
[16]491
492And then, in your CGI script, use the new module:
493
[12]494    #!/usr/bin/perl -w
495    use strict;
496   
497    use CGI;
[19]498    use My::Form;
[12]499   
500    my $q = CGI->new;
[19]501    my $form = My::Form::get_form($q);
[12]502   
503    # do the standard CGI::FormBuilder stuff
504    if ($form->submitted && $form->validate) {
505        # process results
506    } else {
507        print $q->header;
508        print $form->render;
509    }
510
[16]511If you pass a true value as the second argument to C<write_module>, the parser
512will run L<Perl::Tidy> on the generated code before writing the module file.
513
[19]514    # write tidier code
515    $parser->write_module('My::Form', 1);
516
[7]517=head2 dump
518
[16]519Uses L<YAML> to print out a human-readable representation of the parsed
[7]520form spec.
521
[1]522=head1 LANGUAGE
523
[19]524    field_name[size]|descriptive label[hint]:type=default{option1[display string],...}//validate
[1]525   
526    !title ...
527   
[12]528    !author ...
529   
[16]530    !description {
531        ...
532    }
533   
[1]534    !pattern name /regular expression/
[16]535   
[1]536    !list name {
[7]537        option1[display string],
538        option2[display string],
[1]539        ...
540    }
[12]541   
542    !list name &{ CODE }
543   
544    !head ...
[1]545
546=head2 Directives
547
548=over
549
550=item C<!pattern>
551
[12]552Defines a validation pattern.
553
[1]554=item C<!list>
555
[12]556Defines a list for use in a C<radio>, C<checkbox>, or C<select> field.
557
[1]558=item C<!title>
559
[7]560=item C<!author>
561
[16]562=item C<!description>
563
[19]564A brief description of the form. Suitable for special instructions on how to
565fill out the form.
566
[12]567=item C<!head>
568
569Inserts a heading between two fields. There can only be one heading between
570any two fields; the parser will warn you if you try to put two headings right
571next to each other.
572
[1]573=back
574
575=head2 Fields
576
[19]577Form fields are each described on a single line. The simplest field is just a
578name:
[1]579
[19]580    color
581
582This yields a form with one text input field of the default size named `color'.
583The label for this field as generated by CGI::FormBuilder would be ``Color''.
584To add a longer or more descriptive label, use:
585
586    color|Favorite color
587
588Field names cannot contain whitespace, but the descriptive label can.
589
590To use a different input type:
591
592    color|Favorite color:select{red,blue,green}
593
594Recognized input types are the same as those used by CGI::FormBuilder:
595
596    text        # the default
597    textarea
598    select
599    radio
600    checkbox
601    static
602
[21]603To change the size of the input field, add a bracketed subscript after the
604field name (but before the descriptive label):
[19]605
[21]606    # for a single line field, sets size="40"
607    title[40]:text
608   
609    # for a multiline field, sets rows="4" and cols="30"
610    description[4,30]:textarea
611
612For the input types that can have options (C<select>, C<radio>, and
613C<checkbox>), here's how you do it:
614
615    color|Favorite color:select{red,blue,green}
616
617Values are in a comma-separated list inside curly braces. Whitespace
618between values is irrelevant, although there cannot be any whitespace
619within a value.
620
[19]621To add more descriptive display text to a vlaue in a list, add a square-bracketed
622``subscript,'' as in:
623
624    ...:select{red[Scarlet],blue[Azure],green[Olive Drab]}
625
626As you can see, spaces I<are> allowed within the display text for a value.
627
[1]628If you have a list of options that is too long to fit comfortably on one line,
[19]629consider using the C<!list> directive:
[1]630
[19]631    !list MONTHS {
632        1[January],
633        2[February],
634        3[March],
635        # and so on...
636    }
637   
638    month:select@MONTHS
639
640There is another form of the C<!list> directive: the dynamic list:
641
642    !list RANDOM &{ map { rand } (0..5) }
643
644The code inside the C<&{ ... }> is C<eval>ed by C<build>, and the results
645are stuffed into the list. The C<eval>ed code can either return a simple
[21]646list, as the example does, or the fancier C<< ( { value1 => 'Description 1'},
647{ value2 => 'Description 2}, ... ) >> form.
[19]648
649B<NOTE:> This feature of the language may go away unless I find a compelling
650reason for it in the next few versions. What I really wanted was lists that
651were filled in at run-time (e.g. from a database), and that can be done easily
652enough with the CGI::FormBuilder object directly.
653
654You can also supply a default value to the field. To get a default value of
655C<green> for the color field:
656
657    color|Favorite color:select=green{red,blue,green}
658
659To validate a field, include a validation type at the end of the field line:
660
661    email|Email address//EMAIL
662
[21]663Valid validation types include any of the builtin defaults from L<CGI::FormBuilder>,
[19]664or the name of a pattern that you define with the C<!pattern> directive elsewhere
665in your form spec:
666
667    !pattern DAY /^([1-3][0-9])|[1-9]$/
668   
669    last_day//DAY
670
671If you just want a required value, use the builtin validation type C<VALUE>:
672
673    title//VALUE
674
[1]675=head2 Comments
676
677    # comment ...
678
679Any line beginning with a C<#> is considered a comment.
680
[7]681=head1 TODO
682
[16]683C<!include> directive to include external formspec files
[7]684
[16]685Field groups all on one line in the generated form
[7]686
[19]687Better tests!
[16]688
[1]689=head1 SEE ALSO
690
691L<CGI::FormBuilder>
692
[16]693=head1 AUTHOR
694
695Peter Eichman <peichman@cpan.org>
696
697=head1 COPYRIGHT AND LICENSE
698
699Copyright E<copy>2004 by Peter Eichman.
700
701This program is free software; you can redistribute it and/or
702modify it under the same terms as Perl itself.
703
[1]704=cut
Note: See TracBrowser for help on using the repository browser.