- Timestamp:
- 12/09/04 11:33:17 (20 years ago)
- Location:
- trunk
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Changes
r40 r42 2 2 3 3 0.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 4 9 * added write_script method to write a skeleton CGI 5 10 script around the FormBuilder object -
trunk/lib/Text/FormBuilder.pm
r39 r42 4 4 use warnings; 5 5 6 use vars qw($VERSION); 7 8 $VERSION = '0.07_01'; 6 use base qw(Exporter); 7 use vars qw($VERSION @EXPORT); 8 9 $VERSION = '0.07_02'; 10 @EXPORT = qw(create_form); 9 11 10 12 use Carp; … … 45 47 my $TIDY_OPTIONS = '-nolq -ci=4 -ce'; 46 48 49 my $HTML_EXTS = qr/\.html?$/; 50 my $SCRIPT_EXTS = qr/\.(pl|cgi)$/; 51 52 # superautomagical exported function 53 sub 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 47 77 sub new { 48 78 my $invocant = shift; … … 56 86 sub parse { 57 87 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 } 60 96 } else { 61 97 $self->parse_file($source); 62 98 } 99 } 100 101 sub 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; 63 107 } 64 108 … … 150 194 151 195 # 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 } ]; 163 209 } 164 $_ = [ 'group', { label => $$_[1]{label} || ucfirst(join(' ',split('_',$$_[1]{name}))), group => \@fields } ];165 210 } 166 211 } … … 168 213 169 214 # 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 170 217 $self->{form_spec}{fields} = []; 171 218 … … 173 220 for my $line (@{ $$section{lines} }) { 174 221 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} }; 176 223 } elsif ($$line[0] eq 'field') { 177 push @{ $self->{form_spec}{fields} }, $$line[1];224 push @{ $self->{form_spec}{fields} }, { %{$$line[1]} }; 178 225 } 179 226 } … … 193 240 warn "[Text::FormBuilder] validate coderefs don't work yet"; 194 241 delete $$_{validate}; 195 ## $$_{validate} = eval "sub $subs{$$_{validate}}";242 ## $$_{validate} = $subs{$$_{validate}}; 196 243 } 197 244 } … … 199 246 200 247 # 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}; 216 266 } 217 } continue {218 delete $$_{list};219 267 } 220 268 … … 226 274 } 227 275 276 # use the list for displaying checkbox groups 228 277 foreach (@{ $self->{form_spec}{fields} }) { 229 278 $$_{ulist} = 1 if ref $$_{options} and @{ $$_{options} } >= 3; … … 242 291 243 292 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 } 246 301 } 247 302 } … … 293 348 } 294 349 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 295 353 sub _form_code { 296 354 my $self = shift; 355 297 356 # automatically call build if needed to 298 357 # allow the new->parse->write shortcut … … 340 399 my $d = Data::Dumper->new([ \%options ], [ '*options' ]); 341 400 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 343 405 ## foreach (@{ $self->{form_spec}{fields} }) { 344 406 ## 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}"; 347 410 ## } 348 ## } 349 ## 411 ## } 350 412 ## my $sub_code = join("\n", each %module_subs); 413 351 414 my $form_options = keys %options > 0 ? $d->Dump : ''; 352 415 … … 392 455 END 393 456 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); 397 458 return $self; 398 459 } … … 474 535 $OUT .= qq[ <tr><th class="subhead" colspan="2"><h3>$$line[1]</h3></th></tr>\n] 475 536 } 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]}; 478 538 479 539 # skip hidden fields in the table … … 499 559 500 560 } 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} }; 503 562 $OUT .= (grep { $$_{invalid} } @group_fields) ? qq[ <tr class="invalid">\n] : qq[ <tr>\n]; 504 563 … … 509 568 $OUT .= qq[ <td>]; 510 569 $OUT .= join(' ', map { qq[<small class="sublabel">$$_{label}</small> $$_{field} $$_{comment}] } @group_fields); 570 $OUT .= " $msg_invalid" if $$_{invalid}; 571 511 572 $OUT .= qq[ </td>\n]; 512 573 $OUT .= qq[ </tr>\n]; … … 536 597 <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title> 537 598 <style type="text/css"> 538 ] . 539 $css . 540 q[ </style> 599 ] . $css . q[ </style> 541 600 <% $jshead %> 542 601 </head> … … 562 621 } 563 622 623 # usage: $self->_template($css, $charset) 564 624 sub _template { 565 625 my $self = shift; … … 632 692 =head2 parse 633 693 634 # parse a file 694 # parse a file (regular scalar) 635 695 $parser->parse($filename); 636 696 637 697 # or pass a scalar ref for parse a literal string 638 698 $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 703 Parse the file or string. Returns the parser object. This method, 704 along with all of its C<parse_*> siblings, may be called as a class 705 method to construct a new object. 641 706 642 707 =head2 parse_file … … 652 717 653 718 Parse the given C<$src> text. Returns the parser object. 719 720 =head2 parse_array 721 722 $parser->parse_array(@lines); 723 724 Concatenates and parses C<@lines>. Returns the parser object. 654 725 655 726 =head2 build … … 841 912 } 842 913 843 !pattern name/regular expression/844 845 !list name{914 !pattern NAME /regular expression/ 915 916 !list NAME { 846 917 option1[display string], 847 918 option2[display string], … … 849 920 } 850 921 851 !list name &{ CODE } 922 !list NAME &{ CODE } 923 924 !group NAME { 925 field1 926 field2 927 ... 928 } 852 929 853 930 !section id heading … … 866 943 867 944 Defines a list for use in a C<radio>, C<checkbox>, or C<select> field. 945 946 =item C<!group> 947 948 Define a named group of fields that are displayed all on one line. Use with 949 the C<!field> directive. 950 951 =item C<!field> 952 953 Include a named instance of a group defined with C<!group>. 868 954 869 955 =item C<!title> … … 1028 1114 were filled in, would have to validate as an C<EMAIL>. 1029 1115 1116 =head2 Field Groups 1117 1118 You 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 1126 You can then include instances of this group using the C<!field> directive: 1127 1128 !field %DATE birthday 1129 1130 This will create a line in the form labeled ``Birthday'' which contains 1131 a month dropdown, and day and year text entry fields. The actual input field 1132 names are formed by concatenating the C<!field> name (e.g. C<birthday>) with 1133 the name of the subfield defined in the group (e.g. C<month>, C<day>, C<year>). 1134 Thus in this example, you would end up with the form fields C<birthday_month>, 1135 C<birthday_day>, and C<birthday_year>. 1136 1030 1137 =head2 Comments 1031 1138 … … 1035 1142 1036 1143 =head1 TODO 1144 1145 Allow renaming of the submit button; allow renaming and inclusion of a 1146 reset button 1037 1147 1038 1148 Allow for custom wrappers around the C<form_template> … … 1049 1159 =head1 BUGS 1050 1160 1051 I'm sure they're in there, I just haven't tripped over any new ones lately. :-) 1161 Creating two $parsers in the same script causes the second one to get the data 1162 from the first one. 1163 1164 Get the fallback to CGI::FormBuilder builtin lists to work. 1165 1166 I'm sure there are more in there, I just haven't tripped over any new ones lately. :-) 1167 1168 Suggestions on how to improve the (currently tiny) test suite would be appreciated. 1052 1169 1053 1170 =head1 SEE ALSO -
trunk/lib/Text/FormBuilder/grammar
r39 r42 75 75 76 76 validate_def: '!validate' var_name <perl_codeblock> 77 { $subs{$item{var_name}} = $item[3]}77 { $subs{$item{var_name}} = eval "sub $item[3]" } 78 78 79 79 group_def: '!group' { $context = 'group' } var_name '{' field_line(s) '}' { $context = 'line' } -
trunk/t/Text-FormBuilder.t
r19 r42 6 6 # change 'tests => 1' to 'tests => last_test_to_print'; 7 7 8 use Test::More tests => 6;8 use Test::More qw(no_plan); #tests => 6; 9 9 BEGIN { use_ok('Text::FormBuilder'); }; 10 10 … … 25 25 isa_ok($p, 'Text::FormBuilder', 'new parser (from parse as class method)'); 26 26 27 28 my $simple = <<END; 29 name 30 email 31 phone 32 END 33 34 my $form = $p->parse(\$simple)->form; 35 # we should have three fields 36 is(keys %{ $form->fields }, 3, 'correct number of fields'); 37 38 my $p2 = Text::FormBuilder->parse_array([qw(code title semester instructor)]); 39 is(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.