]> Dogcows Code - chaz/p5-CGI-Ex/commitdiff
CGI::Ex 2.04 v2.04
authorPaul Seamons <perl@seamons.com>
Mon, 10 Jul 2006 00:00:00 +0000 (00:00 +0000)
committerCharles McGarvey <chazmcgarvey@brokenzipper.com>
Fri, 9 May 2014 23:46:40 +0000 (17:46 -0600)
22 files changed:
Changes
MANIFEST
META.yml
lib/CGI/Ex.pm
lib/CGI/Ex/App.pm
lib/CGI/Ex/Auth.pm
lib/CGI/Ex/Conf.pm
lib/CGI/Ex/Dump.pm
lib/CGI/Ex/Fill.pm
lib/CGI/Ex/JSONDump.pm [new file with mode: 0644]
lib/CGI/Ex/Template.pm
lib/CGI/Ex/Template.pod
lib/CGI/Ex/Validate.pm
lib/CGI/Ex/validate.js
samples/app/cgi_ex_1.cgi [new file with mode: 0755]
samples/app/cgi_ex_2.cgi [new file with mode: 0755]
samples/benchmark/bench_jsondump.pl [new file with mode: 0755]
samples/benchmark/bench_template.pl
samples/index.cgi [new file with mode: 0755]
samples/memory_template.pl
t/8_auth_00_base.t
t/9_jsondump_00_base.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 3e7bee4b9ee128a8bbdf86235f793c5b97dbfd48..fa799ac374f24f2847b768b8ede04568671bb9e7 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,3 +1,12 @@
+2.04   2006-07-10
+        * Allow for items not in group order to get added to validation correctly in CGI::Ex::Validate.
+        * Add samples/index.cgi
+        * Fix samples/app/cgi_ex_* to actually work.
+        * Added CGI::Ex::JSONDump
+        * Change Validate to use JSONDump instaed of JSON
+        * Various perldoc and other bug fixes.
+        * Removed CGI-Ex.spec - use cpan2rpm or cpan2deb instead.
+
 2.03   2006-06-10
         * Fix the associativity of operators in Template to match perl
         * Allow for multiple prefix operators.
index d57f9bd2c1a58ce1581805f710c12d2bb259d331..2772de739793cc259eec5634a069bdf6c3893b4c 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -1,4 +1,3 @@
-CGI-Ex.spec
 Changes
 lib/CGI/Ex.pm
 lib/CGI/Ex/App.pm
@@ -8,6 +7,7 @@ lib/CGI/Ex/Conf.pm
 lib/CGI/Ex/Die.pm
 lib/CGI/Ex/Dump.pm
 lib/CGI/Ex/Fill.pm
+lib/CGI/Ex/JSONDump.pm
 lib/CGI/Ex/md5.js
 lib/CGI/Ex/sha1.js
 lib/CGI/Ex/Template.pm
@@ -20,18 +20,19 @@ MANIFEST
 MANIFEST.SKIP
 META.yml
 README
+samples/app/cgi_ex_1.cgi
+samples/app/cgi_ex_2.cgi
 samples/benchmark/bench_auth.pl
 samples/benchmark/bench_cgix_hfif.pl
 samples/benchmark/bench_conf_readers.pl
 samples/benchmark/bench_conf_writers.pl
+samples/benchmark/bench_jsondump.pl
 samples/benchmark/bench_method_calling.pl
 samples/benchmark/bench_optree.pl
 samples/benchmark/bench_template.pl
 samples/benchmark/bench_template_tag_parser.pl
 samples/benchmark/bench_validation.pl
 samples/benchmark/bench_various_templaters.pl
-samples/cgi_ex_1.cgi
-samples/cgi_ex_2.cgi
 samples/conf_path_1/apples.pl
 samples/conf_path_1/oranges.pl
 samples/conf_path_3/apples.pl
@@ -42,6 +43,7 @@ samples/devel/dprof_validation.d
 samples/generate_js.pl
 samples/html1.htm
 samples/html2.htm
+samples/index.cgi
 samples/js_validate_1.html
 samples/js_validate_2.html
 samples/js_validate_3.html
@@ -93,3 +95,4 @@ t/6_die_00_base.t
 t/7_template_00_base.t
 t/7_template_01_includes.t
 t/8_auth_00_base.t
+t/9_jsondump_00_base.t
index 7dff86db3972a8f8a656ca1c8e27e7249945ae4d..cb1be5c3b13f9039cdad1b0b21228996ffb5db40 100644 (file)
--- a/META.yml
+++ b/META.yml
@@ -1,7 +1,7 @@
 # http://module-build.sourceforge.net/META-spec.html
 #XXXXXXX This is a prototype!!!  It will change in the future!!! XXXXX#
 name:         CGI-Ex
-version:      2.03
+version:      2.04
 version_from: lib/CGI/Ex.pm
 installdirs:  site
 requires:
index e0093244d7d360af2606df5701340cea9808c198..955e26e1d685576c6b77da6922c6d094400ea5aa 100644 (file)
@@ -24,7 +24,7 @@ use vars qw($VERSION
 use base qw(Exporter);
 
 BEGIN {
-    $VERSION               = '2.03';
+    $VERSION               = '2.04';
     $PREFERRED_CGI_MODULE  ||= 'CGI';
     @EXPORT = ();
     @EXPORT_OK = qw(get_form
index 0c3b1f7277cb9c7f511fc7cf27d92cd8a504976e..a08741d2f80ce3f14f9b2943c4e25d2431e6196e 100644 (file)
@@ -10,7 +10,7 @@ use strict;
 use vars qw($VERSION);
 
 BEGIN {
-    $VERSION = '2.03';
+    $VERSION = '2.04';
 
     Time::HiRes->import('time') if eval {require Time::HiRes};
 }
@@ -382,6 +382,8 @@ sub run_hook {
     my ($code, $found) = @{ $self->find_hook($hook, $step) };
     if (! $code) {
         croak "Could not find a method named ${step}_${hook} or ${hook}";
+    } elsif (! UNIVERSAL::isa($code, 'CODE')) {
+        croak "Value for $hook ($found) is not a code ref ($code)";
     }
 
     ### record history
index 67605ff08efffd172d7d0e70112fcfbda8dd0260..337801cd6b4da37927eb23fd8c2a9198fd8e2c39 100644 (file)
@@ -18,7 +18,7 @@ use MIME::Base64 qw(encode_base64 decode_base64);
 use Digest::MD5 qw(md5_hex);
 use CGI::Ex;
 
-$VERSION = '2.03';
+$VERSION = '2.04';
 
 ###----------------------------------------------------------------###
 
@@ -315,7 +315,9 @@ sub login_hash_common {
         $self->key_time    => $self->server_time,
         $self->key_payload => $self->generate_payload({%$data, login_form => 1}),
         $self->key_expires_min => $self->expires_min,
-
+        text_user          => $self->text_user,
+        text_pass          => $self->text_pass,
+        text_save          => $self->text_save,
     };
 }
 
@@ -337,7 +339,7 @@ sub verify_token {
         my $key;
         for my $armor ('none', 'base64', 'blowfish') { # try with and without base64 encoding
             my $copy = ($armor eq 'none')           ? $token
-                     : ($armor eq 'base64')         ? decode_base64($token)
+                     : ($armor eq 'base64')         ? eval { local $^W; decode_base64($token) }
                      : ($key = $self->use_blowfish) ? decrypt_blowfish($token, $key)
                      : next;
             if ($copy =~ m|^ ([^/]+) / (\d+) / (-?\d+) / (.*) / ([a-fA-F0-9]{32}) (?: / (sh\.\d+\.\d+))? $|x) {
@@ -383,6 +385,13 @@ sub verify_token {
     } elsif (! defined($pass = eval { $self->get_pass_by_user($data->{'user'}) })) {
         $data->add_data({details => $@});
         $data->error('Could not get pass');
+    } elsif (ref $pass eq 'HASH') {
+        my $extra = $pass;
+        $pass = exists($extra->{'real_pass'}) ? delete($extra->{'real_pass'})
+              : exists($extra->{'password'})  ? delete($extra->{'password'})
+              : do { $data->error('Data returned by get_pass_by_user did not contain real_pass or password'); undef };
+        $data->error('Invalid login') if ! defined $pass && ! $data->error;
+        $data->add_data($extra);
     }
     return $data if $data->error;
 
@@ -622,16 +631,16 @@ sub login_form {
     <input type="hidden" name="[% key_expires_min %]" value="">
     <table class="login_table">
     <tr class="login_username">
-      <td>Username:</td>
+      <td>[% text_user %]</td>
       <td><input name="[% key_user %]" type="text" size="30" value=""></td>
     </tr>
     <tr class="login_password">
-      <td>Password:</td>
+      <td>[% text_pass %]</td>
       <td><input name="[% key_pass %]" type="password" size="30" value=""></td>
     </tr>
     <tr class="login_save">
       <td colspan="2">
-        <input type="checkbox" name="[% key_save %]" value="1"> Save Password ?
+        <input type="checkbox" name="[% key_save %]" value="1"> [% text_save %]
       </td>
     </tr>
     <tr class="login_submit">
@@ -645,6 +654,10 @@ sub login_form {
 };
 }
 
+sub text_user { my $self = shift; return defined($self->{'text_user'}) ? $self->{'text_user'} : 'Username:' }
+sub text_pass { my $self = shift; return defined($self->{'text_pass'}) ? $self->{'text_pass'} : 'Password:' }
+sub text_save { my $self = shift; return defined($self->{'text_save'}) ? $self->{'text_save'} : 'Save Password ?' }
+
 sub login_script {
   return q {
     [%~ IF ! use_plaintext %]
@@ -797,6 +810,9 @@ defined separately.
     secure_hash_keys
     template_args
     template_include_path
+    text_user
+    text_pass
+    text_save
     use_base64
     use_blowfish
     use_crypt
@@ -946,6 +962,9 @@ Passed to the template swapped during login_print.
     $self->key_time    # $self->server_time,     # the server's time
     $self->key_payload # $data->{'payload'}      # the payload (if any)
     $self->key_expires_min # $self->expires_min  # how many minutes crams are valid
+    text_user          # $self->text_user        # template text Username:
+    text_pass          # $self->text_pass        # template text Password:
+    text_save          # $self->text_save        # template text Save Password ?
 
 =item C<key_logout>
 
@@ -1028,6 +1047,26 @@ valid password for the user.  It can always return plaintext.  If use_crypt is
 enabled, it should return the crypted password.  If use_plaintext and use_crypt
 are not enabled, it may return the md5 sum of the password.
 
+   get_pass_by_user => sub {
+       my ($auth_obj, $user) = @_;
+       return $some_obj->get_pass({user => $user});
+   }
+
+Alternately, get_pass_by_user may return a hashref of data items that
+will be added to the data object if the token is valid.  The hashref
+must also contain a key named real_pass or password that contains the
+password.  Note that keys passed back in the hashref that are already
+in the data object will override those in the data object.
+
+   get_pass_by_user => sub {
+       my ($auth_obj, $user) = @_;
+       my ($pass, $user_id) = $some_obj->get_pass({user => $user});
+       return {
+           password => $pass,
+           user_id  => $user_id,
+       };
+   }
+
 =item C<cgix>
 
 Returns a CGI::Ex object.
@@ -1080,8 +1119,18 @@ Contains javascript that will attach to the form from login_form.  This script
 is capable of taking the login_fields and creating an md5 cram which prevents
 the password from being passed plaintext.
 
+=item C<text_user, text_pass, text_save>
+
+The text items shown in the default login template.  The default values are:
+
+    text_user  "Username:"
+    text_pass  "Password:"
+    text_save  "Save Password ?"
+
+=back
+
 =head1 AUTHORS
 
-Paul Seamons <perlspam at seamons dot com>
+Paul Seamons <paul at seamons dot com>
 
 =cut
index 787be6fb2f41cba4d4a7d991cd057a042aa936e7..322fd1c1c80649fc7f3ad5fc52eb8fab4973d70b 100644 (file)
@@ -26,9 +26,9 @@ use vars qw($VERSION
             $HTML_KEY
             @EXPORT_OK
             );
-@EXPORT_OK = qw(conf_read conf_write);
+@EXPORT_OK = qw(conf_read conf_write in_cache);
 
-$VERSION = '2.03';
+$VERSION = '2.04';
 
 $DEFAULT_EXT = 'conf';
 
@@ -583,47 +583,52 @@ sub write_handler_html {
 ###----------------------------------------------------------------###
 
 sub preload_files {
-  my $self  = shift;
-  my $paths = shift || $self->paths;
-  require File::Find;
-
-  ### what extensions do we look for
-  my %EXT;
-  if ($self->{handler}) {
-    if (UNIVERSAL::isa($self->{handler},'HASH')) {
-      %EXT = %{ $self->{handler} };
-    }
-  } else {
-    %EXT = %EXT_READERS;
-    if (! $self->{html_key} && ! $HTML_KEY) {
-      delete $EXT{$_} foreach qw(html htm);
-    }
-  }
-  return if ! keys %EXT;
-
-  ### look in the paths for the files
-  foreach my $path (ref($paths) ? @$paths : $paths) {
-    $path =~ s|//+|/|g;
-    $path =~ s|/$||;
-    next if exists $CACHE{$path};
-    if (-f $path) {
-      my $ext = ($path =~ /\.(\w+)$/) ? $1 : '';
-      next if ! $EXT{$ext};
-      $CACHE{$path} = $self->read($path);
-    } elsif (-d _) {
-      $CACHE{$path} = 1;
-      File::Find::find(sub {
-        return if exists $CACHE{$File::Find::name};
-        return if $File::Find::name =~ m|/CVS/|;
-        return if ! -f;
-        my $ext = (/\.(\w+)$/) ? $1 : '';
-        return if ! $EXT{$ext};
-        $CACHE{$File::Find::name} = $self->read($File::Find::name);
-      }, "$path/");
+    my $self  = shift;
+    my $paths = shift || $self->paths;
+
+    ### what extensions do we look for
+    my %EXT;
+    if ($self->{'handler'}) {
+        if (UNIVERSAL::isa($self->{'handler'},'HASH')) {
+            %EXT = %{ $self->{'handler'} };
+        }
     } else {
-      $CACHE{$path} = 0;
+        %EXT = %EXT_READERS;
+        if (! $self->{'html_key'} && ! $HTML_KEY) {
+            delete $EXT{$_} foreach qw(html htm);
+        }
     }
-  }
+    return if ! keys %EXT;
+
+    ### look in the paths for the files
+    foreach my $path (ref($paths) ? @$paths : $paths) {
+        $path =~ s|//+|/|g;
+        $path =~ s|/$||;
+        next if exists $CACHE{$path};
+        if (-f $path) {
+            my $ext = ($path =~ /\.(\w+)$/) ? $1 : '';
+            next if ! $EXT{$ext};
+            $CACHE{$path} = $self->read($path);
+        } elsif (-d _) {
+            $CACHE{$path} = 1;
+            require File::Find;
+            File::Find::find(sub {
+                return if exists $CACHE{$File::Find::name};
+                return if $File::Find::name =~ m|/CVS/|;
+                return if ! -f;
+                my $ext = (/\.(\w+)$/) ? $1 : '';
+                return if ! $EXT{$ext};
+                $CACHE{$File::Find::name} = $self->read($File::Find::name);
+            }, "$path/");
+        } else {
+            $CACHE{$path} = 0;
+        }
+    }
+}
+
+sub in_cache {
+    my ($self, $file) = (@_ == 2) ? @_ : (undef, shift());
+    return exists($CACHE{$file}) || 0;
 }
 
 ###----------------------------------------------------------------###
@@ -634,43 +639,53 @@ __END__
 
 =head1 SYNOPSIS
 
-  my $cob = CGI::Ex::Conf->new;
+    use CGI::Ex::Conf qw(conf_read conf_write);
+
+    my $hash = conf_read("/tmp/foo.yaml");
+
+    conf_write("/tmp/foo.yaml", {key1 => $val1, key2 => $val2});
+
+
+    ### OOP interface
+
+    my $cob = CGI::Ex::Conf->new;
 
-  my $full_path_to_file = "/tmp/foo.val"; # supports ini, sto, val, pl, xml
-  my $hash = $cob->read($file);
+    my $full_path_to_file = "/tmp/foo.val"; # supports ini, sto, val, pl, xml
+    my $hash = $cob->read($file);
 
-  local $cob->{default_ext} = 'conf'; # default anyway
+    local $cob->{default_ext} = 'conf'; # default anyway
 
 
-  my @paths = qw(/tmp, /home/pauls);
-  local $cob->{paths} = \@paths;
-  my $hash = $cob->read('My::NameSpace');
-  # will look in /tmp/My/NameSpace.conf and /home/pauls/My/NameSpace.conf
+    my @paths = qw(/tmp, /home/pauls);
+    local $cob->{paths} = \@paths;
+    my $hash = $cob->read('My::NameSpace');
+    # will look in /tmp/My/NameSpace.conf and /home/pauls/My/NameSpace.conf
 
-  my $hash = $cob->read('My::NameSpace', {paths => ['/tmp']});
-  # will look in /tmp/My/NameSpace.conf
 
+    my $hash = $cob->read('My::NameSpace', {paths => ['/tmp']});
+    # will look in /tmp/My/NameSpace.conf
 
-  local $cob->{directive} = 'MERGE';
-  my $hash = $cob->read('FooSpace');
-  # OR #
-  my $hash = $cob->read('FooSpace', {directive => 'MERGE'});
-  # will return merged hashes from /tmp/FooSpace.conf and /home/pauls/FooSpace.conf
-  # immutable keys are preserved from originating files
 
+    local $cob->{directive} = 'MERGE';
+    my $hash = $cob->read('FooSpace');
+    # OR #
+    my $hash = $cob->read('FooSpace', {directive => 'MERGE'});
+    # will return merged hashes from /tmp/FooSpace.conf and /home/pauls/FooSpace.conf
+    # immutable keys are preserved from originating files
 
-  local $cob->{directive} = 'FIRST';
-  my $hash = $cob->read('FooSpace');
-  # will return values from first found file in the path.
 
+    local $cob->{directive} = 'FIRST';
+    my $hash = $cob->read('FooSpace');
+    # will return values from first found file in the path.
 
-  local $cob->{directive} = 'LAST'; # default behavior
-  my $hash = $cob->read('FooSpace');
-  # will return values from last found file in the path.
 
+    local $cob->{directive} = 'LAST'; # default behavior
+    my $hash = $cob->read('FooSpace');
+    # will return values from last found file in the path.
 
-  ### manipulate $hash
-  $cob->write('FooSpace'); # will write it out the changes
+
+    ### manipulate $hash
+    $cob->write('FooSpace'); # will write it out the changes
 
 =head1 DESCRIPTION
 
@@ -688,7 +703,7 @@ Oh - and it writes too.
 
 =over 4
 
-=item C<-E<gt>read_ref>
+=item C<read_ref>
 
 Takes a file and optional argument hashref.  Figures out the type
 of handler to use to read the file, reads it and returns the ref.
@@ -696,7 +711,7 @@ If you don't need the extended merge functionality, or key fallback,
 or immutable keys, or path lookup ability - then use this method.
 Otherwise - use ->read.
 
-=item C<-E<gt>read>
+=item C<read>
 
 First argument may be either a perl data structure, yaml string, a
 full filename, or a file "namespace".
@@ -754,13 +769,13 @@ The immutable defaults may be overriden using $IMMUTABLE_QR and $IMMUTABLE_KEY.
 
 Errors during read die.  If the file does not exist undef is returned.
 
-=item C<-E<gt>write_ref>
+=item C<write_ref>
 
 Takes a file and the reference to be written.  Figures out the type
 of handler to use to write the file and writes it. If you used the ->read_ref
 use this method.  Otherwise, use ->write.
 
-=item C<-E<gt>write>
+=item C<write>
 
 Allows for writing back out the information read in by ->read.  If multiple
 paths where used - the directive 'FIRST' will write the changes to the first
@@ -769,7 +784,7 @@ immutable keys, then those keys are removed before writing.
 
 Errors during write die.
 
-=item C<-E<gt>preload_files>
+=item C<preload_files>
 
 Arguments are file(s) and/or directory(s) to preload.  preload_files will
 loop through the arguments, find the files that exist, read them in using
@@ -778,6 +793,36 @@ in %CACHE.  Directories are spidered for file extensions which match those
 listed in %EXT_READERS.  This is useful for a server environment where CPU
 may be more precious than memory.
 
+=item C<in_cache>
+
+Allow for testing if a particular filename is registered in the %CACHE - typically
+from a preload_files call.  This is useful when building wrappers around the
+conf_read and conf_write method calls.
+
+=back
+
+=head1 FUNCTIONS
+
+=over4
+
+=item conf_read
+
+Takes a filename.  Returns the read contents of that filename.  The handler
+to use is based upon the extention on the file.
+
+    my $hash = conf_read('/tmp/foo.yaml');
+
+    my $hash = conf_read('/tmp/foo', {file_type => 'yaml'});
+
+Takes a filename and a data structure.  Writes the data to the filename.  The handler
+to use is based upon the extention on the file.
+
+    conf_write('/tmp/foo.yaml', \%hash);
+
+    conf_write('/tmp/foo', \%hash, {file_type => 'yaml'});
+
+=back
+
 =head1 FILETYPES
 
 CGI::Ex::Conf supports the files found in %EXT_READERS by default.
index a841e8e88febbc325b181e2f759d3281a2eb2da0..c4d49f977f3d7d71ad25167eb0f40704129c636f 100644 (file)
@@ -17,7 +17,7 @@ use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION
 use strict;
 use Exporter;
 
-$VERSION   = '2.03';
+$VERSION   = '2.04';
 @ISA       = qw(Exporter);
 @EXPORT    = qw(dex dex_warn dex_text dex_html ctrace dex_trace);
 @EXPORT_OK = qw(dex dex_warn dex_text dex_html ctrace dex_trace debug);
@@ -76,9 +76,13 @@ sub _what_is_this {
   ### dump it out
   my @dump = map {&$SUB($_)} @_;
   my @var  = ('$VAR') x ($#dump + 1);
-  if ($line =~ s/^ .*\b \Q$called\E ( \(?\s* | \s+ )//x
-      && $line =~ s/(?:\s+if\s+.+)? ;? \s*$//x) {
-    $line =~ s/ \s*\) $ //x if $1 && $1 =~ /\(/;
+  my $hold;
+  if ($line =~ s/^ .*\b \Q$called\E ( \s* \( \s* | \s+ )//x
+      && ($hold = $1)
+      && (   $line =~ s/ \s* \b if \b .* \n? $ //x
+          || $line =~ s/ \s* ; \s* $ //x
+          || $line =~ s/ \s+ $ //x)) {
+    $line =~ s/ \s*\) $ //x if $hold =~ /^\s*\(/;
     my @_var = map {/^[\"\']/ ? 'String' : $_} split (/\s*,\s*/, $line);
     @var = @_var if $#var == $#_var;
   }
index 0947028efc42ddaf0f092296089eb07acdd00959..191f392d756f85da17f953a73bb48fcb3dbec0e1 100644 (file)
@@ -24,7 +24,7 @@ use vars qw($VERSION
 use base qw(Exporter);
 
 BEGIN {
-    $VERSION   = '2.03';
+    $VERSION   = '2.04';
     @EXPORT    = qw(form_fill);
     @EXPORT_OK = qw(fill form_fill html_escape get_tagval_by_key swap_tagval_by_key);
 };
diff --git a/lib/CGI/Ex/JSONDump.pm b/lib/CGI/Ex/JSONDump.pm
new file mode 100644 (file)
index 0000000..fe7c562
--- /dev/null
@@ -0,0 +1,358 @@
+package CGI::Ex::JSONDump;
+
+=head1 NAME
+
+CGI::Ex::JSONDump - Comprehensive data to JSON dump.
+
+=cut
+
+###----------------------------------------------------------------###
+#  Copyright 2006 - Paul Seamons                                     #
+#  Distributed under the Perl Artistic License without warranty      #
+###----------------------------------------------------------------###
+
+use vars qw($VERSION
+            @EXPORT @EXPORT_OK);
+use strict;
+use base qw(Exporter);
+
+BEGIN {
+    $VERSION  = '2.04';
+
+    @EXPORT = qw(JSONDump);
+    @EXPORT_OK = @EXPORT;
+
+};
+
+sub JSONDump {
+    my ($data, $args) = @_;
+    return __PACKAGE__->new($args)->dump($data);
+}
+
+###----------------------------------------------------------------###
+
+sub new {
+    my $class = shift || __PACKAGE__;
+    my $args  = shift || {};
+    my $self  = bless {%$args}, $class;
+
+    $self->{'skip_keys'} = {map {$_ => 1} ref($self->{'skip_keys'}) eq 'ARRAY' ? @{ $self->{'skip_keys'} } : $self->{'skip_keys'}}
+        if $self->{'skip_keys'} && ref $self->{'skip_keys'} ne 'HASH';
+
+    $self->{'sort_keys'} = 1 if ! exists $self->{'sort_keys'};
+
+    return $self;
+}
+
+sub dump {
+    my ($self, $data, $args) = @_;
+    $self = $self->new($args) if ! ref $self;
+
+    local $self->{'indent'}   = ! $self->{'pretty'} ? ''  : defined($self->{'indent'})   ? $self->{'indent'}   : '  ';
+    local $self->{'hash_sep'} = ! $self->{'pretty'} ? ':' : defined($self->{'hash_sep'}) ? $self->{'hash_sep'} : ' : ';
+    local $self->{'hash_nl'}  = ! $self->{'pretty'} ? ''  : defined($self->{'hash_nl'})  ? $self->{'hash_nl'}  : "\n";
+    local $self->{'array_nl'} = ! $self->{'pretty'} ? ''  : defined($self->{'array_nl'}) ? $self->{'array_nl'} : "\n";
+    local $self->{'str_nl'}   = ! $self->{'pretty'} ? ''  : defined($self->{'str_nl'})   ? $self->{'str_nl'}   : "\n";
+
+    return $self->_dump($data, '');
+}
+
+sub _dump {
+    my ($self, $data, $prefix) = @_;
+    my $ref = ref $data;
+
+    if ($ref eq 'CODE' && $self->{'play_coderefs'}) {
+        $data = $data->();
+        $ref = ref $data;
+    }
+
+    if ($ref eq 'HASH') {
+        my @keys = (grep { my $r = ref $data->{$_};
+                           ! $r || $self->{'handle_unknown_types'} || $r eq 'HASH' || $r eq 'ARRAY' || ($r eq 'CODE' && $self->{'play_coderefs'})}
+                    grep { ! $self->{'skip_keys'}    || ! $self->{'skip_keys'}->{$_} }
+                    grep { ! $self->{'skip_keys_qr'} || $_ !~ $self->{'skip_keys_qr'} }
+                    ($self->{'sort_keys'} ? (sort keys %$data) : (keys %$data)));
+        return "{}" if ! @keys;
+        return "{$self->{hash_nl}${prefix}$self->{indent}"
+            . join(",$self->{hash_nl}${prefix}$self->{indent}",
+                   map  { $self->js_escape($_, "${prefix}$self->{indent}")
+                              . $self->{'hash_sep'}
+                              . $self->_dump($data->{$_}, "${prefix}$self->{indent}") }
+                   @keys)
+            . "$self->{hash_nl}${prefix}}";
+
+    } elsif ($ref eq 'ARRAY') {
+        return "[]" if ! @$data;
+        return "[$self->{array_nl}${prefix}$self->{indent}"
+            . join(",$self->{array_nl}${prefix}$self->{indent}",
+                   map { $self->_dump($_, "${prefix}$self->{indent}") }
+                   @$data)
+            . "$self->{array_nl}${prefix}]";
+
+    } elsif ($ref) {
+        return $self->{'handle_unknown_types'}->($self, $data, $ref) if ref($self->{'handle_unknown_types'}) eq 'CODE';
+        return '"'.$data.'"'; ### don't do anything
+
+    } else {
+        return $self->js_escape($data, "${prefix}$self->{indent}");
+    }
+}
+
+sub js_escape {
+    my ($self, $str, $prefix) = @_;
+    return 'null'  if ! defined $str;
+
+    ### allow things that look like numbers to show up as numbers (and those that aren't quite to not)
+    return $str if $str =~ /^ -? (?: \d{0,13} \. \d* [1-9] | \d{1,13}) $/x;
+
+    my $quote = $self->{'single_quote'} ? "'" : '"';
+
+    $str =~ s/\\/\\\\/g;
+    $str =~ s/\r/\\\r/g;
+    $str =~ s/\t/\\\t/g;
+    $self->{'single_quote'} ? $str =~ s/\'/\\\'/g : $str =~ s/\"/\\\"/g;
+
+    ### allow for really odd chars
+    $str =~ s/([\x00-\x07\x0b\x0e-\x1f])/'\\u00' . unpack('H2',$1)/eg; # from JSON::Converter
+    utf8::decode($str) if $self->{'utf8'} && &utf8::decode;
+
+    ### escape <html> and </html> tags in the text
+    $str =~ s{(</? (?: htm | scrip | !-))}{$1$quote+$quote}gx;
+
+    ### add nice newlines (unless pretty is off)
+    if ($self->{'str_nl'} && length($str) > 80) {
+        if ($self->{'single_quote'}) {
+            $str =~ s/\'\s*\+\'$// if $str =~ s/\n/\\n\'$self->{str_nl}${prefix}+\'/g;
+        } else {
+            $str =~ s/\"\s*\+\"$// if $str =~ s/\n/\\n\"$self->{str_nl}${prefix}+\"/g;
+        }
+    } else {
+        $str =~ s/\n/\\n/g;
+    }
+
+    return $quote . $str . $quote;
+}
+
+1;
+
+__END__
+
+=head1 SYNOPSIS
+
+    use CGI::Ex::JSONDump;
+
+    my $js = JSONDump(\%complex_data, {pretty => 0});
+
+    ### OR
+
+    my $js = CGI::Ex::JSONDump->new({pretty => 0})->dump(\%complex_data);
+
+=head1 DESCRIPTION
+
+CGI::Ex::JSONDump is a very lightweight and fast perl data structure to javascript object
+notation dumper.  This is useful for AJAX style methods, or dynamic page creation that
+needs to embed perl data in the presented page.
+
+CGI::Ex::JSONDump has roughly the same output as JSON::objToJson, but with the following
+differences:
+
+    - CGI::Ex::JSONDump is much much lighter and smaller (a whopping 134 lines).
+    - It dumps Javascript in more browser friendly format (handling of </script> tags).
+    - It removes unknown key types by default instead of dying.
+    - It allows for a general handler to handle unknown key types.
+    - It allows for fine grain control of all whitespace.
+    - It allows for skipping keys by name or by regex.
+    - It dumps both data structures and scalar types.
+
+=head1 METHODS
+
+=over 4
+
+=item new
+
+Create a CGI::Ex::JSONDump object.  Takes arguments hashref as single argument.
+
+    my $obj = CGI::Ex::JSONDump->new(\%args);
+
+See the arguments section for a list of the possible arguments.
+
+=item dump
+
+Takes a perl data structure or scalar string or number and returns a string
+containing the javascript representation of that string (in Javascript object
+notation - JSON).
+
+=item js_escape
+
+Takes a scalar string or number and returns a javascript escaped string that will
+embed properly in javascript.  All numbers and strings of nested data structures
+are passed through this method.
+
+=back
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item JSONDump
+
+A wrapper around the new and dump methods.  Takes a structure to dump
+and optional args to pass to the new routine.
+
+    JSONDump($data, $args);
+
+Is the same as:
+
+    CGI::Ex::JSONDump->new($args)->dump($data);
+
+=back
+
+=head1 ARGUMENTS
+
+The following arguments may be passed to the new method or as the second
+argument to the JSONDump function.
+
+=over 4
+
+=item pretty
+
+0 or 1.  Default 0 (false).  If true then dumped structures will
+include whitespace to make them more readable.
+
+     JSONDump({a => [1, 2]}, {pretty => 0});
+     JSONDump({a => [1, 2]}, {pretty => 1});
+
+     Would print
+
+     {"a":[1,2]}
+     {
+       "a" : [
+         1,
+         2,
+       ]
+     }
+
+=item single_quote
+
+0 or 1.  Default 0 (false).  If true then escaped values will be quoted
+with single quotes.  Otherwise values are quoted with double quotes.
+
+     JSONDump("a", {single_quote => 0});
+     JSONDump('a', {single_quote => 0});
+
+     Would print
+
+     "a"
+     'a'
+
+=item sort_keys
+
+0 or 1.  Default 1 (true)
+
+If true, then key/value pairs of hashrefs will be sorted will be output in sorted order.
+
+=item play_coderefs
+
+0 or 1.  Default 0 (false).  If true, then any code refs will be executed
+and the returned string will be dumped.
+
+If false, then keys of hashrefs that contain coderefs will be skipped (unless
+the handle_unknown_types property is set).  Coderefs
+that are in arrayrefs will show up as "CODE(0x814c648)" unless
+the handle_unknown_types property is set.
+
+=item handle_unknown_types
+
+Default undef.  If true it should contain a coderef that will be called if any
+unknown types are encountered.  The only default known types are scalar string
+or number values, unblessed HASH refs and ARRAY refs (and CODE refs if the
+play_coderefs property is set).  All other types will be passed to the
+handle_unknown_types method call.
+
+    JSONDump({a => bless({}, 'A'), b => 1}, {
+        handle_unknown_types => sub {
+            my $self = shift; # a JSON object
+            my $data = shift; # the object to dump
+
+            return $self->js_escape("Ref=" . ref $data);
+        },
+        pretty => 0,
+    });
+
+    Would print
+
+    {"a":"Ref=A","b":1}
+
+If the handle_unknown_types method is not set then keys hashrefs that have values
+with unknown types will not be included in the javascript output.
+
+    JSONDump({a => bless({}, 'A'), b => 1}, {pretty => 0});
+
+    Would print
+
+    {"b":1}
+
+=item skip_keys
+
+Should contain an arrayref of keys or a hashref whose keys are the keys to skip.  Default
+is unset.  Any keys of hashrefs that are in the skip_keys item will not be included in
+the javascript output.
+
+    JSONDump({a => 1, b => 1}, {skip_keys => ['a'], pretty => 0});
+
+    Would print
+
+    {"b":1}
+
+=item skip_keys_qr
+
+Similar to skip_keys but should contain a regex.  Any keys of hashrefs that match the
+skip_keys_qr regex will not be included in the javascript output.
+
+    JSONDump({a => 1, _b => 1}, {skip_keys_qr => qr/^_/, pretty => 0});
+
+    Would print
+
+    {"a":1}
+
+=item indent
+
+The level to indent each nested data structure level if pretty is true.  Default is "  ".
+
+=item hash_nl
+
+The whitespace to add after each hashref key/value pair if pretty is true.  Default is "\n".
+
+=item hash_sep
+
+The separator and whitespace to put between each hashref key/value pair if pretty is true.  Default is " : ".
+
+=item array_nl
+
+The whitespace to add after each arrayref entry if pretty is true.  Default is "\n".
+
+=item str_nl
+
+The whitespace to add in between newline separated strings if pretty is true or the output line is
+greater than 80 characters.  Default is "\n".
+
+    JSONDump("This is a long string\n"
+             ."with plenty of embedded newlines\n"
+             ."and is greater than 80 characters.\n", {pretty => 1, str_nl => "\n"});
+
+    Would print
+
+    "This is a long string\n"
+      +"with plenty of embedded newlines\n"
+      +"and is greater than 80 characters.\n"
+
+If the string is less than 80 characters, or if str_nl is set to '', then the escaped
+string will be contained on a single line.
+
+=back
+
+=head1 AUTHORS
+
+Paul Seamons <paul at seamons dot com>
+
+=cut
index 75886ef629274ac705423f1c6d48944a8cb202b2..6c95025d56646ae428c69a454eb148dea94ef552 100644 (file)
@@ -39,7 +39,7 @@ use vars qw($VERSION
             );
 
 BEGIN {
-    $VERSION = '2.03';
+    $VERSION = '2.04';
 
     $PACKAGE_EXCEPTION   = 'CGI::Ex::Template::Exception';
     $PACKAGE_ITERATOR    = 'CGI::Ex::Template::Iterator';
index cd7dd094f9366e6d0410f6658e8cf051801b92e4..83545c41b1845b080255d7bc0e79cefb81b029a0 100644 (file)
@@ -399,13 +399,8 @@ CET has its own built in recursive grammar system.
 
 =item There are no references.
 
-There was in initial beta tests, but it was decided to remove the little used feature.
-
-It makes it the same as
-
-    [% obj.method("foo") %]
-
-This is removed in CET.
+There were in initial beta tests, but it was decided to remove the little used feature which
+took a length of code to implement.
 
 =item The DEBUG directive is more limited.
 
@@ -416,7 +411,8 @@ are on rather than a general line range.
 
 =item There is no ANYCASE configuration item.
 
-There was in initial beta tests, but it was dropped in favor of consistent parsing syntax.
+There was in initial beta tests, but it was dropped in favor of consistent parsing syntax (and
+a minimal amount of speedup).
 
 =item There is no V1DOLLAR configuration item.
 
@@ -751,7 +747,8 @@ object (except for true filters such as eval and redirect).
 
 =item '0'
 
-    [% item = 'foo' %][% item.0 %] Returns self.  Allows for scalars to mask as arrays.
+    [% item = 'foo' %][% item.0 %] Returns self.  Allows for scalars to mask as arrays (scalars
+                                   already will, but this allows for more direct access).
 
 =item as
 
@@ -797,7 +794,8 @@ This is a filter and is not available via the Text virtual object.
 
 =item format
 
-    [% item.format('%d') %] Print the string out in the specified format.  Each line is
+    [% item.format('%d') %] Print the string out in the specified format.  It is similar to
+    the "as" virtual method, except that the item is split on newline and each line is
     processed separately.
 
 =item hash
@@ -847,7 +845,7 @@ Note: This filter is not available as of TT2.15.
 
 =item remove
 
-    [% item.remove("\s+") %] Same as remove - but is global and replaces with nothing.
+    [% item.remove("\s+") %] Same as replace - but is global and replaces with nothing.
 
 =item redirect
 
@@ -868,13 +866,13 @@ This is a filter and is not available via the Text virtual object.
 
     [% item.replace("\s+", "&nbsp;") %] Globally replace all space with &nbsp;
 
-    [% item.replace("foo", "bar", 0) Replace only the first instance of foo with bar.
+    [% item.replace("foo", "bar", 0) %] Replace only the first instance of foo with bar.
 
     [% item.replace("(\w+)", "($1)") %] Surround all words with parenthesis.
 
 =item search
 
-    [% item.search("(\w+)" %] Tests if the given pattern is in the string.
+    [% item.search("(\w+)") %] Tests if the given pattern is in the string.
 
 =item size
 
@@ -2195,7 +2193,7 @@ Collapse adjacent whitespace to a single space.  The "=" is used to indicate CHO
 
     Hello.
 
-    [%- "Hi." -%]
+    [%= "Hi." =%]
 
     Howdy.
 
@@ -2209,7 +2207,7 @@ Remove all adjacent whitespace.  The "~" is used to indicate CHOMP_GREEDY.
 
     Hello.
 
-    [%- "Hi." -%]
+    [%~ "Hi." ~%]
 
     Howdy.
 
@@ -2350,9 +2348,9 @@ Allow for passing in TT style filters.
 
     my $str = q{
         [% a = "Hello" %]
-        1([% a | filter1 %])
-        2([% a | filter2 %])
-        3([% a | filter3 %])
+        1 ([% a | filter1 %])
+        2 ([% a | filter2 %])
+        3 ([% a | filter3 %])
     };
 
     my $obj = CGI::Ex::Template->new(FILTERS => $filters);
@@ -2360,9 +2358,9 @@ Allow for passing in TT style filters.
 
 Would print:
 
-        (11111)
-        (22222)
-        (33333)
+        (11111)
+        (22222)
+        (33333)
 
 Filters passed in as an arrayref should contain a coderef and a value
 indicating if they are dynamic or static (true meaning dynamic).  The
index 2749642b763de75bc73a7a2dd412727195af2379..16591d51aee1145fcf940560da993db6a41a310d 100644 (file)
@@ -22,7 +22,7 @@ use vars qw($VERSION
             @UNSUPPORTED_BROWSERS
             );
 
-$VERSION = '2.03';
+$VERSION = '2.04';
 
 $DEFAULT_EXT   = 'val';
 $QR_EXTRA      = qr/^(\w+_error|as_(array|string|hash)_\w+|no_\w+)/;
@@ -126,6 +126,7 @@ sub validate {
       next if $found{$field};
       my $field_val = $group_val->{$field};
       die "Found a nonhashref value on field $field" if ! UNIVERSAL::isa($field_val, 'HASH');
+      $field_val->{'field'} = $field if ! defined $field_val->{'field'};
       push @$fields, $field_val;
     }
 
@@ -795,7 +796,16 @@ sub generate_js {
         "$js_uri_path/CGI/Ex/validate.js";
     };
 
-    if (eval { require JSON }) {
+    if (! $self->{'no_jsondump'} && eval { require CGI::Ex::JSONDump }) {
+        my $json = CGI::Ex::JSONDump->new({pretty => 1})->dump($val_hash);
+        return qq{<script src="$js_uri_path_validate"></script>
+<script>
+document.validation = $json;
+if (document.check_form) document.check_form("$form_name");
+</script>
+};
+
+    } elsif (! $self->{'no_json'} && eval { require JSON }) {
         my $json = JSON->new(pretty => 1)->objToJson($val_hash);
 
         return qq{<script src="$js_uri_path_validate"></script>
index a3b98ebb912a2ce16457fadbd88fba1554542340..d8a62704c4bfdfe04b9ac0f212f0187d46e5fe94 100644 (file)
@@ -4,7 +4,7 @@
 *  Based upon CGI/Ex/Validate.pm v1.14 from Perl                     *
 *  For instructions on usage, see perldoc of CGI::Ex::Validate       *
 ***----------------------------------------------------------------**/
-// $Revision: 1.35 $
+// $Revision: 1.36 $
 
 function Validate () {
  this.error             = vob_error;
@@ -292,7 +292,8 @@ function vob_validate_buddy (form, field, field_val, N_level, ifs_match) {
    for (var i = 0; i < tests.length; i ++) {
      var el = form[field];
      var type = el.type;
-     if (type && (type == 'hidden' || type == 'password' || type == 'text' || type == 'textarea' || type == 'submit')) el.value = values[0] = field_val[tests[i]];
+     if (type && (type == 'hidden' || type == 'password' || type == 'text' || type == 'textarea' || type == 'submit'))
+       el.value = values[0] = '' + field_val[tests[i]];
    }
  }
 
diff --git a/samples/app/cgi_ex_1.cgi b/samples/app/cgi_ex_1.cgi
new file mode 100755 (executable)
index 0000000..16ce4ba
--- /dev/null
@@ -0,0 +1,183 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+cgi_ex_1.cgi - Show a basic example using some of the CGI::Ex tools (but not App based)
+
+=cut
+
+if (__FILE__ eq $0) {
+    main();
+}
+
+###----------------------------------------------------------------###
+
+use strict;
+use CGI::Ex;
+use CGI::Ex::Validate ();
+use CGI::Ex::Dump qw(debug);
+
+###----------------------------------------------------------------###
+
+sub main {
+    my $cgix = CGI::Ex->new;
+    my $vob  = CGI::Ex::Validate->new;
+    my $form = $cgix->get_form();
+
+    ### allow for js validation libraries
+    ### path_info should contain something like /CGI/Ex/yaml_load.js
+    ### see the line with 'js_val' below
+    my $info = $ENV{PATH_INFO} || '';
+    if ($info =~ m|^(/\w+)+.js$|) {
+        $info =~ s|^/+||;
+        $cgix->print_js($info);
+        return;
+    }
+
+
+    ### check for errors - if they have submitted information
+    my $has_info = ($form->{'processing'}) ? 1 : 0;
+    my $errob = $has_info ? $vob->validate($form, validation_hash()) : undef;
+    my $form_name = 'formfoo';
+
+    ### failed validation - send out the template
+    if (! $has_info || $errob) {
+
+        ### get a template and swap defaults
+        my $swap = defaults_hash();
+
+        ### add errors to the swap (if any)
+        if ($errob) {
+            my $hash = $errob->as_hash();
+            $swap->{$_} = delete($hash->{$_}) foreach keys %$hash;
+            $swap->{'error_header'} = 'Please correct the form information below';
+        }
+
+        ### get js validation ready
+        $swap->{'form_name'} = $form_name;
+        $swap->{'js_val'} = $vob->generate_js(validation_hash(), # filename or valhash
+                                              $form_name,         # name of form
+                                              $ENV{'SCRIPT_NAME'}); # browser path to cgi that calls print_js
+
+        ### swap in defaults, errors and js_validation
+        my $content = $cgix->swap_template(get_content_form(), $swap);
+
+        ### fill form fields
+        $cgix->fill(\$content, $form);
+        #debug $content;
+
+        ### print it out
+        $cgix->print_content_type();
+        print $content;
+        return;
+    }
+
+    debug $form;
+
+    ### show some sort of success if there were no errors
+    $cgix->print_content_type;
+    my $content = $cgix->swap_template(get_content_success(), defaults_hash());
+    print $content;
+    return;
+
+}
+
+###----------------------------------------------------------------###
+
+sub validation_hash {
+    return {
+        'group order' => ['username', 'password', 'password_verify'],
+        username => {
+            required => 1,
+            min_len  => 3,
+            max_len  => 30,
+            match    => 'm/^\w+$/',
+            # could probably all be done with match => 'm/^\w{3,30}$/'
+        },
+        password => {
+            required => 1,
+            max_len  => 20,
+        },
+        password_verify => {
+            validate_if => 'password',
+            equals      => 'password',
+        },
+    };
+}
+
+sub defaults_hash {
+    return {
+        title       => 'My Application',
+        script_name => $ENV{'SCRIPT_NAME'},
+        color       => ['#ccccff', '#aaaaff'],
+    }
+}
+
+###----------------------------------------------------------------###
+
+sub get_content_form {
+  return qq{
+    <html>
+    <head>
+      <title>[% title %]</title>
+      <style>
+      .error {
+        display: block;
+        color: red;
+        font-weight: bold;
+      }
+      </style>
+    </head>
+    <body>
+    <h1 style='color:blue'>Please Enter information</h1>
+    <span style='color:red'>[% error_header %]</span>
+    <br>
+
+    <form name="[% form_name %]" action="[% script_name %]" method="POST">
+    <input type=hidden name=processing value=1>
+
+    <table>
+    <tr bgcolor=[% color.0 %]>
+      <td>Username:</td>
+      <td>
+        <input type=text size=30 name=username>
+        <span class=error id=username_error>[% username_error %]</span></td>
+    </tr>
+    <tr bgcolor=[% color.1 %]>
+      <td>Password:</td>
+      <td><input type=password size=20 name=password>
+        <span class=error id=password_error>[% password_error %]</span></td>
+    </tr>
+    <tr bgcolor=[% color.0 %]>
+      <td>Password Verify:</td>
+      <td><input type=password size=20 name=password_verify>
+        <span class=error id=password_verify_error>[% password_verify_error %]</span></td>
+    </tr>
+    <tr bgcolor=[% color.1 %]>
+      <td colspan=2 align=right><input type=submit value=Submit></td>
+    </tr>
+
+    </table>
+
+    </form>
+
+    [% js_val %]
+    </body>
+    </html>
+  };
+}
+
+sub get_content_success {
+  return qq{
+    <html>
+    <head><title>[% title %]</title></head>
+    <body>
+    <h1 style='color:green'>Success</h1>
+    <br>
+    print "I can now continue on with the rest of my script!";
+    </body>
+    </html>
+  };
+}
+
+__END__
diff --git a/samples/app/cgi_ex_2.cgi b/samples/app/cgi_ex_2.cgi
new file mode 100755 (executable)
index 0000000..73f6e81
--- /dev/null
@@ -0,0 +1,144 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+cgi_ex_2.cgi - Rewrite of cgi_ex_1.cgi using CGI::Ex::App
+
+=cut
+
+use strict;
+use base qw(CGI::Ex::App);
+use CGI::Ex::Dump qw(debug);
+
+if ($0 eq __FILE__) {
+    __PACKAGE__->navigate;
+}
+
+### show what hooks ran when we are done
+sub post_navigate { debug shift->dump_history }
+
+### this will work for both userinfo_hash_common and _success_hash_common
+sub hash_common {
+    return {
+        title => 'My Application',
+        color => ['#ccccff', '#aaaaff'],
+    };
+}
+
+###----------------------------------------------------------------###
+
+sub main_hash_validation {
+    return {
+        'group order' => ['username', 'password'],
+        username => {
+            required => 1,
+            min_len  => 3,
+            max_len  => 30,
+            match    => 'm/^\w+$/',
+            # could probably all be done with match => 'm/^\w{3,30}$/'
+        },
+        password => {
+            required => 1,
+            max_len  => 20,
+        },
+        password_verify => {
+            validate_if => 'password',
+            equals      => 'password',
+        },
+    };
+}
+
+sub main_finalize {
+    my $self = shift;
+    my $form = $self->form;
+    debug $form;
+    return 1;
+}
+
+sub main_next_step { '_success' }
+
+sub main_file_print {
+    return \ qq {
+    <html>
+    <head>
+      <title>[% title %]</title>
+      <style>
+      .error {
+        display: block;
+        color: red;
+        font-weight: bold;
+      }
+      </style>
+    </head>
+    <body>
+    <h1 style='color:blue'>Please Enter information</h1>
+    <span style='color:red'>[% error_header %]</span>
+    <br>
+
+    <form name="[% form_name %]" action="[% script_name %]" method="POST">
+    <input type=hidden name=processing value=1>
+
+    <table>
+    <tr bgcolor=[% color.0 %]>
+      <td>Username:</td>
+      <td>
+        <input type=text size=30 name=username>
+        <span class=error id=username_error>[% username_error %]</span></td>
+    </tr>
+    <tr bgcolor=[% color.1 %]>
+      <td>Password:</td>
+      <td><input type=password size=20 name=password>
+        <span class=error id=password_error>[% password_error %]</span></td>
+    </tr>
+    <tr bgcolor=[% color.0 %]>
+      <td>Password Verify:</td>
+      <td><input type=password size=20 name=password_verify>
+        <span class=error id=password_verify_error>[% password_verify_error %]</span></td>
+    </tr>
+    <tr bgcolor=[% color.1 %]>
+      <td colspan=2 align=right><input type=submit value=Submit></td>
+    </tr>
+
+    </table>
+
+    </form>
+
+    [% js_validation %]
+    </body>
+    </html>
+};
+}
+
+###----------------------------------------------------------------###
+
+sub _success_file_print {
+    return \ qq{
+    <html>
+    <head><title>[% title %]</title></head>
+    <body>
+    <h1 style='color:green'>Success</h1>
+    <br>
+    print "I can now continue on with the rest of my script!";
+    </body>
+    </html>
+};
+}
+
+###----------------------------------------------------------------###
+### These methods override the base functionality of CGI::Ex::App
+
+sub ready_validate { shift->form->{'processing'} ? 1 : 0 }
+
+sub set_ready_validate {
+    my $self = shift;
+    my ($step, $is_ready) = (@_ == 2) ? @_ : (undef, shift);
+    if ($is_ready) {
+        $self->form->{'processing'} = 1;
+    } else {
+        delete $self->form->{'processing'};
+    }
+}
+
+
+__END__
+
diff --git a/samples/benchmark/bench_jsondump.pl b/samples/benchmark/bench_jsondump.pl
new file mode 100755 (executable)
index 0000000..38c1594
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/perl -w
+
+# Benchmark: running cejd, json, zejd for at least 2 CPU seconds...
+#       cejd:  4 wallclock secs ( 2.18 usr +  0.00 sys =  2.18 CPU) @ 7045.87/s (n=15360)
+#       json:  3 wallclock secs ( 2.16 usr +  0.00 sys =  2.16 CPU) @ 6634.26/s (n=14330)
+#       zejd:  3 wallclock secs ( 2.16 usr +  0.00 sys =  2.16 CPU) @ 6634.26/s (n=14330)
+#        Rate zejd json cejd
+# zejd 6634/s   --   0%  -6%
+# json 6634/s   0%   --  -6%
+# cejd 7046/s   6%   6%   --
+#
+# Benchmark: running cejd, json for at least 2 CPU seconds...
+#       cejd:  3 wallclock secs ( 2.04 usr +  0.00 sys =  2.04 CPU) @ 5690.20/s (n=11608)
+#       json:  2 wallclock secs ( 2.06 usr +  0.00 sys =  2.06 CPU) @ 5291.75/s (n=10901)
+#        Rate json cejd
+# json 5292/s   --  -7%
+# cejd 5690/s   8%   --
+#
+# Benchmark: running cejd, json for at least 2 CPU seconds...
+#       cejd:  4 wallclock secs ( 2.21 usr +  0.00 sys =  2.21 CPU) @ 24320.81/s (n=53749)
+#       json:  3 wallclock secs ( 2.14 usr +  0.00 sys =  2.14 CPU) @ 10048.13/s (n=21503)
+#         Rate json cejd
+# json 10048/s   -- -59%
+# cejd 24321/s 142%   --
+
+use strict;
+
+use Benchmark qw(cmpthese timethese);
+use JSON;
+use CGI::Ex::JSONDump;
+
+my $json = JSON->new(pretty => 0, keysort => 0);
+my $cejd = CGI::Ex::JSONDump->new({pretty => 0, no_sort => 1});
+
+
+my $data = {
+    one   => 'two',
+    three => [qw(a b c)],
+    four  => 1,
+    five  => '1.0',
+    six   => undef,
+};
+
+print "JSON\n--------------------\n". $json->objToJson($data)."\n----------------------------\n";
+print "CEJD\n--------------------\n". $cejd->dump($data)     ."\n----------------------------\n";
+
+cmpthese timethese(-2, {
+    json => sub { my $a = $json->objToJson($data) },
+    cejd => sub { my $a = $cejd->dump($data) },
+    zejd => sub { my $a = $cejd->dump($data) },
+});
+
+###----------------------------------------------------------------###
+
+$json = JSON->new(pretty => 1, keysort => 1);
+$cejd = CGI::Ex::JSONDump->new({pretty => 1});
+
+$data = {
+    one   => 'two',
+    three => [qw(a b c)],
+    four  => 1,
+    five  => '1.0',
+    six   => '12345678901234567890',
+    seven => undef,
+};
+
+print "JSON\n--------------------\n". $json->objToJson($data)."\n----------------------------\n";
+print "CEJD\n--------------------\n". $cejd->dump($data)     ."\n----------------------------\n";
+
+cmpthese timethese(-2, {
+    json => sub { my $a = $json->objToJson($data) },
+    cejd => sub { my $a = $cejd->dump($data) },
+});
+
+###----------------------------------------------------------------###
+
+$json = JSON->new(pretty => 1);
+$cejd = CGI::Ex::JSONDump->new({pretty => 1});
+
+$data = ["foo\n<script>\nThis is sort of \"odd\"\n</script>"];
+
+print "JSON\n--------------------\n". $json->objToJson($data)."\n----------------------------\n";
+print "CEJD\n--------------------\n". $cejd->dump($data)     ."\n----------------------------\n";
+
+cmpthese timethese(-2, {
+    json => sub { my $a = $json->objToJson($data) },
+    cejd => sub { my $a = $cejd->dump($data) },
+});
index 450078b1c3d3c6d8c89d16ad63f114e8393013ae..7b48028a0deb5e232f6942d60c62616b7cb1aeb0 100644 (file)
@@ -83,6 +83,27 @@ my $longer_template = "[% INCLUDE bar.tt %]"
     ."[% array.join('|') %]"
     ."[% PROCESS bar.tt %]";
 
+my $hello2000 = "<html><head><title>[% title %]</title></head><body>
+[% array = [ \"Hello\", \"World\", \"2000\", \"Hello\", \"World\", \"2000\" ] %]
+[% sorted = array.sort %]
+[% multi = [ sorted, sorted, sorted, sorted, sorted ] %]
+<table>
+[% FOREACH row = multi %]
+  <tr bgcolor=\"[% loop.count % 2 ? 'gray' : 'white' %]\">
+  [% FOREACH col = row %]
+    <td align=\"center\"><font size=\"+1\">[% col %]</font></td>
+  [% END %]
+  </tr>
+[% END %]
+</table>
+[% param = integer %]
+[% FOREACH i = [ 1 .. 10 ] %]
+  [% var = i + param %]"
+  .("\n  [%var%] Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World <br/>"x20)."
+[% END %]
+</body></html>
+";
+
 ###----------------------------------------------------------------###
 ### set a few globals that will be available in our subs
 my $show_list = grep {$_ eq '--list'} @ARGV;
@@ -146,7 +167,8 @@ my $tests = {                                                             #
     '43_filteruri' => "[% ' ' | uri %]",                                  #  132%  #  550%  #  379%  #  471%  # 12524.4/s #
     '44_filterevl' => "[% foo | eval %]",                                 #  303%  #  530%  #  434%  #  478%  # 5475.5/s #
     '45_capture'   => "[% foo = BLOCK %]Hi[% END %][% foo %]",            #  102%  #  386%  #  291%  #  304%  # 10606.5/s #
-    '46_complex'   => "$longer_template",                                 #   55%  #  288%  #  133%  #  251%  # 1230.3/s #
+    '46_complex'   => "$longer_template",                                 #   60%  #  290%  #  160%  #  270%  # 1054.3/s #
+    '47_hello2000' => "$hello2000",                                       #    2%  #  136%  #   39%  #  115%  # 184.8/s #
     # overall                                                             #   95%  #  406%  #  251%  #  346%  #
 
 
diff --git a/samples/index.cgi b/samples/index.cgi
new file mode 100755 (executable)
index 0000000..eb9a399
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/perl -w
+
+=head1 NAME
+
+index.cgi - Show a listing of available utilties in the samples directories.
+
+=cut
+
+use strict;
+use base qw(CGI::Ex::App);
+use FindBin qw($Bin);
+
+__PACKAGE__->navigate;
+
+sub main_file_print {
+    return \ q{<html>
+<head><title>CGI::Ex Samples</title></head>
+<body>
+<h1>CGI::Ex Samples</h1>
+Looking at directory: [% base %]<br>
+All of the samples in this directory should be ready to run.  To
+enable this directory you should use something similar to the following in your apache conf file:
+<pre>
+ScriptAlias /samples/ /home/pauls/perl/CGI-Ex/samples/
+&lt;Location /samples/>
+    SetHandler perl-script
+    PerlResponseHandler ModPerl::PerlRun
+    Options +ExecCGI
+&lt;/Location>
+</pre>
+For mod_perl 1 you would use something similar to:
+<pre>
+ScriptAlias /samples/ /home/pauls/perl/CGI-Ex/samples/
+&lt;Location /samples/>
+    SetHandler perl-script
+    PerlHandler Apache::PerlRun
+    Options +ExecCGI
+&lt;/Location>
+</pre>
+
+<h2>Application examples</h2>
+[% FOREACH file = app.keys.sort ~%]
+<a href="[% script_dir ~ file %]">[% script_dir ~ file %]</a> - [% app.$file %]<br>
+[% END -%]
+
+<h2>Benchmark stuff</h2>
+[% FOREACH file = bench.keys.sort ~%]
+[% file %] - [% bench.$file %]<br>
+[% END -%]
+
+<h2>Other files</h2>
+[% FOREACH file = therest.keys.sort ~%]
+[% file %] - [% therest.$file %]<br>
+[% END -%]
+
+</body>
+</html>
+    };
+}
+
+sub main_hash_swap {
+    my $self = shift;
+    my $base = $self->base_dir_abs;
+    my $hash = {};
+    my %file;
+
+    require File::Find;
+    File::Find::find(sub {
+        return if ! -f;
+        return if $File::Find::name =~ / CVS | ~$ | ^\# /x;
+        $File::Find::name =~ /^\Q$base\E(.+)/ || return;
+        my $name = $1;
+        my $desc = '';
+        if (open FH, "<$_") {
+            read FH, my $str, -s;
+            close FH;
+            if ($str =~ /^=head1 NAME\s+(.+)\s+^=cut\s+/m) {
+                $desc = $1;
+                $desc =~ s/^\w+(?:\.\w+)?\s+-\s+//;
+            }
+        }
+        $file{$name} = $desc;
+    }, $base);
+
+    $hash->{'base'}    = $base;
+
+    $hash->{'script_dir'} = $ENV{'SCRIPT_NAME'} || $0;
+    $hash->{'script_dir'} =~ s|/[^/]+$||;
+
+    $hash->{'app'}     = {map {$_ => $file{$_}} grep {/app/ && /\.cgi$/}    keys %file};
+
+    $hash->{'bench'}   = {map {$_ => $file{$_}} grep {/bench/ && /\.pl$/}   keys %file};
+
+    $hash->{'therest'} = {map {$_ => $file{$_}} grep {! exists $hash->{'bench'}->{$_}
+                                                      && ! exists $hash->{'app'}->{$_}} keys %file};
+
+    return $hash;
+}
+
+sub base_dir_abs {
+    my $dir = $0;
+    $dir =~ s|/[^/]+$||;
+    return $dir;
+}
+
index 28b17105331b72f6328abd0bb60894ba259827a8..6b03cdf39e08ff60d39214a47e2ec3d68ea3fee8 100644 (file)
@@ -10,6 +10,7 @@ my $swap = {
 };
 
 my $txt  = "[% one %][% two %][% three %][% hash.keys.join %] [% code(one).length %] [% hash.\$a_var %]\n";
+#$txt = hello2000();
 
 ###----------------------------------------------------------------###
 
@@ -37,3 +38,26 @@ sleep 15; # go and check the 'ps fauwx|grep perl'
 
 
 ###----------------------------------------------------------------###
+
+sub hello2000 {
+    my $hello2000 = "<html><head><title>[% title %]</title></head><body>
+[% array = [ \"Hello\", \"World\", \"2000\", \"Hello\", \"World\", \"2000\" ] %]
+[% sorted = array.sort %]
+[% multi = [ sorted, sorted, sorted, sorted, sorted ] %]
+<table>
+[% FOREACH row = multi %]
+  <tr bgcolor=\"[% loop.count % 2 ? 'gray' : 'white' %]\">
+  [% FOREACH col = row %]
+    <td align=\"center\"><font size=\"+1\">[% col %]</font></td>
+  [% END %]
+  </tr>
+[% END %]
+</table>
+[% param = integer %]
+[% FOREACH i = [ 1 .. 10 ] %]
+  [% var = i + param %]"
+  .("\n  [%var%] Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World Hello World <br/>"x20)."
+[% END %]
+</body></html>
+";
+}
index c47492c3c8d7be119a9a5f2ac6b73ffad3ff7dda..4b5cbfdef5b23d9c7311b2deaa081a40ea3c93a8 100644 (file)
@@ -7,7 +7,7 @@
 =cut
 
 use strict;
-use Test::More tests => 33;
+use Test::More tests => 35;
 
 use_ok('CGI::Ex::Auth');
 
@@ -33,7 +33,7 @@ use_ok('CGI::Ex::Auth');
     use vars qw($crypt);
     BEGIN { $crypt = crypt('123qwe', 'SS') };
     sub use_crypt { 1 }
-    sub get_pass_by_user { $crypt }
+    sub get_pass_by_user { {password => $crypt, foobar => 'baz'} }
 }
 
 my $token = Auth->new->generate_token({user => 'test', real_pass => '123qwe', use_base64 => 1});
@@ -97,6 +97,11 @@ ok(! $Auth::set_cookie, "Set_cookie was not called");
 ok($Auth::deleted_cookie, "deleted_cookie was not called");
 
 
+my $auth = Aut2->get_valid_auth({form => {%$form_good3}});
+my $data = $auth->last_auth_data;
+ok($auth && $data, "Aut2 worked again");
+ok($data->{'foobar'} eq 'baz', 'And it contained the correct value');
+
 SKIP: {
     skip("Crypt::Blowfish not found", 4) if ! eval { require Crypt::Blowfish };
 
diff --git a/t/9_jsondump_00_base.t b/t/9_jsondump_00_base.t
new file mode 100644 (file)
index 0000000..a124a4f
--- /dev/null
@@ -0,0 +1,109 @@
+# -*- Mode: Perl; -*-
+
+=head1 NAME
+
+9_jsondump_00_base.t - Testing of the CGI::Ex::JSONDump module.
+
+=cut
+
+use strict;
+use Test::More tests => 49;
+
+use_ok('CGI::Ex::JSONDump');
+
+ok(eval { CGI::Ex::JSONDump->import('JSONDump'); 1 }, "Import JSONDump");
+
+ok(&JSONDump, "Got the sub");
+
+my $obj = CGI::Ex::JSONDump->new;
+
+ok(JSONDump({a => 1}) eq $obj->dump({a => 1}), "Function and OO Match");
+
+ok($obj->dump("foo") eq $obj->js_escape("foo"), "js_escape works");
+
+sub test_dump {
+    my $data = shift;
+    my $str  = shift;
+    my $args = shift || {};
+    my ($sub, $file, $line) = caller;
+
+    my $out = JSONDump($data, $args);
+
+    if ($out eq $str) {
+        ok(1, "Dump matched at line $line");
+    } else {
+        ok(0, "Didn't match at line $line - shouldv'e been"
+           ."\n---------------------\n"
+           . $str
+           ."\n---------------------\n"
+           ."Was"
+           ."\n---------------------\n"
+           . $out
+           ."\n---------------------\n"
+           );
+    }
+}
+
+###----------------------------------------------------------------###
+
+test_dump({a => 1}, "{\n  \"a\" : 1\n}", {pretty => 1});
+test_dump({a => 1}, "{\"a\":1}", {pretty => 0});
+
+test_dump([1, 2, 3], "[\n  1,\n  2,\n  3\n]", {pretty => 1});
+test_dump([1, 2, 3], "[1,2,3]", {pretty => 0});
+
+test_dump({a => [1,2]}, "{\"a\":[1,2]}", {pretty => 0});
+test_dump({a => [1,2]}, "{\n  \"a\" : [\n    1,\n    2\n  ]\n}", {pretty => 1});
+
+test_dump({a => sub {}}, "{}", {pretty => 0});
+test_dump({a => sub {}}, "{\"a\":\"CODE\"}", {handle_unknown_types => sub {my $self=shift;return $self->js_escape(ref shift)}, pretty => 0});
+
+test_dump({a => 1}, "{}", {skip_keys => ['a']});
+test_dump({a => 1}, "{}", {skip_keys => {a=>1}});
+
+test_dump({2 => 1, _a => 1}, "{2:1,\"_a\":1}", {pretty=>0});
+test_dump({2 => 1, _a => 1}, "{2:1}", {pretty=>0, skip_keys_qr => qr/^_/});
+
+test_dump({a => 1}, "{\n  \"a\" : 1\n}", {pretty => 1});
+test_dump({a => 1}, "{\n  \"a\" : 1\n}", {pretty => 1, hash_nl => "\n", hash_sep => " : ", indent => "  "});
+test_dump({a => 1}, "{\n\"a\" : 1\n}", {pretty => 1, hash_nl => "\n", hash_sep => " : ", indent => ""});
+test_dump({a => 1}, "{\"a\" : 1}", {pretty => 1, hash_nl => "", hash_sep => " : ", indent => ""});
+test_dump({a => 1}, "{\"a\":1}", {pretty => 1, hash_nl => "", hash_sep => ":", indent => ""});
+test_dump({a => 1}, "{\"a\":1}", {pretty => 0, hash_nl => "\n", hash_sep => " : "});
+
+test_dump(['a' => 1], "[\n  \"a\",\n  1\n]", {pretty => 1});
+test_dump(['a' => 1], "[\n  \"a\",\n  1\n]", {pretty => 1, array_nl => "\n", indent => "  "});
+test_dump(['a' => 1], "[\n\"a\",\n1\n]", {pretty => 1, array_nl => "\n", indent => ""});
+test_dump(['a' => 1], "[\"a\",1]", {pretty => 1, array_nl => "", indent => ""});
+test_dump(['a' => 1], "[\"a\",1]", {pretty => 0, array_nl => "\n"});
+
+
+
+test_dump(1, "1");
+test_dump('1.0', '"1.0"');
+test_dump('123456789012345', '"123456789012345"');
+test_dump('a', '"a"');
+test_dump("\n", '"\\n"');
+test_dump("\\", '"\\\\"');
+test_dump('<script>', '"<scrip"+"t>"');
+test_dump('<script>', "'<scrip'+'t>'", {single_quote => 1});
+test_dump('<html>', '"<htm"+"l>"');
+test_dump('<!--', '"<!-"+"-"');
+test_dump('"', '"\\""');
+test_dump('a', "'a'", {single_quote => 1});
+test_dump('"', "'\"'", {single_quote => 1});
+
+my $code = sub {};
+my $str  = "\"$code\"";
+test_dump($code, $str);
+test_dump($code, "\"CODE\"", {handle_unknown_types => sub { my($self, $data)=@_; return '"'.ref($data).'"'}});
+
+
+test_dump(sub { "ab" }, '"ab"', {play_coderefs => 1});
+test_dump({a => sub { "ab" }}, '{"a":"ab"}', {pretty=>0,play_coderefs => 1});
+
+test_dump("Foo\n".("Bar"x30), "\"Foo\\n\"\n  +\"".("Bar"x30)."\"", {pretty => 1});
+test_dump("Foo\n".("Bar"x30), "\"Foo\\n\"\n\n  +\"".("Bar"x30)."\"", {pretty => 1, str_nl => "\n\n"});
+
+test_dump("Foo\n".("Bar"x30), "'Foo\\n'\n  +'".("Bar"x30)."'", {pretty => 1, single_quote => 1});
+test_dump("Foo\n".("Bar"x30), "'Foo\\n'\n\n  +'".("Bar"x30)."'", {pretty => 1, str_nl => "\n\n", single_quote => 1});
This page took 0.082559 seconds and 4 git commands to generate.