Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/.cvsignore
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/.cvsignore	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/.cvsignore	(revision 22129)
@@ -0,0 +1,6 @@
+Build
+META.yml
+Makefile
+Makefile.PL
+_build
+blib
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Build.PL
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Build.PL	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Build.PL	(revision 22129)
@@ -0,0 +1,43 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+my $class = Module::Build->subclass(code => <<'EOF');
+use File::Copy;
+use File::stat;
+
+my $grammar = "config_grammar.txt";
+my $parser  = "lib/PS/IPP/Metadata/Parser.pm";
+
+sub ACTION_code {
+    my $self = shift;
+
+    if (!-e $parser || (stat($grammar)->mtime > stat($parser)->mtime)) {
+        system("perl -MParse::RecDescent - $grammar PS::IPP::Metadata::Parser") == 0
+            or die "Parse::RecDecent code gen failed: $?";
+        move("Parser.pm", $parser) or die "move failed: $!";
+    }
+
+    $self->SUPER::ACTION_code(@_);
+}
+EOF
+
+$class->new(
+    module_name         => 'PS::IPP::Metadata::Config',
+    dist_version_from   => 'lib/PS/IPP/Metadata/Config.pm',
+    author              => 'Joshua Hoblitt <jhoblitt@cpan.org>',
+    license             => 'gpl',
+    create_makefile_pl  => 'passthrough',
+    requires            =>  {
+        'Carp'                      => 0,
+        'Class::Accessor::Fast'     => '0.19',
+        'Params::Validate'          => '0.72',
+        'DateTime::Format::ISO8601' => '0.0402',
+        'Parse::RecDescent'         => '1.94',
+    },
+    recommends          => {
+        'Test::Distribution'    => '1.22',
+    },
+    script_files        => [qw(
+        scripts/mdc-dump
+    )],
+)->create_build_script;
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Changes
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Changes	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Changes	(revision 22129)
@@ -0,0 +1,61 @@
+Revision history for Perl module PS::IPP::Metadata::Config
+
+1.01 Fri Nov  9 14:50:58 HST 2007
+    - report an error with line # when eof not parsed
+    - remove mdc-dump --file option flag
+
+1.00 Fri Feb  2 21:51:20 HST 2007
+    - version bump only
+
+0.11 Thu Feb  1 15:42:28 HST 2007
+    - add t/00_distribution.t
+
+0.10 Fri Nov 24 17:11:13 HST 2006
+    - fix handling of NULL strings
+    - merge the 'time' production into the 'scalar' production for a ~20% speed boost 
+    - remove support for "epoch format" times
+    - additional metadata structure tests
+    - merge metadata_name & metadata_end into metdata
+    - merge empty metdata case into main metadata production
+    - error check vector
+    - change type & vtype into regex alternations instead of rule alternations
+
+0.09 Thu Oct 26 16:16:28 HST 2006
+    - fix a bug in Build.PL where a rebuilt grammar was not installed
+    - fix time tests that were dependent on MULTI times being broken
+    - fix MULTI time
+    - no longer require the 'Z' and the end of ISO8601 times
+    - implement NULL time support
+
+0.08 Wed Oct 11 16:09:26 HST 2006
+    - add support for empty METADATAs
+
+0.07 Tue Oct 10 17:09:28 HST 2006
+    - fix MULTI parsing of TYPEs
+    - minor test update
+
+0.06 Tue Oct  3 15:25:44 HST 2006
+    - fix Build.PL script to not throw and error when $parser does not exist
+    - fix the trailing white space left in string values when followed by a
+      comment
+
+0.05 Mon Sep 25 12:06:22 HST 2006
+    - fix a bug in the manifest
+    - fix Build.PL so the parser is only regenerated when the grammar has been
+      changed
+
+0.04 Mon Sep 25 11:50:20 HST 2006
+    - add fractional second support to the ISO8601 format
+
+0.03 Fri Sep 22 14:27:14 HST 2006
+    - add float nan/inf support
+
+0.02 Thu Aug 24 17:08:14 HST 2006
+    - split grammar out of PS::IPP::Metadata::Config into grammar_config.txt
+    - precompile the parser as PS::IPP::Metadata::Pasrser
+    - change PS::IPP::Metadata::Config to use PS::IPP::Metadata::Pasrser
+
+0.01 Tue Feb 22 15:46:35 2005
+	- original version; created by ExtUtils::ModuleMaker 0.32
+
+
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/LICENSE
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/LICENSE	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/LICENSE	(revision 22129)
@@ -0,0 +1,260 @@
+The General Public License (GPL)
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
+Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
+verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share
+and change it. By contrast, the GNU General Public License is intended to
+guarantee your freedom to share and change free software--to make sure the
+software is free for all its users. This General Public License applies to most of
+the Free Software Foundation's software and to any other program whose
+authors commit to using it. (Some other Free Software Foundation software is
+covered by the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our
+General Public Licenses are designed to make sure that you have the freedom
+to distribute copies of free software (and charge for this service if you wish), that
+you receive source code or can get it if you want it, that you can change the
+software or use pieces of it in new free programs; and that you know you can do
+these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny
+you these rights or to ask you to surrender the rights. These restrictions
+translate to certain responsibilities for you if you distribute copies of the
+software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a
+fee, you must give the recipients all the rights that you have. You must make
+sure that they, too, receive or can get the source code. And you must show
+them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer
+you this license which gives you legal permission to copy, distribute and/or
+modify the software.
+
+Also, for each author's protection and ours, we want to make certain that
+everyone understands that there is no warranty for this free software. If the
+software is modified by someone else and passed on, we want its recipients to
+know that what they have is not the original, so that any problems introduced by
+others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish
+to avoid the danger that redistributors of a free program will individually obtain
+patent licenses, in effect making the program proprietary. To prevent this, we
+have made it clear that any patent must be licensed for everyone's free use or
+not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification
+follow.
+
+GNU GENERAL PUBLIC LICENSE
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
+MODIFICATION
+
+0. This License applies to any program or other work which contains a notice
+placed by the copyright holder saying it may be distributed under the terms of
+this General Public License. The "Program", below, refers to any such program
+or work, and a "work based on the Program" means either the Program or any
+derivative work under copyright law: that is to say, a work containing the
+Program or a portion of it, either verbatim or with modifications and/or translated
+into another language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by
+this License; they are outside its scope. The act of running the Program is not
+restricted, and the output from the Program is covered only if its contents
+constitute a work based on the Program (independent of having been made by
+running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as
+you receive it, in any medium, provided that you conspicuously and appropriately
+publish on each copy an appropriate copyright notice and disclaimer of warranty;
+keep intact all the notices that refer to this License and to the absence of any
+warranty; and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at
+your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus
+forming a work based on the Program, and copy and distribute such
+modifications or work under the terms of Section 1 above, provided that you also
+meet all of these conditions:
+
+a) You must cause the modified files to carry prominent notices stating that you
+changed the files and the date of any change.
+
+b) You must cause any work that you distribute or publish, that in whole or in
+part contains or is derived from the Program or any part thereof, to be licensed
+as a whole at no charge to all third parties under the terms of this License.
+
+c) If the modified program normally reads commands interactively when run, you
+must cause it, when started running for such interactive use in the most ordinary
+way, to print or display an announcement including an appropriate copyright
+notice and a notice that there is no warranty (or else, saying that you provide a
+warranty) and that users may redistribute the program under these conditions,
+and telling the user how to view a copy of this License. (Exception: if the
+Program itself is interactive but does not normally print such an announcement,
+your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable
+sections of that work are not derived from the Program, and can be reasonably
+considered independent and separate works in themselves, then this License,
+and its terms, do not apply to those sections when you distribute them as
+separate works. But when you distribute the same sections as part of a whole
+which is a work based on the Program, the distribution of the whole must be on
+the terms of this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to
+work written entirely by you; rather, the intent is to exercise the right to control
+the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the
+Program (or with a work based on the Program) on a volume of a storage or
+distribution medium does not bring the other work under the scope of this
+License.
+
+3. You may copy and distribute the Program (or a work based on it, under
+Section 2) in object code or executable form under the terms of Sections 1 and 2
+above provided that you also do one of the following:
+
+a) Accompany it with the complete corresponding machine-readable source
+code, which must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange; or,
+
+b) Accompany it with a written offer, valid for at least three years, to give any
+third party, for a charge no more than your cost of physically performing source
+distribution, a complete machine-readable copy of the corresponding source
+code, to be distributed under the terms of Sections 1 and 2 above on a medium
+customarily used for software interchange; or,
+
+c) Accompany it with the information you received as to the offer to distribute
+corresponding source code. (This alternative is allowed only for noncommercial
+distribution and only if you received the program in object code or executable
+form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making
+modifications to it. For an executable work, complete source code means all the
+source code for all modules it contains, plus any associated interface definition
+files, plus the scripts used to control compilation and installation of the
+executable. However, as a special exception, the source code distributed need
+not include anything that is normally distributed (in either source or binary form)
+with the major components (compiler, kernel, and so on) of the operating system
+on which the executable runs, unless that component itself accompanies the
+executable.
+
+If distribution of executable or object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the source
+code from the same place counts as distribution of the source code, even though
+third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as
+expressly provided under this License. Any attempt otherwise to copy, modify,
+sublicense or distribute the Program is void, and will automatically terminate
+your rights under this License. However, parties who have received copies, or
+rights, from you under this License will not have their licenses terminated so long
+as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it.
+However, nothing else grants you permission to modify or distribute the Program
+or its derivative works. These actions are prohibited by law if you do not accept
+this License. Therefore, by modifying or distributing the Program (or any work
+based on the Program), you indicate your acceptance of this License to do so,
+and all its terms and conditions for copying, distributing or modifying the
+Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program),
+the recipient automatically receives a license from the original licensor to copy,
+distribute or modify the Program subject to these terms and conditions. You
+may not impose any further restrictions on the recipients' exercise of the rights
+granted herein. You are not responsible for enforcing compliance by third parties
+to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement
+or for any other reason (not limited to patent issues), conditions are imposed on
+you (whether by court order, agreement or otherwise) that contradict the
+conditions of this License, they do not excuse you from the conditions of this
+License. If you cannot distribute so as to satisfy simultaneously your obligations
+under this License and any other pertinent obligations, then as a consequence
+you may not distribute the Program at all. For example, if a patent license would
+not permit royalty-free redistribution of the Program by all those who receive
+copies directly or indirectly through you, then the only way you could satisfy
+both it and this License would be to refrain entirely from distribution of the
+Program.
+
+If any portion of this section is held invalid or unenforceable under any particular
+circumstance, the balance of the section is intended to apply and the section as
+a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other
+property right claims or to contest validity of any such claims; this section has
+the sole purpose of protecting the integrity of the free software distribution
+system, which is implemented by public license practices. Many people have
+made generous contributions to the wide range of software distributed through
+that system in reliance on consistent application of that system; it is up to the
+author/donor to decide if he or she is willing to distribute software through any
+other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a
+consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries
+either by patents or by copyrighted interfaces, the original copyright holder who
+places the Program under this License may add an explicit geographical
+distribution limitation excluding those countries, so that distribution is permitted
+only in or among countries not thus excluded. In such case, this License
+incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the
+General Public License from time to time. Such new versions will be similar in
+spirit to the present version, but may differ in detail to address new problems or
+concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a
+version number of this License which applies to it and "any later version", you
+have the option of following the terms and conditions either of that version or of
+any later version published by the Free Software Foundation. If the Program does
+not specify a version number of this License, you may choose any version ever
+published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs
+whose distribution conditions are different, write to the author to ask for
+permission. For software which is copyrighted by the Free Software Foundation,
+write to the Free Software Foundation; we sometimes make exceptions for this.
+Our decision will be guided by the two goals of preserving the free status of all
+derivatives of our free software and of promoting the sharing and reuse of
+software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS
+NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE
+COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
+"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
+IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
+YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
+CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED
+TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY
+WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS
+PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
+ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY
+OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS
+BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/MANIFEST
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/MANIFEST	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/MANIFEST	(revision 22129)
@@ -0,0 +1,21 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+README
+Todo
+config_grammar.txt
+docs/sdrs_grammar.txt
+lib/PS/IPP/Metadata/Config.pm
+lib/PS/IPP/Metadata/Config.pod
+lib/PS/IPP/Metadata/Parser.pm
+scripts/mdc-dump
+t/00_distribution.t
+t/01_load.t
+t/02_examples.t
+t/03_metadata.t
+t/04_type.t
+t/05_time.t
+t/06_multi.t
+t/07_floats.t
+t/08_strings.t
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/README
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/README	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/README	(revision 22129)
@@ -0,0 +1,16 @@
+pod2text PS::IPP::Metadata::Config.pm > README
+
+If this is still here it means the programmer was too lazy to create the readme file.
+
+You can create it now by using the command shown above from this directory.
+
+At the very least you should be able to use this set of instructions
+to install the module...
+
+perl Build.PL
+./Build
+./Build test
+./Build install
+
+
+If you are on a windows box you should use 'nmake' rather than 'make'.
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Todo
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Todo	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/Todo	(revision 22129)
@@ -0,0 +1,5 @@
+TODO list for Perl module PS::IPP::Metadata::Config
+
+- Nothing yet
+
+
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/config_grammar.txt
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/config_grammar.txt	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/config_grammar.txt	(revision 22129)
@@ -0,0 +1,355 @@
+# Copyright (c) 2005  Joshua Hoblitt
+#
+# $Id: config_grammar.txt,v 1.16 2007-11-10 00:50:19 jhoblitt Exp $
+
+{
+    use strict;
+
+    use Carp qw( carp );
+    use DateTime::Format::ISO8601;
+
+    our @scope_stack;
+}
+
+startrule: grammar <commit> eofile
+    { $item{grammar} }
+    | <error:could not parse beyond this point>
+
+grammar:
+    statement(s)
+        {
+            $thisparser->{local} = pop @scope_stack;
+            [ grep $_->{class} !~ /comment_line/, @{$item{'statement(s)'}} ];
+        }
+
+statement:
+    scalar
+    | comment_line
+    | metadata
+    | typedef
+    | typedef_declare
+    | multi_declare
+    | vector
+    | <error:unmatched statement: $text>
+
+scalar:
+    <skip:'[ \t\r]*'> name type <matchrule:$item{type}> comment(?) "\n"
+        {
+            $thisparser->{local}{name}{ $item{name} }++
+                unless defined $thisparser->{local}{name}{ $item{name} };
+
+            $return = {
+                class   => 'scalar',
+                name    => $item{name},
+                type    => $item{type},
+                value   => $item[4],
+            };
+
+            $return->{comment} = $item{'comment(?)'}[0]
+                if $item{'comment(?)'}[0];
+
+            if ( defined $thisparser->{local}{name}{ $item{name} } ) {
+                $return->{multi}++
+                    if $thisparser->{local}{name}{ $item{name} } =~ /MULTI/;
+            } else {
+                $thisparser->{local}{name}{ $item{name} }++;
+            }
+        }
+
+vector:
+     <skip:'[ \t\r]*'> vname <commit> vtype vvalue[ $item{vtype} ] comment(?) "\n"
+        {
+            $thisparser->{local}{name}{ $item{vname} }++
+                unless defined $thisparser->{local}{name}{ $item{vname} };
+
+            $return = {
+                class   => 'vector',
+                name    => $item{vname},
+                type    => $item{vtype},
+                value   => $item{vvalue},
+                comment => $item{comment}
+            };
+
+            $return->{comment} = $item{'comment(?)'}[0]
+                if $item{'comment(?)'}[0];
+        }
+    | <error?:redefinition of MULTI> <reject>
+
+multi_declare:
+    <skip:'[ \t\r]*'> name /MULTI/i <commit> comment(?) "\n"
+        {
+            if ( ! defined $thisparser->{local}{name}{ $item{name} } ) {
+                $thisparser->{local}{name}{ $item{name} } = "MULTI";
+                $return = { class   => 'comment_line' };
+            } else {
+                $return = undef;
+            }
+        }
+    | <error?:redefinition of MULTI> <reject>
+
+# can't comit after the metadat_name here as it seems to break parsing empty
+# metadatas
+metadata:
+    <skip:'[ \t\r]*'> name /METADATA/i comment(?) "\n"
+    <commit>
+    { push @scope_stack, $thisparser->{local}; $thisparser->{local} = {}; 1; }
+    grammar(?)
+    <skip:'[ \t\r]*'> /END/i comment(?) "\n"
+
+        {
+            if (defined(@{$item{'grammar(?)'}})) {
+                $return = {
+                    class   => 'metadata',
+                    name    => $item{name},
+                    value   => @{$item{'grammar(?)'}},
+                };
+            } else {
+                $return = {
+                    class   => 'metadata',
+                    name    => $item{name},
+                    value   => [],
+                };
+            }
+
+            $return->{comment} = $item[4][0]
+                if defined $item[4][0];
+
+            if ( defined $thisparser->{local}{name}{ $item{name} } ) {
+                $return->{multi}++
+                    if $thisparser->{local}{name}{ $item{name} } =~ /MULTI/;
+            } else {
+                $thisparser->{local}{name}{ $item{name} }++;
+            }
+        } 
+    | <error?:bad METADATA syntax: $text> <reject>
+
+comment_line:
+    <skip:'[ \t\r]*'> comment(?) "\n"
+        {{ class   => 'comment_line' }}
+
+typedef_declare:
+    <skip:'[ \t\r]*'> 'TYPE' <commit> name name(s) comment(?) "\n"
+        {
+            if (defined $thisparser->{local}{typedef}{ $item{name} }) {
+                carp "refinition of TYPE $item{name}";    
+                $return = undef;
+            } else {
+
+                my $type = {
+                    class   => 'typedef_declare',
+                    name    => $item{name},
+                    fields  => $item{'name(s)'},
+                    length  => scalar @{ $item{'name(s)'} },
+                };
+
+                $thisparser->{local}{typedef}{ $item{name} } = $type;
+
+                # don't return anything in the parse tree
+                $return = { class => 'comment_line' };
+            }
+        }
+    | <error?:redefinition of TYPE> <reject>
+
+typedef:
+    <skip:'[ \t\r]*'> name typedef_type <commit> word(s) comment(?) "\n"
+        {
+            my $type = $thisparser->{local}{typedef}{ $item{typedef_type} };
+
+            unless ( scalar @{ $item{'word(s)'} } == $type->{length} ) {
+                my $expect = $type->{length};
+                my $got = scalar @{ $item{'word(s)'} };
+                carp "\"$item{name} $item{typedef_type}\""
+                    . " does not have enough fields, epected: $expect, got: $got";
+                $return = undef;
+            } else {
+                my @md;
+                my $i = 0;
+                foreach my $name ( @{ $type->{fields} } ) {
+                    push @md, {
+                        name    => $name,
+                        class   => 'scalar',
+                        type    => 'STR',
+                        value   => $item{'word(s)'}[$i],
+                    };
+
+                    $i++;
+                }
+
+                $return = {
+                    class   => 'metadata',
+                    name    => $item{name},
+                    value   => \@md,
+                };
+
+                if ( defined $thisparser->{local}{name}{ $item{name} } ) {
+                    $return->{multi}++
+                        if $thisparser->{local}{name}{ $item{name} } =~ /MULTI/;
+                } else {
+                    $thisparser->{local}{name}{ $item{name} }++;
+                }
+            }
+
+        }
+    | <error?:'TYPE' does not have enough parameters: $text> <reject>
+
+
+typedef_type: name
+        {
+            if ( ! defined $thisparser->{local}{typedef}{ $item{name} } ) {
+                $return = undef;
+            } else {
+                $return = $item{name};
+            }
+        }
+
+comment:
+    '#' to_end_of_line
+        { $item{to_end_of_line} }
+
+vname:
+    '@' name
+        { $item{name} }
+
+name:
+    /[\w][.\w-]*/i
+        {
+            if ( $item[1] =~ /^(METADATA|END|TYPE)$/i ) {
+                $return = undef;
+            } else {
+                $return = $item[1];
+            }
+        }
+
+# 'STR' seems to be the most common type, trying to match it first is a simple
+# optimization.
+type: 
+    vtype
+    | /(STR
+    | STRING
+    | UTC
+    | UT1
+    | TAI
+    | TT)/x
+
+vtype: 
+    /(S8
+    | S16
+    | S32
+    | S64
+    | U8 
+    | U16
+    | U32
+    | U64
+    | F32
+    | F64
+    | C32
+    | C64
+    | BOOL)/x
+
+S8: int
+S16: int
+S32: int
+S64: int
+U8 : int
+U16: int
+U32: int
+U64: int
+F32: float
+F64: float
+C32: float
+C64: float
+BOOL: bool
+STR: string
+STRING: string
+
+vvalue:
+    _vvalue[%arg](s)
+        { [ map { @$_ } @{$item[1]} ] }
+
+# backtracking optimization
+_vvalue:
+    <matchrule:$arg[0]> vector_sep(?)
+        { [ $item[1] ] }
+
+vector_sep:
+    /,|\s+/
+
+int:
+    # $RE{num}{int} from Regexp::Common::number
+    /(?:(?:[+-]?)(?:[0-9]+))/
+
+    # based on $RE{num}{real} from Regexp::Common::number
+float:
+    /(?:(?i)(?:[+-]?)(?:(?=[0-9]|[.])(?:[0-9]*)(?:(?:[.])(?:[0-9]{0,}))?)(?:(?:[Ee])(?:(?:[+-]?)(?:[0-9]+))|))/
+    | inf
+    | nan
+
+inf:
+    /[+-]?inf(?:inity)?/i
+        {
+            if ($item[1] =~ /^-/) {
+                $return = "-inf";
+            } else {
+                $return = "+inf";
+            }
+        }
+nan:
+    /[+-]?nan(?:\(\S*?\))?/i
+        { $return = "nan" }
+
+bool:
+    /(true|false|t|f)/i
+        { $item[1] =~ /t/i ? 1 : 0 }
+
+string:
+    /[^#\n\s]+[^#\n]*/
+        # trim tailing the trailing white that's left when the string is
+        # followed by a comment (#)
+        { 
+            $item[1] =~ s/\s*$//;
+            $return = $item[1];
+        }
+    | NULL
+
+word:
+    /(?:[^#\s\n]+)/
+
+to_end_of_line:
+    /[^\n]*/
+        {
+            my $comment = $item[1];
+            # remove leading whitespace
+            $comment =~ s/^\s+//;
+            # remove trailing whitespace
+            $comment =~ s/\s+$//;
+
+            $return = $comment;
+        }
+
+UTC:
+    iso8601
+    | NULL
+
+UT1:
+    iso8601
+    | NULL
+
+TAI:
+    iso8601
+    | NULL
+
+TT:
+    iso8601
+    | NULL
+
+iso8601:
+    # based on code from DateTime::Format::ISO8601
+    / \d{4} -?? \d\d -?? \d\d T?? \d\d :?? \d\d :?? \d\d (?:[\.,] (\d+))? Z?/x
+        { DateTime::Format::ISO8601->parse_datetime( $item[1] ) }
+
+NULL:
+    /NULL/
+    { $return = "NULL" }
+
+eofile:
+    /^\z/
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt	(revision 22129)
@@ -0,0 +1,144 @@
+startrule: statement(s) /\z/
+
+statement: scalar | vector | multi_declare | comment
+
+comment: '#' end_of_line
+
+scalar: name type value comment | name type value 
+
+vector: vname vtype vvalue comment | vname vtype vvalue 
+
+multi_declare: name '*' end_of_line
+
+name: /[a-z][.\w]*/i
+
+vname: /\@[a-z][.\w]*/i
+
+type: 
+    vtype | 'STR' | 'STRING'
+
+vtype: 
+    'S8'  |
+    'S16' |
+    'S32' |
+    'S64' |
+    'U8'  |
+    'U16' |
+    'U32' |
+    'U64' |
+    'F32' |
+    'F64' |
+    'C32' |
+    'C64' |
+    'BOOL'
+
+value: vector_value | string
+
+vvalue: vector_value vector_sep vvalue | vector_value vector_sep(?)
+
+vector_sep: ','
+
+vector_value: float | int | bool
+
+int: integer_constant
+
+float: floating_constant
+
+bool: /[tf]\s+?/i
+
+string:
+    /\S[^#\n]*/
+
+end_of_line: /[^\n]*/
+
+### based on syntax found in "C A Reference Manual"
+
+# integer constants
+
+integer_constant:
+    decimal_constant integer_suffix(?) |
+    octal_constant integer_suffix(?) |
+    hexadecimal_constant integer_suffix(?)
+
+decimal_constant:
+    digit(2..) |
+    nonzero_digit
+
+octal_constant:
+    '0' octal_digit(s) |
+    '0'
+   
+hexadecimal_constant:
+    hex_prefix hex_digit_sequence
+
+digit:
+    /[0-9]/
+
+nonzero_digit:
+    /[1-9]/
+
+octal_digit:
+    /[0-7]/
+
+hex_digit:
+    /0_9a-f/i
+
+integer_suffix:
+    long_suffix unsigned_suffix(?) |
+    long_long_suffix unsigned_suffix(?) |
+    unsigned_suffix long_suffix(?) |
+    unsigned_suffix long_long_suffix(?)
+
+long_suffix:
+    /l/i
+
+long_long_suffix:
+    /ll/i
+
+unsigned_suffix:
+    /u/i
+
+# floating-point constants
+
+floating_constant:
+    decimal_floating_constant |
+    hexadecimal_floating_constant
+
+decimal_floating_constant:
+    digit_sequence exponent floating_suffix(?) |
+    dotted_digits exponent(?) floating_suffix(?)
+
+digit_sequence:
+    digit(s)
+
+dotted_digits:
+    digit_sequence '.' digit_sequence  |
+    '.' digit_sequence |
+    digit_sequence '.'
+
+exponent:
+    /e/i sign_part digit_sequence
+
+sign_part:
+    /[+-]/
+
+floating_suffix:
+    /[fl]/i
+
+hexadecimal_floating_constant:
+    hex_prefix dotted_hex_digits binary_exponent floating_suffix(?) |
+    hex_prefix hex_digit_sequence binary_exponent floating_suffix(?)
+
+hex_prefix:
+    /0x/i
+
+dotted_hex_digits:
+    hex_digit_sequence '.' hex_digit_sequence |
+    '.' hex_digit_sequence |
+    hex_digit_sequence '.'
+
+hex_digit_sequence:
+    hex_digit(s) 
+
+binary_exponent:
+    /p/i sign_part(?) digit_sequence
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/.cvsignore
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/.cvsignore	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/.cvsignore	(revision 22129)
@@ -0,0 +1,1 @@
+Parser.pm
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm	(revision 22129)
@@ -0,0 +1,137 @@
+# Copyright (c) 2005  Joshua Hoblitt
+#
+# $Id: Config.pm,v 1.32 2007-11-10 00:50:19 jhoblitt Exp $
+
+package PS::IPP::Metadata::Config;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = '1.01';
+
+use Carp qw( carp );
+use PS::IPP::Metadata::Parser;
+
+use base qw( Class::Accessor::Fast );
+__PACKAGE__->mk_accessors( qw( overwrite ) );
+
+#$::RD_TRACE = 1;
+#$::RD_HINT = 1;
+#use Data::Dumper;
+
+sub new {
+    my $class = shift;
+
+    my $self = { _parser => PS::IPP::Metadata::Parser->new };
+
+    bless $self, $class;
+
+    $self->overwrite( undef );
+
+    return $self;
+}
+
+sub parse {
+    my ( $self, $metadata ) = @_;
+
+    return undef if not defined $metadata;
+    return undef if $metadata =~ /^\s*$/;
+
+    # remove any local data from a prevous run
+    # this is slightly faster then using Storable::dclone to clone a new parser
+    delete $self->{_parser}{local};
+
+    my $tree = $self->{_parser}->startrule( $metadata );
+
+    return undef unless defined $tree;
+
+    # look for duplicate names, there should be none after processing the
+    # multi-symbols
+    $self->_merge_duplicates( $tree ) or return undef;
+
+    # translate NULL values into perl undefs (Parse::RecDescent can't return
+    # undefs, args)
+    $self->_fix_nulls( $tree ) or return undef;
+
+    #print Dumper($tree);
+
+    return $tree;
+}
+
+sub _merge_duplicates {
+    my ( $self, $tree ) = @_;
+
+    # encountered names
+    my %names;
+
+    # Iteratate through the parse tree looking for duplicate declarations and
+    # resolving them by discarding elements according to the value of
+    # 'overwrite'.
+    for (my $i = 0; $i < @{$tree}; $i++) {
+        my $elem = $tree->[$i];
+
+        # stop if the prevous pass removed the last element and called redo
+        last unless defined $elem;
+                
+        # recurse through nested metadata
+        if ( $elem->{class} eq "metadata" ) {
+            $self->_merge_duplicates( $elem->{value} );
+        }
+
+        # ignore elements with the "multi" flag
+        if ( defined $elem->{multi} ) {
+            delete $elem->{multi};
+            next;
+        } 
+
+        if ( defined $names{ $elem->{name} } ) {
+            if ( $self->{overwrite} ) {
+                # remove the previous occurance
+                carp "duplicate variable name: ", $elem->{name}
+                    , ", removed previous occurance\n";
+                splice @{$tree}, $names{ $elem->{name} }, 1;
+            } else {
+                # remove the current occurance
+                carp "duplicate variable name: ", $elem->{name}
+                    , ", removed\n";
+                splice @{$tree}, $i, 1;
+            }
+
+            # the list just got shorter by one element so we don't want to
+            # increment the cursor
+            redo;
+        }
+
+        # add element name and location to record of previously seen names
+        $names{ $elem->{name} } = $i;
+    }
+
+    return 1;
+}
+
+sub _fix_nulls {
+    my ( $self, $tree ) = @_;
+
+    # Iteratate through the parse tree looking for values of NULL and
+    # translating them into Perl undefs
+    for (my $i = 0; $i < @{$tree}; $i++) {
+        my $elem = $tree->[$i];
+
+        # recurse through nested metadata
+        if ( $elem->{class} eq "metadata" ) {
+            $self->_fix_nulls( $elem->{value} );
+        }
+
+        # force stringification of $elem->{value} -- specifically because you
+        # can't compare a DateTime object to a string value
+        if ("$elem->{value}" eq "NULL") {
+            $elem->{value} = undef;
+        }
+    }
+
+    return 1;
+}
+
+1;
+
+__END__
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod	(revision 22129)
@@ -0,0 +1,137 @@
+=pod
+
+=head1 NAME
+
+PS::IPP::Metadata::Config - Parses the psMetadata config language
+
+=head1 SYNOPSIS
+
+    use PS::IPP::Metadata::Config;
+
+    my $mdparser = PS::IPP::Metadata::Config->new;
+    $mdparser->overwrite( 1 );
+
+    my $config = $mdparser->parse( $example );
+
+=head1 DESCRIPTION
+
+Parses the psMetadata configuration language into an AoH parse tree.
+
+=head1 USAGE
+
+=head2 Import Parameters
+
+This module accepts no arguments to it's C<import> method and exports no
+I<symbols>.
+
+=head2 Methods
+
+=head3 Constructors
+
+=over 4
+
+=item * new
+
+Accepts no parameters and returns a L<PS::IPP::Metadata::Config> object.
+
+=back
+
+=head3 Object Methods
+
+=over 4
+
+=item * overwrite
+
+Controls the behavior of the parser when encountering keys that have been
+defined more then once.  A true value instructs the parser to discard all but
+the latest declaration and a false value will preserve the first key parsed.
+Accepts 0, 1, or undef.
+
+Defaults to undef.
+
+=item * parse
+
+Accepts a string.  Returns an AoH structure on success or undef on failure.
+
+The format of the returned structure is as follows:
+
+    [
+        {
+            'comment'   => 'This is a comment',
+            'value'     => '123456789',
+            'name'      => 'Bob',
+            'type'      => 'U64',
+            'class'     => 'scalar'
+        },
+        {
+            'comment'   => 'These are prime numbers',
+            'value'     => [
+                            '2',
+                            '3'
+                        ],
+            'name'      => '@primes',
+            'type'      => 'U8',
+            'class'     => 'vector'
+        },
+        ...
+    ]
+
+=back
+
+=head1 DEVELOPER NOTES
+
+Currently, "multiple symbols" are translated into "STR" vectors as the
+appropriate behavior isn't clear in the psLib SDRS.
+
+The parser does not have complete support for all of C99's floating point
+constants or any support for complex numbers.
+
+None of the "extended" config language syntax, as defined in the SDRS, is
+supported.
+
+=head1 REFERENCES
+
+=over 4
+
+=item PSDC-430-007
+
+Pan-STARRS PS-1 IPP Library Supplementary Design Requirements Specification
+(SDRS): Section 5.3.4, "Configuration Files".
+
+=back
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2005  Joshua Hoblitt.  All rights reserved.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place - Suite 330, Boston, MA  02111-1307, USA.
+
+The full text of the license can be found in the LICENSE file included with
+this module, or in the L<perlgpl> Pod as supplied with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+=cut
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/scripts/mdc-dump
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/scripts/mdc-dump	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/scripts/mdc-dump	(revision 22129)
@@ -0,0 +1,147 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: mdc-dump,v 1.3 2007-11-10 00:40:08 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.02';
+
+use Data::Dumper qw( Dumper );
+use PS::IPP::Metadata::Config;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ( $file, $overwrite, $verbose );
+GetOptions(
+    'overwrite' => \$overwrite,
+    'verbose'   => \$verbose,
+) || pod2usage( 2 );
+
+$file = shift;
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+# open STDIN if no file was specified
+$file = '-' unless $file;
+
+if (defined $verbose) {
+    $::RD_TRACE = 1;
+    $::RD_HINT = 1;
+}
+my $md_parser = PS::IPP::Metadata::Config->new;
+
+if ( $overwrite ) {
+   $md_parser->overwrite( 1 );
+} else {
+   $md_parser->overwrite( 0 );
+}
+
+open( my $md, $file ) or die "can't open file: $!";
+
+my $data = $md_parser->parse( do { local $/; <$md> } );
+
+close( $md );
+
+if ( $data ) {
+    print Dumper( $data );
+} else {
+    warn "Invalid Syntax, Parse failed.\n";
+    exit 1;
+}
+
+__END__
+
+=pod
+
+=head1 NAME
+
+mddump.pl - validate psMetadataConfig files
+
+=head1 SYNOPSIS
+
+    mddump.pl [--overwrite] [--verbose] <filename>
+
+    or
+
+    cat <filename> | mddump.pl [--overwrite] [--verbose]
+
+    or
+
+    mddump.pl [--help | -h | -? | --version]
+
+=head1 DESCRIPTION
+
+Parses psMetadataConfig formatted text either from a file or STDIN and prints a
+Perl data structure that represents the metadata.
+
+=head1 OPTIONS
+
+=over 4
+
+=item * <filename>
+
+Accepts the name of a file to use as input.  If this option is omitted the
+STDIN will be read from.
+
+=item * --overwrite | -o
+
+A flag that turns L<PS::IPP::Metadata::Config>'s overwrite behavior on.
+Overwrite is always off unless this flag is specified.
+
+=item * --verbose | -v
+
+A flag that turns L<Parse::RecDescent>'s C<RD_TRACE> and C<RD_HINT> tracing
+flags.  verbose is always off unless this flag is specified.
+
+=item * --help | -h | -?
+
+Prints usage information.
+
+=item * --version
+
+Prints the version.
+
+=back
+
+=head1 CREDITS
+
+Just me, myself, and I.
+
+=head1 SUPPORT
+
+Please contact the author directly via e-mail.
+
+=head1 AUTHOR
+
+Joshua Hoblitt <jhoblitt@cpan.org>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2004  Joshua Hoblitt.  All rights reserved.
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place - Suite 330, Boston, MA  02111-1307, USA.
+
+The full text of the license can be found in the L<perlgpl> Pod as supplied
+with Perl 5.8.1 and later.
+
+=head1 SEE ALSO
+
+L<PS::IPP::Metadata::Config>
+
+=cut
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/00_distribution.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/00_distribution.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/00_distribution.t	(revision 22129)
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 00_distribution.t,v 1.1 2007-01-30 01:30:48 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib ./t );
+
+use Test::More;
+
+# example taken from Test::Distribution Pod
+
+BEGIN {
+    eval {
+        require Test::Distribution;
+    };
+    if($@) {
+        plan skip_all => 'Test::Distribution not installed';
+    } else {
+        import Test::Distribution not => qw( podcover );
+    }
+}
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/01_load.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/01_load.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/01_load.t	(revision 22129)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 01_load.t,v 1.1.1.1 2005-03-01 03:38:45 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+use Test::More tests => 1;
+
+BEGIN { use_ok( 'PS::IPP::Metadata::Config' ); }
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/02_examples.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/02_examples.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/02_examples.t	(revision 22129)
@@ -0,0 +1,385 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 02_examples.t,v 1.5 2006-07-12 02:49:53 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 9;
+use PS::IPP::Metadata::Config;
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+
+{
+    my $config = $config_parser->parse(undef);
+    is($config, undef, "parsing undef returns undef");
+}
+
+{
+    my $config = $config_parser->parse('');
+    is($config, undef, "parsing an empty string returns undef");
+}
+
+{
+    my $config = $config_parser->parse("\n\n\n\n\n");
+    is($config, undef, "parsing an empty string returns undef");
+}
+
+{
+my $example = q{
+Double     F64     1.23456789      # This is a comment
+Float    F32 0.98765#This is a comment too
+String  STR This is the string that forms the value #comment
+
+ # This is a comment line and is to be ignored
+boolean     BOOL    T # The value of `boolean' is `true'
+ 
+@primes U8  2,3 5 7,11,13 17 #   These are prime numbers
+
+comment MULTI # The rest of this line is ignored, but `comment' is set to be non-unique
+comment STR This
+comment STR     is
+comment STR       a
+comment STR        non-unique
+comment STR                  key
+Float F64 1.23456 # This generates a warning, and, if `overwrite' is `false', is ignored
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "SDRS example parsed");
+
+    my $tree = [
+        {
+            name    => 'Double',
+            class   => 'scalar',
+            type    => 'F64',
+            value   => '1.23456789',
+            comment => 'This is a comment',
+        },
+        {
+            name    => 'Float',
+            class   => 'scalar',
+            type    => 'F32',
+            value   => '0.98765',
+            comment => 'This is a comment too',
+        },
+        {
+            name    => 'String',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'This is the string that forms the value',
+            comment => 'comment',
+        },
+        {
+            name    => 'boolean',
+            class   => 'scalar',
+            type    => 'BOOL',
+            value   => 1,
+            comment => q{The value of `boolean' is `true'},
+        },
+        {
+            name    => 'primes',
+            class   => 'vector',
+            type    => 'U8',
+            value   => [qw( 2 3 5 7 11 13 17 )],
+            comment => 'These are prime numbers',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'This',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'is',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'a',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'non-unique',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'key',
+        },
+    ];
+
+    is_deeply( $config, $tree, "SDRS example structure, no overwrite" );
+}
+
+{
+my $example = q{
+Double     F64     1.23456789      # This is a comment
+Float    F32 0.98765#This is a comment too
+String  STR This is the string that forms the value #comment
+
+ # This is a comment line and is to be ignored
+boolean     BOOL    T # The value of `boolean' is `true'
+ 
+@primes U8  2,3 5 7,11,13 17 #   These are prime numbers
+
+comment MULTI # The rest of this line is ignored, but `comment' is set to be non-unique
+comment STR This
+comment STR     is
+comment STR       a
+comment STR        non-unique
+comment STR                  key
+Float F64 1.23456 # This generates a warning, and, if `overwrite' is `false', is ignored
+};
+    $config_parser->overwrite( 1 );
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "SDRS example parsed");
+
+    my $tree = [
+        {
+            name    => 'Double',
+            class   => 'scalar',
+            type    => 'F64',
+            value   => '1.23456789',
+            comment => 'This is a comment',
+        },
+        {
+            name    => 'String',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'This is the string that forms the value',
+            comment => 'comment',
+        },
+        {
+            name    => 'boolean',
+            class   => 'scalar',
+            type    => 'BOOL',
+            value   => 1,
+            comment => q{The value of `boolean' is `true'},
+        },
+        {
+            name    => 'primes',
+            class   => 'vector',
+            type    => 'U8',
+            value   => [qw( 2 3 5 7 11 13 17 )],
+            comment => 'These are prime numbers',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'This',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'is',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'a',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'non-unique',
+        },
+        {
+            name    => 'comment',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'key',
+        },
+        {
+            name    => 'Float',
+            class   => 'scalar',
+            type    => 'F64',
+            value   => '1.23456',
+            comment => q{This generates a warning, and, if `overwrite' is `false', is ignored},
+        },
+    ];
+
+    is_deeply( $config, $tree, "SDRS example structure, with overwrite" );
+}
+
+{
+my $example = q{
+# these are examples of camera definition variables:
+# skyprobe
+NCELL       S32    1
+NCHIP       S32    1
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC     
+CELL.00     STR    %f00.%x     PHU      [0,0:0,0]   CHIP.00   [0,0:0,0] 
+                   
+# megacam-raw      
+NCELL       S32    72
+NCHIP       S32    36
+#                  FILENAME    EXTNAME  DATASEC     CHIP      BIASSEC     
+CELL.00     STR    %f.%x       AMP00    [0,0:0,0]   CHIP.00   BIASSEC
+CELL.01     STR    %f.%x       AMP01    [0,0:0,0]   CHIP.00   [2100,2110:0,4096]   
+CELL.02     STR    %f.%x       AMP02    [0,0:0,0]   CHIP.01   [0,0:0,0]   
+CELL.03     STR    %f.%x       AMP03    [0,0:0,0]   CHIP.01   [0,0:0,0]   
+
+# megacam-splice
+NCELL       S32    72
+NCHIP       S32    36
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC   TRIMSEC
+CELL.00     STR    %f.%x       CCD00    ASEC-00     CHIP.00   BSEC-00   DSEC-00
+CELL.01     STR    %f.%x       CCD00    ASEC-01     CHIP.00   BSEC-01   DSEC-01
+CELL.02     STR    %f.%x       CCD01    ASEC-00     CHIP.01   BSEC-00   DSEC-00
+CELL.03     STR    %f.%x       CCD01    ASEC-01     CHIP.01   BSEC-01   DSEC-01
+
+# cfh12k-split
+NCELL       S32    12
+NCHIP       S32    12
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC     
+CELL.00     STR    %f/%f00.%x  PHU      [0,0:0,0]   CHIP.00   [0,0:0,0]   
+CELL.01     STR    %f/%f01.%x  PHU      [0,0:0,0]   CHIP.01   [0,0:0,0]   
+CELL.02     STR    %f/%f02.%x  PHU      [0,0:0,0]   CHIP.02   [0,0:0,0]   
+
+# cfh12k-mef
+NCELL       S32    12
+NCHIP       S32    12
+#                  FILENAME    EXTNAME  REGION      CHIP      BIASSEC     
+CELL.00     STR    %f.%x       CHIP00   [0,0:0,0]   CHIP.00   [0,0:0,0]   
+CELL.01     STR    %f.%x       CHIP01   [0,0:0,0]   CHIP.01   [0,0:0,0]   
+CELL.02     STR    %f.%x       CHIP02   [0,0:0,0]   CHIP.02   [0,0:0,0]   
+
+#- REGION can be defined by a header keyword in IRAF format or by a explicit IRAF format 
+#- what is default for NAXIS1,2 for IRAF format?
+#- Nreadout is always NAXIS3?
+
+# recipe file:
+# this makes the assumption that, for a given camera, all chips &
+# cells have the same recipe.  this is probably a good start, but may
+# not cut it in general. eg, it is already clear that for 
+
+# recipe file must be a function of time and camera.
+# 
+
+# BIAS:
+BIAS.IMAGE                 STR    NONE
+BIAS.IMAGE  		   STR    FILE:bias.fits
+BIAS.IMAGE  		   STR    DB:BEST
+BIAS.IMAGE  		   STR    DB:CLOSE
+
+BIAS.OVERSCAN 		   STR    HEADER:BIASSEC
+BIAS.OVERSCAN 		   STR    RECIPE:[0,0:0,0]
+BIAS.OVERSCAN 		   STR    NONE
+
+BIAS.OVERSCAN.STATS 	   STR    MEDIAN
+BIAS.OVERSCAN.STATS 	   STR    MEAN
+
+BIAS.OVERSCAN.FIT          STR    SPLINE
+BIAS.OVERSCAN.FIT.NPTS     S32    5
+
+BIAS.OVERSCAN.FIT          STR    POLYNOMIAL
+BIAS.OVERSCAN.FIT.ORDER    S32    3
+BIAS.OVERSCAN.FIT.NBIN     S32    5
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "GENE example");
+}
+
+{
+my $example = q{
+# The raw MegaCam data comes off the telescope with each of the chips stored in extensions of a MEF file.
+
+# How to identify this type
+RULE	METADATA
+	TELESCOP	STR	CFHT 3.6m
+	DETECTOR	STR	MegaCam
+	EXTEND		BOOL	T
+	NEXTEND		S32	72
+END
+
+# How to read this data
+PHU		STR	FPA	# The FITS file represents an entire FPA
+EXTENSIONS	STR	CELLS	# The extensions represent cells
+EXTENSION_KEY	STR	EXTNAME	# You get the extensions by looking at the EXTNAME header
+IDENTIFIER	STR	OBSID	# We identify the observation by the observation Id in the header
+
+# What's in the FITS file?
+CONTENTS	METADATA
+	TYPE	CELL	CHIP	CELLTYPE
+	# Extension name, chip
+	amp00	CELL	ccd00	science
+	amp01	CELL	ccd00	science
+	amp02	CELL	ccd01	science
+	amp03	CELL	ccd01	science
+	guide	CELL	guide	guide		# A guide CCD thrown in, just for fun
+END
+
+# Specify the cell data
+CELLS	METADATA
+	science	METADATA	# A science CCD
+		TYPE	LOCATION	SOURCE	VALUE
+		BIASSEC	LOCATION	VALUE	[1:10,1:4096];[1035:1084,1:4096]
+		TRIMSEC	LOCATION	VALUE	[11:1034,1:4096]
+	#	BIASSEC	LOCATION	HEADER	BIASSEC
+	#	TRIMSEC	LOCATION	HEADER	TRIMSEC
+	END
+	guide	METADATA	# A guide CCD
+		TYPE	LOCATION	SOURCE	VALUE
+		BIASSEC	LOCATION	VALUE	[1:10,1:1024];[1035:1084,1:1024]
+		TRIMSEC	LOCATION	VALUE	[11:1034,1:1024]
+	#	BIASSEC	LOCATION	HEADER	BIASSEC
+	#	TRIMSEC	LOCATION	HEADER	TRIMSEC
+	END
+		
+END
+
+
+# How to translate PS concepts into FITS headers
+TRANSLATION	METADATA
+	AIRMASS         STR             AIRMASS
+	EXPTIME         STR             EXPOSURE
+	DARKTIME        STR             EXPOSURE # No specific darktime header; use exposure time
+	FILTER          STR             FILTER
+	DATE-OBS        STR             DATE-OBS
+	TIME-OBS        STR             TIME-OBS
+	POSANGLE        STR             POSANG
+	RA              STR             OBJ-RA
+	DEC             STR             OBJ-DEC
+END
+
+# Default PS concepts that may be specified by value
+DEFAULTS	METADATA
+	RADECSYS	STR		ICRS
+END
+
+# How to translation PS concepts into database lookups
+DATABASE	METADATA
+	TYPE		dbEntry		TABLE		COLUMN		GIVENDBCOL	GIVENPS
+	GAIN            dbEntry         Camera          gain            chipId,cellId	CHIP,CELL
+	READNOISE       dbEntry         Camera          readNoise       chipId,cellId	CHIP,CELL
+
+# A database entry refers to a particular column (COLUMN) in a
+# particular table (TABLE), given certain PS concepts (GIVENPS) that
+# match certain database columns (GIVENDBCOL).
+
+END
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "Paul/megacam example");
+}
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/03_metadata.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/03_metadata.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/03_metadata.t	(revision 22129)
@@ -0,0 +1,439 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 03_metadata.t,v 1.6 2006-11-25 01:15:47 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 13;
+use PS::IPP::Metadata::Config;
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+
+{
+my $example = q{
+CELL      METADATA
+ EXTNAME   STR   CCD00
+ BIASSEC   STR   BSEC-00
+ CHIP      STR   CHIP.00
+ NCELL     S32   24
+END
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "SDRS example parsed");
+
+    my $tree = [
+        {
+            name    => 'CELL',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'EXTNAME',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'CCD00'
+                },
+                {
+                    name    => 'BIASSEC',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'BSEC-00'
+                },
+                {
+                    name    => 'CHIP',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'CHIP.00'
+                },
+                {
+                    name    => 'NCELL',
+                    class   => 'scalar',
+                    type    => 'S32',
+                    value   => 24,
+                },
+            ],
+        }
+    ];
+
+    is_deeply( $config, $tree, "SDRS example structure");
+}
+
+{
+my $example = q{
+CELL      METADATA          # foo
+ EXTNAME   STR   CCD00      # bar
+ BIASSEC   STR   BSEC-00    # baz 
+ CHIP      STR   CHIP.00    # zab
+ NCELL     S32   24         # rab
+END                         # oof
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "SDRS example + comments parsed");
+
+    my $tree = [
+        {
+            name    => 'CELL',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'EXTNAME',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'CCD00',
+                    comment => 'bar',
+                },
+                {
+                    name    => 'BIASSEC',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'BSEC-00',
+                    comment => 'baz',
+                },
+                {
+                    name    => 'CHIP',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'CHIP.00',
+                    comment => 'zab',
+                },
+                {
+                    name    => 'NCELL',
+                    class   => 'scalar',
+                    type    => 'S32',
+                    value   => 24,
+                    comment => 'rab',
+                },
+            ],
+            comment => 'foo',
+        }
+    ];
+
+    is_deeply( $config, $tree, "SDRS example + comments structure");
+}
+
+{
+my $example = q{
+CELL      METADATA
+    FOO METADATA
+        BAR     STR BAZ
+        PING    STR PONG
+    END
+    
+    EXTNAME   STR   CCD00
+    BIASSEC   STR   BSEC-00
+    CHIP      STR   CHIP.00
+    NCELL     S32   24
+END
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "nested metadata parsed");
+
+    my $tree = [
+        {
+            name    => 'CELL',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'FOO',
+                    class   => 'metadata',
+                    value   => [
+                        {
+                            name    => 'BAR',
+                            class   => 'scalar',
+                            type    => 'STR',
+                            value   => 'BAZ',
+                        },
+                        {
+                            name    => 'PING',
+                            class   => 'scalar',
+                            type    => 'STR',
+                            value   => 'PONG',
+                        },
+                    ],
+                },
+                {
+                    name    => 'EXTNAME',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'CCD00',
+                },
+                {
+                    name    => 'BIASSEC',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'BSEC-00',
+                },
+                {
+                    name    => 'CHIP',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'CHIP.00',
+                },
+                {
+                    name    => 'NCELL',
+                    class   => 'scalar',
+                    type    => 'S32',
+                    value   => 24,
+                },
+            ],
+        }
+    ];
+
+    is_deeply( $config, $tree, "nested metadata structure");
+}
+
+{
+my $example = q{
+FOO1 METADATA
+    FOO2 METADATA
+        FOO3 METADATA
+            FOO4 METADATA
+                FOO5 METADATA
+                    FOO6 METADATA
+                        BAR     STR BAZ
+                        PING    STR PONG
+                    END
+                    BAR     STR BAZ
+                    PING    STR PONG
+                END
+                BAR     STR BAZ
+                PING    STR PONG
+            END
+            BAR     STR BAZ
+            PING    STR PONG
+        END
+        BAR     STR BAZ
+        PING    STR PONG
+    END
+    BAR     STR BAZ
+    PING    STR PONG
+END
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "deeply nested metadata");
+
+    my $tree = [
+        {
+            name    => 'FOO1',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'FOO2',
+                    class   => 'metadata',
+                    value   => [
+                        {
+                            name    => 'FOO3',
+                            class   => 'metadata',
+                            value   => [
+                                {
+                                    name    => 'FOO4',
+                                    class   => 'metadata',
+                                    value   => [
+                                        {
+                                            name    => 'FOO5',
+                                            class   => 'metadata',
+                                            value   => [
+                                                {
+                                                    name    => 'FOO6',
+                                                    class   => 'metadata',
+                                                    value   => [
+                                                        {
+                                                            name    => 'BAR',
+                                                            class   => 'scalar',
+                                                            type    => 'STR',
+                                                            value   => 'BAZ',
+                                                        },
+                                                        {
+                                                            name    => 'PING',
+                                                            class   => 'scalar',
+                                                            type    => 'STR',
+                                                            value   => 'PONG',
+                                                        },
+                                                    ],
+                                                },
+                                                {
+                                                    name    => 'BAR',
+                                                    class   => 'scalar',
+                                                    type    => 'STR',
+                                                    value   => 'BAZ',
+                                                },
+                                                {
+                                                    name    => 'PING',
+                                                    class   => 'scalar',
+                                                    type    => 'STR',
+                                                    value   => 'PONG',
+                                                },
+                                            ],
+                                        },
+                                        {
+                                            name    => 'BAR',
+                                            class   => 'scalar',
+                                            type    => 'STR',
+                                            value   => 'BAZ',
+                                        },
+                                        {
+                                            name    => 'PING',
+                                            class   => 'scalar',
+                                            type    => 'STR',
+                                            value   => 'PONG',
+                                        },
+                                    ],
+                                },
+                                {
+                                    name    => 'BAR',
+                                    class   => 'scalar',
+                                    type    => 'STR',
+                                    value   => 'BAZ',
+                                },
+                                {
+                                    name    => 'PING',
+                                    class   => 'scalar',
+                                    type    => 'STR',
+                                    value   => 'PONG',
+                                },
+                            ],
+                        },
+                        {
+                            name    => 'BAR',
+                            class   => 'scalar',
+                            type    => 'STR',
+                            value   => 'BAZ',
+                        },
+                        {
+                            name    => 'PING',
+                            class   => 'scalar',
+                            type    => 'STR',
+                            value   => 'PONG',
+                        },
+                    ],
+                },
+                {
+                    name    => 'BAR',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'BAZ',
+                },
+                {
+                    name    => 'PING',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'PONG',
+                },
+            ],
+        },
+    ];
+
+    is_deeply( $config, $tree, "nested metadata structure");
+}
+
+{
+my $example = q{
+FOO1 METADATA
+    BAR     STR BAZ
+    PING    STR PONG
+    FOO2 METADATA
+        BAR     STR BAZ
+        PING    STR PONG
+        FOO3 METADATA
+            BAR     STR BAZ
+            PING    STR PONG
+            FOO4 METADATA
+                BAR     STR BAZ
+                PING    STR PONG
+                FOO5 METADATA
+                    BAR     STR BAZ
+                    PING    STR PONG
+                    FOO6 METADATA
+                        BAR     STR BAZ
+                        PING    STR PONG
+                    END
+                END
+            END
+        END
+    END
+END
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "deeply nested metadata");
+}
+
+{
+my $example = q{
+FOO1 METADATA
+    BAR     STR BAZ
+    FOO2 METADATA
+        BAR     STR BAZ
+        FOO3 METADATA
+            BAR     STR BAZ
+            FOO4 METADATA
+                BAR     STR BAZ
+                FOO5 METADATA
+                    BAR     STR BAZ
+                    FOO6 METADATA
+                        BAR     STR BAZ
+                        PING    STR PONG
+                    END
+                    PING    STR PONG
+                END
+                PING    STR PONG
+            END
+            PING    STR PONG
+        END
+        PING    STR PONG
+    END
+    PING    STR PONG
+END
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "deeply nested metadata");
+}
+
+{
+my $example = q{
+FOO1 METADATA
+    FOO2 METADATA
+        BAR     STR BAZ
+        PING    STR PONG
+    END
+    FOO3 METADATA
+        BAR     STR BAZ
+        PING    STR PONG
+    END
+END
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "two metadata at the same depth");
+}
+
+{
+my $example = q{
+foo METADATA
+END
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+
+    ok( defined( $config ), "empty METADATA");
+
+    my $tree = [
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [],
+        },
+    ];
+
+    is_deeply( $config, $tree, "empty METADATA structure");
+}
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/04_type.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/04_type.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/04_type.t	(revision 22129)
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 04_type.t,v 1.1 2005-03-23 01:53:55 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 9;
+use PS::IPP::Metadata::Config;
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+
+{
+my $example = q{
+TYPE      CELL   EXTNAME   BIASSEC  CHIP
+CELL.00   CELL   CCD00     BSEC-00  CHIP.00
+CELL.01   CELL   CCD01     BSEC-01  CHIP.00
+};
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "basic TYPE");
+}
+
+{
+my $example = q{
+TYPE      CELL   EXTNAME   BIASSEC  CHIP    # comment
+CELL.00   CELL   CCD00     BSEC-00  CHIP.00 # foo
+CELL.01   CELL   CCD01     BSEC-01  CHIP.00 #
+};
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "TYPE with comments");
+}
+
+{
+my $example = q{
+TYPE      CELL   EXTNAME   BIASSEC  CHIP
+CELL.00   CELL   CCD00     BSEC-00  CHIP.00
+CELL.01   CELL   CCD01     BSEC-01  CHIP.00
+FOO METADATA
+    TYPE      CELL   EXTNAME   BIASSEC
+    CELL.00   CELL   CCD00     BSEC-00
+    CELL.01   CELL   CCD01     BSEC-01
+    FOO METADATA
+        TYPE      CELL   EXTNAME
+        CELL.00   CELL   CCD00
+        CELL.01   CELL   CCD01
+    END
+END
+};
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "TYPE not visible in lower scopes");
+}
+
+{
+my $example = q{
+TYPE      CELL   EXTNAME   BIASSEC  CHIP
+FOO METADATA
+    CELL.00   CELL   CCD00     BSEC-00  CHIP.00
+END
+};
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "TYPE not in scope");
+}
+
+{
+my $example = q{
+FOO METADATA
+    TYPE      CELL   EXTNAME   BIASSEC  CHIP
+END
+CELL.00   CELL   CCD00     BSEC-00  CHIP.00
+};
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "TYPE not in scope");
+}
+
+{
+my $example = q{
+FOO METADATA
+    TYPE      CELL   EXTNAME   BIASSEC  CHIP
+END
+BAR METADATA
+    CELL.00   CELL   CCD00     BSEC-00  CHIP.00
+END
+};
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "TYPE not in scope");
+}
+
+
+{
+my $example = q{
+TYPE      CELL   EXTNAME   BIASSEC  CHIP
+CELL.00   CELL   CCD00     BSEC-00
+CELL.01   CELL   CCD01     BSEC-01  CHIP.00
+};
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "TYPE with missing parameters");
+}
+
+{
+my $example = q{
+TYPE      CELL   EXTNAME   BIASSEC  CHIP
+TYPE      CELL   EXTNAME   BIASSEC  CHIP
+CELL.00   CELL   CCD00     BSEC-00  CHIP.00
+CELL.01   CELL   CCD01     BSEC-01  CHIP.00
+};
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "TYPE redefinition");
+}
+
+{
+my $example = q{
+CELL.00   CELL   CCD00     BSEC-00  CHIP.00
+CELL.01   CELL   CCD01     BSEC-01  CHIP.00
+};
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "missing TYPE declaration");
+}
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/05_time.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/05_time.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/05_time.t	(revision 22129)
@@ -0,0 +1,283 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 05_time.t,v 1.5 2006-11-22 23:54:50 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 15;
+use PS::IPP::Metadata::Config;
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+
+{
+my $example =<<END;
+mytime  MULTI
+mytime  UTC     NULL
+mytime  UT1     NULL
+mytime  TAI     NULL
+mytime  TT      NULL
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "NULL times parsed");
+
+    my $tree = [
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'UTC',
+            value   => undef,
+        },
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'UT1',
+            value   => undef,
+        },
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'TAI',
+            value   => undef,
+        },
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'TT',
+            value   => undef,
+        },
+    ];
+
+    is_deeply( $config, $tree, "NULL times structure" );
+}
+
+{
+my $example =<<END;
+mytime  MULTI
+mytime  UTC     NULL    # comment
+mytime  UT1     NULL    # comment
+mytime  TAI     NULL    # comment
+mytime  TT      NULL    # comment
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "NULL times with comments parsed");
+
+    my $tree = [
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'UTC',
+            value   => undef,
+            comment => 'comment',
+        },
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'UT1',
+            value   => undef,
+            comment => 'comment',
+        },
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'TAI',
+            value   => undef,
+            comment => 'comment',
+        },
+        {
+            name    => 'mytime',
+            class   => 'scalar',
+            type    => 'TT',
+            value   => undef,
+            comment => 'comment',
+        },
+    ];
+
+    is_deeply( $config, $tree, "NULL times with comments structure" );
+}
+
+{
+my $example =<<END;
+recently    MULTI
+recently    UTC     2005-03-18T16:05:00
+recently    UT1     2005-03-18T16:05:00
+recently    TAI     2005-03-18T16:05:00
+recently    TT      2005-03-18T16:05:00
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "basic ISO8601 parsed");
+
+    my $tree = [
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'UTC',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'UT1',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'TAI',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'TT',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+    ];
+
+    is_deeply( $config, $tree, "basic ISO8601 structure" );
+}
+
+{
+my $example =<<END;
+recently    MULTI
+recently    UTC     2005-03-18T16:05:00Z
+recently    UT1     2005-03-18T16:05:00Z
+recently    TAI     2005-03-18T16:05:00Z
+recently    TT      2005-03-18T16:05:00Z
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "basic ISO8601 w/Z parsed");
+
+    my $tree = [
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'UTC',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'UT1',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'TAI',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+        {
+            name    => 'recently',
+            class   => 'scalar',
+            type    => 'TT',
+            value   => DateTime::Format::ISO8601->parse_datetime("2005-03-18T16:05:00Z"),
+        },
+    ];
+
+    is_deeply( $config, $tree, "basic ISO8601 w/Z structure" );
+}
+
+{
+my $example =<<END;
+recently    MULTI
+recently    UTC     2005-03-18T16:05:00.000001Z
+recently    UT1     2005-03-18T16:05:00.000001Z
+recently    TAI     2005-03-18T16:05:00.000001Z
+recently    TT      2005-03-18T16:05:00.000001Z
+END
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "basic IS8601 with fractional seconds parsed");
+}
+
+{
+my $example =<<END;
+recently    MULTI
+recently    UTC     2005-03-18T16:05:00Z    # foo
+recently    UT1     2005-03-18T16:05:00Z    # bar
+recently    TAI     2005-03-18T16:05:00Z    # baz
+recently    TT      2005-03-18T16:05:00Z    #
+END
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "ISO8601 with comments parsed");
+}
+
+{
+my $example =<<END;
+recently    MULTI
+recently    UTC     2005-03-18T16:05:00.000001Z    # foo
+recently    UT1     2005-03-18T16:05:00.000001Z    # bar
+recently    TAI     2005-03-18T16:05:00.000001Z    # baz
+recently    TT      2005-03-18T16:05:00.000001Z    #
+END
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "ISO8601 with comments and fractional seconds parsed");
+}
+
+#diag("epoch time format maybe deprecated");
+#
+#{
+#my $example =<<END;
+#recently    MULTI
+#recently    UTC     123456, 5000, 1
+#recently    UT1     123456, 5000
+#recently    TAI     123456, 5000
+#recently    TT      123456, 5000
+#END
+#    my $config = $config_parser->parse( $example );
+#    ok( defined( $config ), "basic epoch time parsed");
+#}
+#
+#{
+#my $example =<<END;
+#recently    MULTI
+#recently    UTC     123456, 5000, 1         # foo
+#recently    UT1     123456, 5000            # bar
+#recently    TAI     123456, 5000            # baz
+#recently    TT      123456, 5000            #
+#END
+#    my $config = $config_parser->parse( $example );
+#    ok( defined( $config ), "epoch time with comments parsed");
+#}
+
+{
+my $example =<<END;
+broken      UTC     2005-03-18 16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
+
+{
+my $example =<<END;
+broken      UT1     2005-03-18 16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
+
+{
+my $example =<<END;
+broken      TAI     2005-03-18 16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
+
+{
+my $example =<<END;
+broken      TT      2005-03-18 16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/06_multi.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/06_multi.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/06_multi.t	(revision 22129)
@@ -0,0 +1,346 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 06_multi.t,v 1.8 2006-10-11 02:49:05 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 15;
+use PS::IPP::Metadata::Config;
+
+{
+my $example = q{
+foo MULTI
+foo S8      -1
+foo STR     bar baz
+foo BOOL    T
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    ok( defined( $config ), "basic MULTI");
+}
+
+{
+my $example = q{
+foo MULTI               # foo
+foo S8      -1          # bar
+foo STR     bar baz     # baz
+foo BOOL    T           #
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    ok( defined( $config ), "MULTI with comments");
+}
+
+{
+my $example = q{
+foo MULTI
+foo S8      -1
+foo STR     bar baz
+foo BOOL    T
+bar METADATA
+    foo MULTI
+    foo S8      -1
+    foo STR     bar baz
+    foo BOOL    T
+END
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    ok( defined( $config ), "MULTI not visible in lower scopes");
+}
+
+{
+my $example = q{
+foo MULTI
+foo METADATA
+    bar BOOL    T
+END
+foo METADATA
+    bar BOOL    T
+END
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+
+    my $tree = [
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [{
+                            name    => 'bar',
+                            class   => 'scalar',
+                            type    => 'BOOL',
+                            value   => 1,
+                        }],
+        },
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [{
+                            name    => 'bar',
+                            class   => 'scalar',
+                            type    => 'BOOL',
+                            value   => 1,
+                        }],
+        },
+    ];
+
+    is_deeply( $config, $tree, "MULTI METADATA structure, declared" );
+}
+
+{
+my $example = q{
+foo METADATA
+    bar BOOL    T
+END
+foo METADATA
+    bar BOOL    T
+END
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+
+    my $tree = [
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [{
+                            name    => 'bar',
+                            class   => 'scalar',
+                            type    => 'BOOL',
+                            value   => 1,
+                        }],
+        },
+    ];
+
+    is_deeply( $config, $tree, "MULTI METADATA structure, not declared" );
+}
+
+{
+my $example = q{
+foo MULTI
+TYPE bar a b c
+foo  bar x y z
+foo  bar x y z
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    ok( defined( $config ), "MULTI TYPE");
+
+    my $tree = [
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'a',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'x',
+                },
+                {
+                    name    => 'b',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'y',
+                },
+                {
+                    name    => 'c',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'z',
+                },
+            ],
+        },
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'a',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'x',
+                },
+                {
+                    name    => 'b',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'y',
+                },
+                {
+                    name    => 'c',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'z',
+                },
+            ],
+        },
+    ];
+
+    is_deeply( $config, $tree, "MULTI TYPE structure, not declared" );
+}
+
+{
+my $example = q{
+TYPE bar a b c
+foo MULTI
+foo  bar x y z
+foo  bar x y z
+};
+
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    ok( defined( $config ), "MULTI TYPE");
+
+    my $tree = [
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'a',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'x',
+                },
+                {
+                    name    => 'b',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'y',
+                },
+                {
+                    name    => 'c',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'z',
+                },
+            ],
+        },
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'a',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'x',
+                },
+                {
+                    name    => 'b',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'y',
+                },
+                {
+                    name    => 'c',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'z',
+                },
+            ],
+        },
+    ];
+
+    is_deeply( $config, $tree, "MULTI TYPE structure, not declared" );
+}
+
+{
+my $example = q{
+TYPE bar a b c
+foo  bar x y z
+foo  bar x y z
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+
+    my $tree = [
+        {
+            name    => 'foo',
+            class   => 'metadata',
+            value   => [
+                {
+                    name    => 'a',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'x',
+                },
+                {
+                    name    => 'b',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'y',
+                },
+                {
+                    name    => 'c',
+                    class   => 'scalar',
+                    type    => 'STR',
+                    value   => 'z',
+                },
+            ],
+        },
+    ];
+
+    is_deeply( $config, $tree, "MULTI TYPE structure, not declared" );
+}
+
+{
+my $example = q{
+foo MULTI
+bar METADATA
+    foo S8      -1
+    foo STR     bar baz
+END
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    # ok because of overwrite
+    ok( defined( $config ), "MULTI not in scope");
+}
+
+{
+my $example = q{
+bar METADATA
+    foo MULTI
+END
+foo S8      -1
+foo STR     bar baz
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    # ok because of overwrite
+    ok( defined( $config ), "MULTI not in scope");
+}
+
+{
+my $example = q{
+bar METADATA
+    foo MULTI
+END
+baz METADATA
+    foo S8      -1
+END
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    # ok because of overwrite
+    ok( defined( $config ), "MULTI not in scope");
+}
+
+{
+my $example = q{
+foo MULTI
+foo MULTI
+foo S8      -1
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    ok( !defined( $config ), "MULTI redeclaration");
+}
+
+{
+my $example = q{
+foo S8      -1
+foo STR     bar baz
+};
+    my $config = PS::IPP::Metadata::Config->new->parse( $example );
+    # ok because of overwrite
+    ok( defined( $config ), "missing MULTI declaration");
+}
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/07_floats.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/07_floats.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/07_floats.t	(revision 22129)
@@ -0,0 +1,209 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 07_floats.t,v 1.1 2006-09-23 00:26:50 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 12;
+use PS::IPP::Metadata::Config;
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+
+{
+my $example =<<END;
+mynan   MULTI
+mynan   F32     nan
+mynan   F32     NaN
+mynan   F32     nAn
+mynan   F32     NAN
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "F32 nans parsed");
+
+    my $tree = [
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F32',
+            value   => 'nan',
+        },
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F32',
+            value   => 'nan',
+        },
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F32',
+            value   => 'nan',
+        },
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F32',
+            value   => 'nan',
+        },
+    ];
+
+    is_deeply( $config, $tree, "F32 nans structure" );
+}
+
+{
+my $example =<<END;
+mynan   MULTI
+mynan   F64     nan
+mynan   F64     NaN
+mynan   F64     nAn
+mynan   F64     NAN
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "F64 nans parsed");
+
+    my $tree = [
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F64',
+            value   => 'nan',
+        },
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F64',
+            value   => 'nan',
+        },
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F64',
+            value   => 'nan',
+        },
+        {
+            name    => 'mynan',
+            class   => 'scalar',
+            type    => 'F64',
+            value   => 'nan',
+        },
+    ];
+
+    is_deeply( $config, $tree, "F64 nans structure" );
+}
+
+my $inftree = [
+    # inf
+    {
+        name    => 'myinf',
+        class   => 'scalar',
+        type    => 'F32',
+        value   => '+inf',
+    },
+    {
+        name    => 'myinf',
+        class   => 'scalar',
+        type    => 'F32',
+        value   => '+inf',
+    },
+    {
+        name    => 'myinf',
+        class   => 'scalar',
+        type    => 'F32',
+        value   => '-inf',
+    },
+    {
+        name    => 'myinf',
+        class   => 'scalar',
+        type    => 'F32',
+        value   => '+inf',
+    },
+    {
+        name    => 'myinf',
+        class   => 'scalar',
+        type    => 'F32',
+        value   => '+inf',
+    },
+    {
+        name    => 'myinf',
+        class   => 'scalar',
+        type    => 'F32',
+        value   => '-inf',
+    },
+];
+
+{
+my $example =<<END;
+myinf   MULTI
+myinf   F32     inf
+myinf   F32     +inf
+myinf   F32     -inf
+myinf   F32     infinity
+myinf   F32     +infinity
+myinf   F32     -infinity
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "F32 infs parsed");
+
+    is_deeply( $config, $inftree, "F32 infs structure" );
+}
+
+{
+my $example =<<END;
+myinf   MULTI
+myinf   F32     InF
+myinf   F32     +InF
+myinf   F32     -InF
+myinf   F32     InFinity
+myinf   F32     +InFinity
+myinf   F32     -InFinity
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "F32 InFs parsed");
+
+    is_deeply( $config, $inftree, "F32 InFs structure" );
+}
+
+{
+my $example =<<END;
+myinf   MULTI
+myinf   F32     iNf
+myinf   F32     +iNf
+myinf   F32     -iNf
+myinf   F32     iNfinity
+myinf   F32     +iNfinity
+myinf   F32     -iNfinity
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "F32 iNfs parsed");
+
+    is_deeply( $config, $inftree, "F32 iNfs structure" );
+}
+
+{
+my $example =<<END;
+myinf   MULTI
+myinf   F32     INF
+myinf   F32     +INF
+myinf   F32     -INF
+myinf   F32     INFINITY
+myinf   F32     +INFINITY
+myinf   F32     -INFINITY
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "F32 INFs parsed");
+
+    is_deeply( $config, $inftree, "F32 INFs structure" );
+}
Index: /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/08_strings.t
===================================================================
--- /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/08_strings.t	(revision 22129)
+++ /tags/ipp-1-X/v1_01/PS-IPP-Metadata-Config/t/08_strings.t	(revision 22129)
@@ -0,0 +1,86 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2006  Joshua Hoblitt
+#
+# $Id: 08_strings.t,v 1.3 2007-07-06 02:27:32 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 6;
+use PS::IPP::Metadata::Config;
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+
+{
+my $example =<<END;
+mystr   MULTI
+mystr   STR     somevalue
+mystr   STR     somevaluebeforeacomment             # some comment
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "strs parsed");
+
+    my $tree = [
+        {
+            name    => 'mystr',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'somevalue',
+        },
+        {
+            name    => 'mystr',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => 'somevaluebeforeacomment',
+            comment => 'some comment',
+        },
+    ];
+
+    is_deeply( $config, $tree, "str structure" );
+}
+
+{
+my $example =<<END;
+mystr   MULTI
+mystr   STR     NULL 
+mystr   STR     NULL    # some comment
+END
+
+    my $config = $config_parser->parse( $example );
+    ok( defined( $config ), "NULL strs parsed");
+
+    my $tree = [
+        {
+            name    => 'mystr',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => undef,
+        },
+        {
+            name    => 'mystr',
+            class   => 'scalar',
+            type    => 'STR',
+            value   => undef,
+            comment => 'some comment',
+        },
+    ];
+
+    is_deeply( $config, $tree, "NULL str structure" );
+}
+
+{
+my $example =<<END;
+mystr   STR     # some comment
+END
+
+    my $config = $config_parser->parse( $example );
+    ok(!defined( $config ), "STR without value");
+
+    is_deeply( $config, undef, "NULL str structure" );
+}
