allow defaults for .define and allow barewords for pair list keys
authorTony Cook <tony@develop-help.com>
Sat, 21 Dec 2013 09:32:54 +0000 (20:32 +1100)
committerTony Cook <tony@develop-help.com>
Sat, 21 Dec 2013 09:32:54 +0000 (20:32 +1100)
site/cgi-bin/modules/Squirrel/Template.pm
site/cgi-bin/modules/Squirrel/Template/Constants.pm
site/cgi-bin/modules/Squirrel/Template/Expr.pm
site/cgi-bin/modules/Squirrel/Template/Parser.pm
site/cgi-bin/modules/Squirrel/Template/Processor.pm
t/020-templater/040-original.t

index 019807d..23193bb 100644 (file)
@@ -20,7 +20,7 @@ BEGIN {
 
 use constant DEBUG_GET_PARMS => 0;
 
-our $VERSION = "1.028";
+our $VERSION = "1.029";
 
 my %compile_cache;
 
@@ -394,9 +394,9 @@ sub set_var {
 }
 
 sub define_macro {
-  my ($self, $name, $content) = @_;
+  my ($self, $name, $content, $defaults) = @_;
 
-  $self->{defines}{$name} = $content;
+  $self->{defines}{$name} = [ $content, $defaults ];
 
   return 1;
 }
@@ -404,10 +404,10 @@ sub define_macro {
 sub get_macro {
   my ($self, $name) = @_;
 
-  my $content = $self->{defines}{$name}
+  my $define = $self->{defines}{$name}
     or return;
 
-  return $content;
+  return @$define;
 }
 
 sub parse {
@@ -825,6 +825,10 @@ The C<.end> token can also be C<.end for>.
 
 C<< <:.define I<name> :> I<content> <:.end:> >>
 
+=item *
+
+C<< <:.define I<name>; name1:value1, name2:value2 :> I<content> <:.end:> >>
+
 Define a macro called I<name> with the specified content.  The C<.end>
 token can be C<.end define>.
 
@@ -834,6 +838,8 @@ eg.
   some content
   <:.end define:>
 
+The second form provides defaults for calls to the macro.
+
 =item *
 
 C<< <:.call I<name-expression> :> >>
index 4e2407f..ec37fb7 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Template::Constants;
 use strict;
 use Exporter qw(import);
 
-our $VERSION = "1.008";
+our $VERSION = "1.009";
 
 sub _define_sequence {
   my ($keys, $start) = @_;
@@ -54,7 +54,7 @@ my @node_expr = qw(NODE_EXPR_EXPR NODE_EXPR_FORMAT);
 _define_sequence(\@node_expr, 4);
 my @node_set = qw(NODE_SET_VAR NODE_SET_EXPR);
 _define_sequence(\@node_set, 4);
-my @node_define = qw(NODE_DEFINE_NAME NODE_DEFINE_END NODE_DEFINE_CONTENT);
+my @node_define = qw(NODE_DEFINE_NAME NODE_DEFINE_END NODE_DEFINE_CONTENT NODE_DEFINE_DEFAULTS);
 _define_sequence(\@node_define, 4);
 my @node_call = qw(NODE_CALL_NAME NODE_CALL_LIST);
 _define_sequence(\@node_call, 4);
index 1407d61..a491a18 100644 (file)
@@ -1,7 +1,7 @@
 package Squirrel::Template::Expr;
 use strict;
 
-our $VERSION = "1.012";
+our $VERSION = "1.013";
 
 package Squirrel::Template::Expr::Eval;
 use Scalar::Util ();
@@ -585,25 +585,12 @@ sub _parse_primary {
     return [ list => $list ];
   }
   elsif ($t->[0] eq 'op{') {
-    my @pairs;
-    if ($tok->peektype eq 'op}') {
-      $tok->get; # discard '}'
-    }
-    else {
-      my $next;
-      do {
-       my $key = $self->_parse_additive($tok);
-       my $colon = $tok->get;
-       $colon->[0] eq 'op:'
-         or die [ error => "Expected : in hash but found $colon->[1]" ];
-       my $value = $self->_parse_expr($tok);
-       push @pairs, [ $key, $value ];
-      } while ($next = $tok->get and $next->[0] eq 'op,');
-      $next->[0] eq 'op}'
-       or die [ error => "Expected , or } but found $tok->[1]" ];
-    }
+    my $pairs = $self->parse_pairs($tok);
+    my $next = $tok->get;
+    $next->[0] eq 'op}'
+      or die [ error => "Expected , or } but found $next->[1]" ];
 
-    return [ hash => \@pairs ];
+    return [ hash => $pairs ];
   }
   elsif ($t->[0] eq 're') {
     return [ re => $t->[2], $t->[3] ];
@@ -613,6 +600,40 @@ sub _parse_primary {
   }
 }
 
+sub parse_pairs {
+  my ($self, $tok) = @_;
+
+  my $nexttype = $tok->peektype;
+  if ($nexttype eq 'op}' || $nexttype eq 'eof') {
+    return [];
+  }
+  else {
+    my $next;
+    my @pairs;
+    do {
+      my $key;
+      if ($tok->peektype eq 'id') {
+       my $id = $tok->get;
+       if ($tok->peektype eq 'op:') {
+         $key = [ const => $id->[2] ];
+       }
+       else {
+         $tok->unget($id);
+       }
+      }
+      $key ||= $self->_parse_additive($tok);
+      my $colon = $tok->get;
+      $colon->[0] eq 'op:'
+       or die [ error => "Expected : in hash but found $colon->[1]" ];
+      my $value = $self->_parse_expr($tok);
+      push @pairs, [ $key, $value ];
+    } while ($next = $tok->get and $next->[0] eq 'op,');
+    $tok->unget($next);
+
+    return \@pairs;
+  }
+}
+
 package Squirrel::Template::Expr::Tokenizer;
 
 use constant TEXT => 0;
index 67075db..44b577e 100644 (file)
@@ -2,7 +2,7 @@ package Squirrel::Template::Parser;
 use strict;
 use Squirrel::Template::Constants qw(:token :node);
 
-our $VERSION = "1.018";
+our $VERSION = "1.019";
 
 use constant TOK => 0;
 use constant TMPLT => 1;
@@ -516,21 +516,39 @@ sub _parse_define {
 
   my $content = $self->_parse_content;
   my $end = $self->[TOK]->get;
-  my $error;
+  my @errors;
   if ($end->[TOKEN_TYPE] eq 'end') {
     if ($end->[TOKEN_END_TYPE] && $end->[TOKEN_END_TYPE] ne 'define') {
-      $error = $self->_error($end, "Expected '.end' or '.end define' for .define started $define->[TOKEN_FILENAME]:$define->[TOKEN_LINE] but found '.end $end->[TOKEN_END_TYPE]'");
+      push @errors, $self->_error($end, "Expected '.end' or '.end define' for .define started $define->[TOKEN_FILENAME]:$define->[TOKEN_LINE] but found '.end $end->[TOKEN_END_TYPE]'");
     }
   }
   else {
     $self->[TOK]->unget($end);
-    $error = $self->_error($end, "Expected '.end' for .define started $define->[TOKEN_FILENAME]:$define->[TOKEN_LINE] but found $end->[TOKEN_TYPE]");
+    push @errors, $self->_error($end, "Expected '.end' for .define started $define->[TOKEN_FILENAME]:$define->[TOKEN_LINE] but found $end->[TOKEN_TYPE]");
     $end = $self->_empty($end);
   }
-  @{$define}[NODE_DEFINE_END, NODE_DEFINE_CONTENT] = ( $end, $content );
 
-  if ($error) {
-    return $self->_comp($define, $error);
+  my $text = $define->[NODE_DEFINE_NAME];
+  my $name;
+  if ($text =~ s(^([^;\s]+))()) {
+    $name = $1;
+  }
+  my $defaults;
+  my %seen_args;
+  if ($text =~ s/^\s*;\s*// && $text ne "") {
+    my $tokens = Squirrel::Template::Expr::Tokenizer->new($text);
+    my $parser = Squirrel::Template::Expr::Parser->new;
+    $defaults = $parser->parse_pairs($tokens);
+    $tokens->peektype eq 'eof'
+      or push @errors, $self->_error($end, "Defaults list for .define started $define->[TOKEN_FILENAME]:$define->[TOKEN_LINE] has extra junk");
+  }
+  $define->[NODE_DEFINE_NAME] = $name;
+
+  @{$define}[NODE_DEFINE_END, NODE_DEFINE_CONTENT, NODE_DEFINE_DEFAULTS] =
+    ( $end, $content, $defaults );
+
+  if (@errors) {
+    return $self->_comp($define, @errors);
   }
   else {
     return $define;
@@ -552,22 +570,12 @@ sub _parse_call {
 
   my @result;
   my $next = $tokens->get;
-  my @args;
+  my $args = [];
   if ($next->[0] eq 'op,') {
     unless (eval {
-      while ($next->[0] eq 'op,') {
-       my $key;
-       my $value;
-       $key = $parser->parse_tokens($tokens);
-       my $colon = $tokens->get;
-       $colon->[0] eq 'op:'
-         or die [ error => "Expected : but found $colon->[0]" ];
-       $value = $parser->parse_tokens($tokens);
-       push @args, [ $key, $value ];
-       $next = $tokens->get;
-      }
+      $args = $parser->parse_pairs($tokens);
 
-      if ($next->[0] ne 'eof') {
+      if ($tokens->peektype ne 'eof') {
        die [ error => "Expected , or eof but found $next->[0]" ];
       }
       1;
@@ -579,7 +587,7 @@ sub _parse_call {
     $error = $self->_error($call, "Expected , or end of expression but found $next->[0]");
   }
 
-  @{$call}[NODE_CALL_NAME, NODE_CALL_LIST] = ( $name_expr, \@args );
+  @{$call}[NODE_CALL_NAME, NODE_CALL_LIST] = ( $name_expr, $args );
 
   return $error ? $self->_comp($call, $error) : $call;
 }
index 4ead9f4..be664b0 100644 (file)
@@ -3,7 +3,7 @@ use strict;
 use Squirrel::Template::Constants qw(:node);
 use Scalar::Util ();
 
-our $VERSION = "1.024";
+our $VERSION = "1.025";
 
 use constant ACTS => 0;
 use constant TMPLT => 1;
@@ -158,7 +158,7 @@ sub _process_set {
 sub _process_define {
   my ($self, $define) = @_;
 
-  $self->[TMPLT]->define_macro($define->[NODE_DEFINE_NAME], $define->[NODE_DEFINE_CONTENT]);
+  $self->[TMPLT]->define_macro($define->[NODE_DEFINE_NAME], $define->[NODE_DEFINE_CONTENT], $define->[NODE_DEFINE_DEFAULTS]);
 
   return;
 }
@@ -169,6 +169,7 @@ sub _process_call {
   my $parsed;
   my %args;
   my @result;
+  my $defaults;
   my $name;
   if (eval {
     $name = $self->[EVAL]->process($node->[NODE_CALL_NAME]);
@@ -178,7 +179,7 @@ sub _process_call {
       $args{$key} = $value;
     }
 
-    $parsed = $self->[TMPLT]->get_macro($name);
+    ($parsed, $defaults) = $self->[TMPLT]->get_macro($name);
     if (!$parsed) {
       ($parsed, my $message) = $self->[TMPLT]->parse_file($name);
       unless ($parsed) {
@@ -190,6 +191,16 @@ sub _process_call {
   }) {
     my $ctx = ".call '$name' from $node->[NODE_FILENAME]:$node->[NODE_LINE]";
     if (eval { $self->[TMPLT]->start_scope($ctx, \%args), 1 }) {
+      if ($defaults) {
+       for my $entry (@$defaults) {
+         my ($name_expr, $value_expr)= @$entry;
+         my $name = $self->[EVAL]->process($name_expr);
+         unless (exists $args{$name}) {
+           my $value = $self->[EVAL]->process($value_expr);
+           $self->[TMPLT]->set_var($name, $value);
+         }
+       }
+      }
       @result = $self->process($parsed);
       $self->[TMPLT]->end_scope();
     }
index c0bdee8..1bbaa7a 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 # Basic tests for Squirrel::Template
 use strict;
-use Test::More tests => 185;
+use Test::More tests => 186;
 use HTML::Entities;
 
 sub template_test($$$$;$$);
@@ -624,9 +624,16 @@ IN
 <:-.define foo:>
 <:-= avar -:>
 <:.end-:>
-<:.call "foo", "avar":"avalue"-:>
+<:.call "foo", avar:"avalue"-:>
 <:.call "foo",
-  "avar":"bvalue"-:>
+  avar:"bvalue"-:>
+IN
+  template_test(<<IN, "2716", "define defaults with call", \%acts, "both", \%vars);
+<:-.define foo; abc:1, def:abc+5 :>
+<:-= abc -:><:= def -:>
+<:.end-:>
+<:.call "foo", "abc":"2"-:>
+<:.call "foo" -:>
 IN
   template_test(<<IN, "other value", "external call", \%acts, "", \%vars);
 <:.call "called.tmpl", "avar":"other value"-:>