Changeset 42 in text-formbuilder for trunk


Ignore:
Timestamp:
12/09/04 11:33:17 (19 years ago)
Author:
peter
Message:

added a create_form exported method to "do the right thing" in simple cases
added parse_array method to parse an array of lines making up a formspec
some internal cleanup of the lines structure

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/Changes

    r40 r42  
    22 
    330.07 
     4    * added a create_form exported method to "do the right 
     5      thing" in simple cases 
     6    * added parse_array method to parse an array of lines making 
     7      up a formspec 
     8    * inline field groups via the !group and !field directives 
    49    * added write_script method to write a skeleton CGI 
    510      script around the FormBuilder object 
  • trunk/lib/Text/FormBuilder.pm

    r39 r42  
    44use warnings; 
    55 
    6 use vars qw($VERSION); 
    7  
    8 $VERSION = '0.07_01'; 
     6use base qw(Exporter); 
     7use vars qw($VERSION @EXPORT); 
     8 
     9$VERSION = '0.07_02'; 
     10@EXPORT = qw(create_form); 
    911 
    1012use Carp; 
     
    4547my $TIDY_OPTIONS = '-nolq -ci=4 -ce'; 
    4648 
     49my $HTML_EXTS   = qr/\.html?$/; 
     50my $SCRIPT_EXTS = qr/\.(pl|cgi)$/; 
     51 
     52# superautomagical exported function 
     53sub create_form { 
     54    my ($source, $options, $destination) = @_; 
     55    my $parser = __PACKAGE__->parse($source); 
     56    $parser->build(%{ $options || {} }); 
     57    if ($destination) { 
     58        if (ref $destination) { 
     59            croak "[Text::FormBuilder::create_form] Don't know what to do with a ref for $destination"; 
     60            #TODO: what do ref dests mean? 
     61        } else { 
     62            # write webpage, script, or module 
     63            if ($destination =~ $HTML_EXTS) { 
     64                $parser->write($destination); 
     65            } elsif ($destination =~ $SCRIPT_EXTS) { 
     66                $parser->write_script($destination); 
     67            } else { 
     68                $parser->write_module($destination); 
     69            } 
     70        } 
     71    } else { 
     72        defined wantarray ? return $parser->form : $parser->write; 
     73    } 
     74} 
     75     
     76 
    4777sub new { 
    4878    my $invocant = shift; 
     
    5686sub parse { 
    5787    my ($self, $source) = @_; 
    58     if (ref $source && ref $source eq 'SCALAR') { 
    59         $self->parse_text($$source); 
     88    if (my $type = ref $source) { 
     89        if ($type eq 'SCALAR') { 
     90            $self->parse_text($$source); 
     91        } elsif ($type eq 'ARRAY') { 
     92            $self->parse_array(@$source); 
     93        } else { 
     94            croak "[Text::FormBuilder::parse] Unknown ref type $type passed as source"; 
     95        } 
    6096    } else { 
    6197        $self->parse_file($source); 
    6298    } 
     99} 
     100 
     101sub parse_array { 
     102    my ($self, @lines) = @_; 
     103    # so it can be called as a class method 
     104    $self = $self->new unless ref $self;     
     105    $self->parse_text(join("\n", @lines));     
     106    return $self; 
    63107} 
    64108 
     
    150194     
    151195    # expand groups 
    152     my %groups = %{ $self->{form_spec}{groups} || {} }; 
    153     for my $section (@{ $self->{form_spec}{sections} || [] }) { 
    154         foreach (grep { $$_[0] eq 'group' } @{ $$section{lines} }) { 
    155             $$_[1]{group} =~ s/^\%//;       # strip leading % from group var name 
    156              
    157             if (exists $groups{$$_[1]{group}}) { 
    158                 my @fields; # fields in the group 
    159                 push @fields, { %$_ } foreach @{ $groups{$$_[1]{group}} }; 
    160                 for my $field (@fields) { 
    161                     $$field{label} ||= ucfirst $$field{name}; 
    162                     $$field{name} = "$$_[1]{name}_$$field{name}";                 
     196    if (my %groups = %{ $self->{form_spec}{groups} || {} }) { 
     197        for my $section (@{ $self->{form_spec}{sections} || [] }) { 
     198            foreach (grep { $$_[0] eq 'group' } @{ $$section{lines} }) { 
     199                $$_[1]{group} =~ s/^\%//;       # strip leading % from group var name 
     200                 
     201                if (exists $groups{$$_[1]{group}}) { 
     202                    my @fields; # fields in the group 
     203                    push @fields, { %$_ } foreach @{ $groups{$$_[1]{group}} }; 
     204                    for my $field (@fields) { 
     205                        $$field{label} ||= ucfirst $$field{name}; 
     206                        $$field{name} = "$$_[1]{name}_$$field{name}";                 
     207                    } 
     208                    $_ = [ 'group', { label => $$_[1]{label} || ucfirst(join(' ',split('_',$$_[1]{name}))), group => \@fields } ]; 
    163209                } 
    164                 $_ = [ 'group', { label => $$_[1]{label} || ucfirst(join(' ',split('_',$$_[1]{name}))), group => \@fields } ]; 
    165210            } 
    166211        } 
     
    168213     
    169214    # the actual fields that are given to CGI::FormBuilder 
     215    # make copies so that when we trim down the sections 
     216    # we don't lose the form field information 
    170217    $self->{form_spec}{fields} = []; 
    171218     
     
    173220        for my $line (@{ $$section{lines} }) { 
    174221            if ($$line[0] eq 'group') { 
    175                 push @{ $self->{form_spec}{fields} }, $_ foreach @{ $$line[1]{group} }; 
     222                push @{ $self->{form_spec}{fields} }, { %{$_} } foreach @{ $$line[1]{group} }; 
    176223            } elsif ($$line[0] eq 'field') { 
    177                 push @{ $self->{form_spec}{fields} }, $$line[1]; 
     224                push @{ $self->{form_spec}{fields} }, { %{$$line[1]} }; 
    178225            } 
    179226        } 
     
    193240                warn "[Text::FormBuilder] validate coderefs don't work yet"; 
    194241                delete $$_{validate}; 
    195 ##                 $$_{validate} = eval "sub $subs{$$_{validate}}"; 
     242##                 $$_{validate} = $subs{$$_{validate}}; 
    196243            } 
    197244        } 
     
    199246     
    200247    # substitute in list names 
    201     my %lists = %{ $self->{form_spec}{lists} || {} }; 
    202     foreach (@{ $self->{form_spec}{fields} }) { 
    203         next unless $$_{list}; 
    204          
    205         $$_{list} =~ s/^\@//;   # strip leading @ from list var name 
    206          
    207         # a hack so we don't get screwy reference errors 
    208         if (exists $lists{$$_{list}}) { 
    209             my @list; 
    210             push @list, { %$_ } foreach @{ $lists{$$_{list}} }; 
    211             $$_{options} = \@list; 
    212         } else { 
    213             # assume that the list name is a builtin  
    214             # and let it fall through to CGI::FormBuilder 
    215             $$_{options} = $$_{list}; 
     248    if (my %lists = %{ $self->{form_spec}{lists} || {} }) { 
     249        foreach (@{ $self->{form_spec}{fields} }) { 
     250            next unless $$_{list}; 
     251             
     252            $$_{list} =~ s/^\@//;   # strip leading @ from list var name 
     253             
     254            # a hack so we don't get screwy reference errors 
     255            if (exists $lists{$$_{list}}) { 
     256                my @list; 
     257                push @list, { %$_ } foreach @{ $lists{$$_{list}} }; 
     258                $$_{options} = \@list; 
     259            } else { 
     260                # assume that the list name is a builtin  
     261                # and let it fall through to CGI::FormBuilder 
     262                $$_{options} = $$_{list}; 
     263            } 
     264        } continue { 
     265            delete $$_{list}; 
    216266        } 
    217     } continue { 
    218         delete $$_{list}; 
    219267    } 
    220268     
     
    226274    } 
    227275     
     276    # use the list for displaying checkbox groups 
    228277    foreach (@{ $self->{form_spec}{fields} }) { 
    229278        $$_{ulist} = 1 if ref $$_{options} and @{ $$_{options} } >= 3; 
     
    242291 
    243292    foreach (@{ $self->{form_spec}{sections} }) { 
    244         for my $line (grep { $$_[0] eq 'field' } @{ $$_{lines} }) { 
    245             $_ eq 'name' or delete $$line[1]{$_} foreach keys %{ $$line[1] }; 
     293        #for my $line (grep { $$_[0] eq 'field' } @{ $$_{lines} }) { 
     294        for my $line (@{ $$_{lines} }) { 
     295            if ($$line[0] eq 'field') { 
     296                $$line[1] = $$line[1]{name}; 
     297##                 $_ eq 'name' or delete $$line[1]{$_} foreach keys %{ $$line[1] }; 
     298##             } elsif ($$line[0] eq 'group') { 
     299##                 $$line[1] = [ map { $$_{name} } @{ $$line[1]{group} } ]; 
     300            } 
    246301        } 
    247302    } 
     
    293348} 
    294349 
     350# generates the core code to create the $form object 
     351# the generated code assumes that you have a CGI.pm 
     352# object named $q 
    295353sub _form_code { 
    296354    my $self = shift; 
     355     
    297356    # automatically call build if needed to 
    298357    # allow the new->parse->write shortcut 
     
    340399    my $d = Data::Dumper->new([ \%options ], [ '*options' ]); 
    341400     
    342     #TODO: need a workaround/better solution since Data::Dumper doesn't like dumping coderefs 
     401    use B::Deparse; 
     402    my $deparse = B::Deparse->new; 
     403##      
     404##     #TODO: need a workaround/better solution since Data::Dumper doesn't like dumping coderefs 
    343405##     foreach (@{ $self->{form_spec}{fields} }) { 
    344406##         if (ref $$_{validate} eq 'CODE') { 
    345 ##             $d->Seen({ "*_validate_$$_{name}" => $$_{validate} }); 
    346 ##             $module_subs{$$_{name}} = "sub _validate_$$_{name} $$_{validate}"; 
     407##             my $body = $deparse->coderef2text($$_{validate}); 
     408##             #$d->Seen({ "*_validate_$$_{name}" => $$_{validate} }); 
     409##             #$module_subs{$$_{name}} = "sub _validate_$$_{name} $$_{validate}"; 
    347410##         } 
    348 ##     } 
    349 ##      
     411##     }     
    350412##     my $sub_code = join("\n", each %module_subs); 
     413     
    351414    my $form_options = keys %options > 0 ? $d->Dump : ''; 
    352415     
     
    392455END 
    393456 
    394     my $outfile = (split(/::/, $package))[-1] . '.pm'; 
    395      
    396     _write_output_file($module, $outfile, $use_tidy); 
     457    _write_output_file($module, (split(/::/, $package))[-1] . '.pm', $use_tidy); 
    397458    return $self; 
    398459} 
     
    474535                $OUT .= qq[  <tr><th class="subhead" colspan="2"><h3>$$line[1]</h3></th></tr>\n] 
    475536            } elsif ($$line[0] eq 'field') { 
    476                 #TODO: we only need the field names, not the full field spec in the lines strucutre 
    477                 local $_ = $field{$$line[1]{name}}; 
     537                local $_ = $field{$$line[1]}; 
    478538                 
    479539                # skip hidden fields in the table 
     
    499559                 
    500560            } elsif ($$line[0] eq 'group') { 
    501                 my @field_names = map { $$_{name} } @{ $$line[1]{group} }; 
    502                 my @group_fields = map { $field{$_} } @field_names; 
     561                my @group_fields = map { $field{$_} } map { $$_{name} } @{ $$line[1]{group} }; 
    503562                $OUT .= (grep { $$_{invalid} } @group_fields) ? qq[  <tr class="invalid">\n] : qq[  <tr>\n]; 
    504563                 
     
    509568                $OUT .= qq[    <td>]; 
    510569                $OUT .= join(' ', map { qq[<small class="sublabel">$$_{label}</small> $$_{field} $$_{comment}] } @group_fields); 
     570                $OUT .= " $msg_invalid" if $$_{invalid}; 
     571 
    511572                $OUT .= qq[    </td>\n]; 
    512573                $OUT .= qq[  </tr>\n]; 
     
    536597  <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title> 
    537598  <style type="text/css"> 
    538 ] . 
    539 $css . 
    540 q[  </style> 
     599] . $css . q[  </style> 
    541600  <% $jshead %> 
    542601</head> 
     
    562621} 
    563622 
     623# usage: $self->_template($css, $charset) 
    564624sub _template { 
    565625    my $self = shift; 
     
    632692=head2 parse 
    633693 
    634     # parse a file 
     694    # parse a file (regular scalar) 
    635695    $parser->parse($filename); 
    636696     
    637697    # or pass a scalar ref for parse a literal string 
    638698    $parser->parse(\$string); 
    639  
    640 Parse the file or string. Returns the parser object. 
     699     
     700    # or an array ref to parse lines 
     701    $parser->parse(\@lines); 
     702 
     703Parse the file or string. Returns the parser object. This method, 
     704along with all of its C<parse_*> siblings, may be called as a class 
     705method to construct a new object. 
    641706 
    642707=head2 parse_file 
     
    652717 
    653718Parse the given C<$src> text. Returns the parser object. 
     719 
     720=head2 parse_array 
     721 
     722    $parser->parse_array(@lines); 
     723 
     724Concatenates and parses C<@lines>. Returns the parser object. 
    654725 
    655726=head2 build 
     
    841912    } 
    842913     
    843     !pattern name /regular expression/ 
    844      
    845     !list name { 
     914    !pattern NAME /regular expression/ 
     915     
     916    !list NAME { 
    846917        option1[display string], 
    847918        option2[display string], 
     
    849920    } 
    850921     
    851     !list name &{ CODE } 
     922    !list NAME &{ CODE } 
     923     
     924    !group NAME { 
     925        field1 
     926        field2 
     927        ... 
     928    } 
    852929     
    853930    !section id heading 
     
    866943 
    867944Defines a list for use in a C<radio>, C<checkbox>, or C<select> field. 
     945 
     946=item C<!group> 
     947 
     948Define a named group of fields that are displayed all on one line. Use with 
     949the C<!field> directive. 
     950 
     951=item C<!field> 
     952 
     953Include a named instance of a group defined with C<!group>. 
    868954 
    869955=item C<!title> 
     
    10281114were filled in, would have to validate as an C<EMAIL>. 
    10291115 
     1116=head2 Field Groups 
     1117 
     1118You can define groups of fields using the C<!group> directive: 
     1119 
     1120    !group DATE { 
     1121        month:select@MONTHS//INT 
     1122        day[2]//INT 
     1123        year[4]//INT 
     1124    } 
     1125 
     1126You can then include instances of this group using the C<!field> directive: 
     1127 
     1128    !field %DATE birthday 
     1129 
     1130This will create a line in the form labeled ``Birthday'' which contains 
     1131a month dropdown, and day and year text entry fields. The actual input field 
     1132names are formed by concatenating the C<!field> name (e.g. C<birthday>) with 
     1133the name of the subfield defined in the group (e.g. C<month>, C<day>, C<year>). 
     1134Thus in this example, you would end up with the form fields C<birthday_month>, 
     1135C<birthday_day>, and C<birthday_year>. 
     1136 
    10301137=head2 Comments 
    10311138 
     
    10351142 
    10361143=head1 TODO 
     1144 
     1145Allow renaming of the submit button; allow renaming and inclusion of a  
     1146reset button 
    10371147 
    10381148Allow for custom wrappers around the C<form_template> 
     
    10491159=head1 BUGS 
    10501160 
    1051 I'm sure they're in there, I just haven't tripped over any new ones lately. :-) 
     1161Creating two $parsers in the same script causes the second one to get the data 
     1162from the first one. 
     1163 
     1164Get the fallback to CGI::FormBuilder builtin lists to work. 
     1165 
     1166I'm sure there are more in there, I just haven't tripped over any new ones lately. :-) 
     1167 
     1168Suggestions on how to improve the (currently tiny) test suite would be appreciated. 
    10521169 
    10531170=head1 SEE ALSO 
  • trunk/lib/Text/FormBuilder/grammar

    r39 r42  
    7575 
    7676validate_def: '!validate' var_name <perl_codeblock> 
    77     { $subs{$item{var_name}} = $item[3] } 
     77    { $subs{$item{var_name}} = eval "sub $item[3]" } 
    7878 
    7979group_def: '!group' { $context = 'group' } var_name '{' field_line(s) '}' { $context = 'line' } 
  • trunk/t/Text-FormBuilder.t

    r19 r42  
    66# change 'tests => 1' to 'tests => last_test_to_print'; 
    77 
    8 use Test::More tests => 6; 
     8use Test::More qw(no_plan); #tests => 6; 
    99BEGIN { use_ok('Text::FormBuilder'); }; 
    1010 
     
    2525isa_ok($p, 'Text::FormBuilder', 'new parser (from parse as class method)'); 
    2626 
     27 
     28my $simple = <<END; 
     29name 
     30email 
     31phone 
     32END 
     33 
     34my $form = $p->parse(\$simple)->form; 
     35# we should have three fields 
     36is(keys %{ $form->fields }, 3, 'correct number of fields'); 
     37 
     38my $p2 = Text::FormBuilder->parse_array([qw(code title semester instructor)]); 
     39is(keys %{ $p2->form->fields }, 4, 'correct number of fields from parse_array'); 
     40$p2->write; 
Note: See TracChangeset for help on using the changeset viewer.