Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/.cvsignore
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/.cvsignore	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/.cvsignore	(revision 22118)
@@ -0,0 +1,6 @@
+Build
+META.yml
+Makefile
+Makefile.PL
+_build
+blib
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Build.PL
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Build.PL	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Build.PL	(revision 22118)
@@ -0,0 +1,17 @@
+use Module::Build;
+# See perldoc Module::Build for details of how this works
+
+Module::Build->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',
+    },
+)->create_build_script;
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Changes
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Changes	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Changes	(revision 22118)
@@ -0,0 +1,6 @@
+Revision history for Perl module PS::IPP::Metadata::Config
+
+0.01 Tue Feb 22 15:46:35 2005
+	- original version; created by ExtUtils::ModuleMaker 0.32
+
+
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/LICENSE
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/LICENSE	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/LICENSE	(revision 22118)
@@ -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/v0_01/PS-IPP-Metadata-Config/MANIFEST
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/MANIFEST	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/MANIFEST	(revision 22118)
@@ -0,0 +1,17 @@
+Build.PL
+Changes
+LICENSE
+MANIFEST
+Makefile.PL
+README
+Todo
+docs/sdrs_grammar.txt
+lib/PS/IPP/Metadata/Config.pm
+lib/PS/IPP/Metadata/Config.pod
+scripts/mddump.pl
+t/01_load.t
+t/02_examples.t
+t/03_metadata.t
+t/04_type.t
+t/05_time.t
+t/06_multi.t
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/README
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/README	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/README	(revision 22118)
@@ -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/v0_01/PS-IPP-Metadata-Config/Todo
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Todo	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/Todo	(revision 22118)
@@ -0,0 +1,5 @@
+TODO list for Perl module PS::IPP::Metadata::Config
+
+- Nothing yet
+
+
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/docs/sdrs_grammar.txt	(revision 22118)
@@ -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/v0_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pm	(revision 22118)
@@ -0,0 +1,453 @@
+# Copyright (c) 2005  Joshua Hoblitt
+#
+# $Id: Config.pm,v 1.16 2005-05-04 22:09:28 jhoblitt Exp $
+
+package PS::IPP::Metadata::Config;
+
+use strict;
+use warnings FATAL => qw( all );
+
+our $VERSION = '0.01';
+
+use Carp qw( carp );
+use Parse::RecDescent;
+
+use base qw( Class::Accessor::Fast );
+__PACKAGE__->mk_accessors( qw( overwrite ) );
+
+#$::RD_TRACE = 1;
+#$::RD_HINT = 1;
+#use Data::Dumper;
+
+my $grammar = do{ local $/; <DATA> };
+
+sub new {
+    my $class = shift;
+
+    my $self = { _parser => Parse::RecDescent->new( $grammar ) };
+
+    bless $self, $class;
+
+    $self->overwrite( undef );
+
+    return $self;
+}
+
+sub parse {
+    my ( $self, $metadata ) = @_;
+
+    # 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;
+
+    #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;
+}
+
+1;
+
+__DATA__
+
+{
+    use strict;
+
+    use Carp qw( carp );
+
+    our @scope_stack;
+}
+
+startrule: grammar eofile
+    { $item{grammar} }
+
+grammar:
+    statement(s)
+        {
+            $thisparser->{local} = pop @scope_stack;
+            [ grep $_->{class} !~ /comment_line/, @{$item[1]} ];
+        }
+
+statement:
+    scalar
+    | vector
+    | comment_line
+    | typedef
+    | metadata
+    | typedef_declare
+    | multi_declare
+    | time
+    | <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 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];
+        }
+
+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>
+
+metadata:
+    metadata_name <commit> 
+    { push @scope_stack, $thisparser->{local}; $thisparser->{local} = {} }
+    grammar
+    metadata_end
+        {
+            my $text = $item[2];
+            
+            if ( defined( $text ) ) {
+                $return = {
+                    class   => 'metadata',
+                    name    => $item{metadata_name},
+                    value   => $item{grammar},
+                };
+            } else {
+                $return = undef;
+            }
+        } 
+    | <error?:bad METADATA syntax: $text> <reject>
+
+comment_line:
+    <skip:'[ \t\r]*'> comment(?) "\n"
+        {{ class   => 'comment_line' }}
+
+time:
+    <skip:'[ \t\r]*'> name ttype <matchrule:$item{ttype}> comment(?) "\n"
+        {
+            use DateTime::Format::ISO8601;
+
+            $return = {
+                class   => 'time',
+                name    => $item{name},
+                type    => $item{ttype},
+                value   => $item[4],
+            };
+
+            $return->{comment} = $item{'comment(?)'}[0]
+                if $item{'comment(?)'}[0];
+        }
+
+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,
+                };
+            }
+        }
+    | <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};
+            }
+        }
+
+metadata_name:
+    <skip:'[ \t\r]*'> name /METADATA/i comment(?) "\n"
+        { $item{name} } 
+    
+metadata_end:
+     <skip:'[ \t\r]*'> /END/i comment(?) "\n"
+
+comment:
+    '#' to_end_of_line
+        { $item{to_end_of_line} }
+
+vname:
+    '@' name
+        { $item{name} }
+
+name:
+    /[a-z][.\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: 
+    'STR'
+    | 'STRING'
+    | vtype
+
+vtype: 
+    'S8'
+    | 'S16'
+    | 'S32'
+    | 'S64'
+    | 'U8' 
+    | 'U16'
+    | 'U32'
+    | 'U64'
+    | 'F32'
+    | 'F64'
+    | 'C32'
+    | 'C64'
+    | 'BOOL'
+
+ttype:
+    'UTC'
+    | 'UT1'
+    | 'TAI'
+    | 'TT'
+
+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]} ] }
+
+tvalue:
+    iso8601
+    | epoch
+
+# backtracking optimization
+_vvalue:
+    <matchrule:$arg[0]> vector_sep(?)
+        { [ $item[1] ] }
+
+vector_sep:
+    /,|\s+/
+
+int:
+    # $RE{num}{int} from Regexp::Common::number
+    /(?:(?:[+-]?)(?:[0-9]+))/
+
+float:
+    # based on $RE{num}{real} from Regexp::Common::number
+    /(?:(?i)(?:[+-]?)(?:(?=[0-9]|[.])(?:[0-9]*)(?:(?:[.])(?:[0-9]{0,}))?)(?:(?:[Ee])(?:(?:[+-]?)(?:[0-9]+))|))/
+
+bool:
+    /[tf]/i
+        { $item[1] =~ /t/i ? 1 : 0 }
+
+string:
+    /(?:\S[^#\n]*)?[^#\n ]/
+
+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
+    | utc_epoch
+
+UT1:
+    iso8601
+    | epoch
+
+TAI:
+    iso8601
+    | epoch
+
+TT:
+    iso8601
+    | epoch
+
+iso8601:
+    # based on code from DateTime::Format::ISO8601
+    / (\d{4}) - (\d\d) - (\d\d) T (\d\d) : (\d\d) : (\d\d) Z /x
+        { DateTime::Format::ISO8601->parse_datetime( $item[1] ) }
+
+utc_epoch:
+    / (-?\d{1,19}) \s*,\s* (\d{1,9}) (?:\s*,\s* (\d))? /x
+        {{
+            sec         => $1,
+            nsec        => $2,
+            leapsecond  => $3,
+        }}
+
+epoch:
+    / (-?\d{1,19}) \s*,\s* (\d{1,9}) /x
+        {{
+            sec     => $1,
+            nsec    => $2,
+        }}
+
+eofile:
+    /^\z/
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/lib/PS/IPP/Metadata/Config.pod	(revision 22118)
@@ -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/v0_01/PS-IPP-Metadata-Config/scripts/mddump.pl
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/scripts/mddump.pl	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/scripts/mddump.pl	(revision 22118)
@@ -0,0 +1,136 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: mddump.pl,v 1.2 2005-05-04 22:14:18 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 );
+GetOptions(
+    'file=s'    => \$file,
+    'overwrite' => \$overwrite,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+
+# open STDIN if no file was specified
+$file = '-' unless $file;
+
+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] --file <filename>
+
+    or
+
+    cat <filename> | mddump.pl [--overwrite]
+
+    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 * --file <filename> | -f <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 * --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/v0_01/PS-IPP-Metadata-Config/t/01_load.t
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/01_load.t	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/01_load.t	(revision 22118)
@@ -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/v0_01/PS-IPP-Metadata-Config/t/02_examples.t
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/02_examples.t	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/02_examples.t	(revision 22118)
@@ -0,0 +1,370 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 02_examples.t,v 1.4 2005-05-04 22:08:08 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 = 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/v0_01/PS-IPP-Metadata-Config/t/03_metadata.t
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/03_metadata.t	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/03_metadata.t	(revision 22118)
@@ -0,0 +1,178 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 03_metadata.t,v 1.3 2005-05-04 22:05:24 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 7;
+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");
+}
+
+{
+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");
+}
+
+{
+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");
+}
+
+{
+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 $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");
+}
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/04_type.t
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/04_type.t	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/04_type.t	(revision 22118)
@@ -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/v0_01/PS-IPP-Metadata-Config/t/05_time.t
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/05_time.t	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/05_time.t	(revision 22118)
@@ -0,0 +1,93 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 05_time.t,v 1.2 2005-04-05 00:16:41 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use lib qw( ./lib );
+
+#$::RD_TRACE = 1;
+
+use Test::More tests => 8;
+use PS::IPP::Metadata::Config;
+
+my $config_parser = PS::IPP::Metadata::Config->new;
+
+{
+my $example =<<END;
+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 IS8601");
+}
+
+{
+my $example =<<END;
+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");
+}
+
+{
+my $example =<<END;
+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");
+}
+
+{
+my $example =<<END;
+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");
+}
+
+{
+my $example =<<END;
+broken      UTC     2005-03-18T16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
+
+{
+my $example =<<END;
+broken      UT1     2005-03-18T16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
+
+{
+my $example =<<END;
+broken      TAI     2005-03-18T16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
+
+{
+my $example =<<END;
+broken      TT      2005-03-18T16:05:00
+END
+    my $config = $config_parser->parse( $example );
+    ok( !defined( $config ), "bad format");
+}
Index: /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/06_multi.t
===================================================================
--- /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/06_multi.t	(revision 22118)
+++ /tags/ipp-1-X/v0_01/PS-IPP-Metadata-Config/t/06_multi.t	(revision 22118)
@@ -0,0 +1,218 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: 06_multi.t,v 1.5 2005-03-24 22:32:55 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{
+foo MULTI
+foo S8      -1
+foo STR     bar baz
+foo BOOL    T
+};
+    my $config = $config_parser->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 = $config_parser->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 = $config_parser->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 = $config_parser->parse( $example );
+    ok( defined( $config ), "MULTI METADATA");
+}
+
+{
+my $example = q{
+foo METADATA
+    bar BOOL    T
+END
+foo METADATA
+    bar BOOL    T
+END
+};
+    my $config = $config_parser->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 = $config_parser->parse( $example );
+    ok( defined( $config ), "MULTI TYPE");
+}
+
+{
+my $example = q{
+TYPE bar a b c
+foo MULTI
+foo  bar x y z
+foo  bar x y z
+};
+    my $config = $config_parser->parse( $example );
+
+    ok( defined( $config ), "MULTI TYPE");
+}
+
+{
+my $example = q{
+TYPE bar a b c
+foo  bar x y z
+foo  bar x y z
+};
+    my $config = $config_parser->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 = $config_parser->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 = $config_parser->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 = $config_parser->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 = $config_parser->parse( $example );
+    ok( !defined( $config ), "MULTI redeclaration");
+}
+
+{
+my $example = q{
+foo S8      -1
+foo STR     bar baz
+};
+    my $config = $config_parser->parse( $example );
+    # ok because of overwrite
+    ok( defined( $config ), "missing MULTI declaration");
+}
Index: /tags/ipp-1-X/v0_01/glueforge/glueforge.in
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/glueforge.in	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/glueforge.in	(revision 22118)
@@ -0,0 +1,321 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2005  Joshua Hoblitt
+#
+# $Id: glueforge.in,v 1.27 2005-06-23 01:59:38 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use Data::Dumper;
+use PS::IPP::Metadata::Config;
+use Template;
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version );
+use Pod::Usage qw( pod2usage );
+
+my ( $input, $output, $template );
+GetOptions(
+    'input=s'       => \$input,
+    'output=s'      => \$output,
+    'template=s'    => \$template,
+) || pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required option: --input|-i", -exitval => 2 ) unless $input;
+
+$template = "./template" unless $template;
+
+die "file doesn't exist: $input" unless -e $input;
+die "file isn't readable: $input" unless -r $input;
+
+my $config = parse_config( $input );
+
+my %data;
+
+# find table name
+for (my $i = 0; $i < @{$config}; $i++) {
+    my $elem = $config->[$i];
+
+    if ( $elem->{name} eq "table" ) {
+        $data{table_name} = $elem->{value};
+
+        # delete element from array
+        splice @$config, $i, 1;
+
+        last;
+    }
+}
+
+$data{pkg_name}    = $data{table_name} . "db";
+$data{file_name}   = $data{pkg_name};
+$data{namespace}   = $data{table_name};
+$data{object_name} = $data{namespace} . "Row";
+
+my @items;
+
+# setup items
+for (my $i = 0; $i < @{$config}; $i++) {
+    my $elem = $config->[$i];
+
+    my $mdtypes = lookup_type( $elem->{type} );
+    next if ! defined $mdtypes;
+
+    push @items, {
+        name    => $elem->{name},
+        type    => $elem->{type},
+        ctype   => $mdtypes->{ctype},
+        mtype   => $mdtypes->{mtype},
+        test    => $mdtypes->{test},
+        comment => $elem->{comment},
+        value   => $elem->{value},
+    };
+}
+
+$data{columns} = \@items;
+
+
+$output = $data{pkg_name} unless defined $output;
+
+my %tt = (
+    'autogen_sh.tt'         => "$output/autogen.sh",
+    'configure_ac.tt'       => "$output/configure.ac",
+    'pkgconfig_pc_in.tt'    => "$output/$data{pkg_name}.pc.in",
+    'doxyfile_in.tt'        => "$output/Doxyfile.in",
+    'top_makefile_am.tt'    => "$output/Makefile.am",
+    'src_makefile_am.tt'    => "$output/src/Makefile.am",
+    'header.tt'             => "$output/src/$data{file_name}.h",
+    'code.tt'               => "$output/src/$data{file_name}.c",
+    'tests_makefile_am.tt'  => "$output/tests/Makefile.am",
+    'testsuite_at.tt'       => "$output/tests/testsuite.at",
+    'dbsetup_c.tt'          => "$output/tests/dbsetup.c",
+    'dbcleanup_c.tt'        => "$output/tests/dbcleanup.c",
+    'alloc_c.tt'            => "$output/tests/alloc.c",
+    'init_c.tt'             => "$output/tests/init.c",
+    'cleanup_c.tt'          => "$output/tests/cleanup.c",
+    'createtable_c.tt'      => "$output/tests/createtable.c",
+    'droptable_c.tt'        => "$output/tests/droptable.c",
+    'insert_c.tt'           => "$output/tests/insert.c",
+    'pop_c.tt'              => "$output/tests/pop.c",
+    'insertobject_c.tt'     => "$output/tests/insertobject.c",
+    'popobject_c.tt'        => "$output/tests/popobject.c",
+    'insertfits_c.tt'       => "$output/tests/insertfits.c",
+    'popfits_c.tt'          => "$output/tests/popfits.c",
+    'selectrowsfits_c.tt'   => "$output/tests/selectrowsfits.c",
+    'metadatafromobject_c.tt' => "$output/tests/metadatafromobject.c",
+    'objectfrommetadata_c.tt' => "$output/tests/objectfrommetadata.c",
+);
+
+my $mangler = Template->new({ INCLUDE_PATH => $template });
+
+foreach my $t ( keys %tt ) {
+    $mangler->process( $t, \%data, $tt{$t})
+        or die $mangler->error;
+}
+
+# make autogen.sh executable
+# permissions are in octal
+chmod 0744, $tt{'autogen_sh.tt'}; 
+
+sub parse_config {
+    my $file = shift;
+
+    my $mdparser = PS::IPP::Metadata::Config->new;
+    $mdparser->overwrite( 1 );
+
+    open( my $data, $file ) or die "can't open file: $!";
+    my $example = do { local $/; <$data> };
+    close( $data ) or die "can't close file: $!";
+
+    my $config = $mdparser->parse( $example )
+        or die "error parsing file: $input";
+
+    return $config;
+}
+
+sub lookup_type {
+    my $type = shift;
+
+    my %primitives = map { $_ => 1 } qw( S8 S16 S32 S64 U8 U16 U32 U64 F32 F64 );
+    my %mtypes = map { $_ => "PS_META_$_" } keys %primitives;
+    my %ctypes = map { $_ => "ps$_" } keys %primitives;
+
+    my %ttypes = (
+        S8      => -8,
+        S16     => -16,
+        S32     => -32,
+        S64     => -64,
+        U8      => 8,
+        U16     => 16,
+        U32     => 32,
+        U64     => 64,
+        F32     => 32.32,
+        F64     => 64.64,
+    );
+
+    if ( $primitives{$type} ) {
+        return {
+            ctype   => $ctypes{$type},
+            mtype   => $mtypes{$type},
+            test    => $ttypes{$type},
+        };
+    } elsif ( $type =~ /STR|STRING/ ) {
+        return {
+            ctype   => "char*",
+            mtype   => 'PS_META_STR',
+            test    => '"a string"',
+        };
+    } elsif ( $type =~ /BOOL/ ) {
+        return {
+            ctype   => "bool",
+            mtype   => 'PS_META_BOOL',
+            test    => 'true',
+        };
+    } else {
+        return undef;
+    }
+}
+
+__END__
+
+=pod
+
+=head1 NAME
+
+md2db.pl - auto-generate 'meta' database bindings
+
+=head1 SYNOPSIS
+
+    md2db.pl --input <filename> [--output <dirname>] [--template <dirname>]
+
+=head1 DESCRIPTION
+
+This program generates a set of database table specific bindings in the ANSI C
+language.  The code generated is largely just wrapper functions around the
+C<pslib> C<DB> API.  A simple table description is read in and a directory
+containing a complete, distributable software package is created.
+
+=head2 Features
+
+=over 4
+
+=item * Generates a complete autoconf/automake/libtool package.
+
+=item * Generated packages include an C<autogen.sh> script.
+
+=item * Installs a pkgconfig C<.pc> data file.
+
+=item * Includes a table specific autotest test suite.
+
+=item * Builds both shared and static libraries by default.
+
+=item * Generates man pages and HTML documentation with Doxygen.
+
+=item * Header files should be C++ safe.
+
+=item * Functions to open and close database connections.
+
+=item * Functions to create and destroy a table of the appropriate format.
+
+=item * Functions to insert and pop a row.
+
+=item * Functions to insert and pop a simple object representing a row.
+
+=item * Functions to insert, select, and pop to/from FITS files.
+
+=item * Functions to convert between row structs and C<psMetadata>s.
+
+=back
+
+=head1 TABLE DESCRIPTION FORMAT
+
+A simple config files specifies the name of the table, the name and types of
+all columns, and can optional specify which column are to be used as indexes.
+These C<Table Description> files are in the C<Configuration files> format as
+described in C<PSDC-430-007>.
+
+=head2 Example table description
+
+    table   STR     foo
+    foo     STR     60      # Primary Key
+    bar     S32     0
+    baz     F32     0.0
+    boing   F64     0.0
+    zot     BOOL    t       # Key
+
+The key name C<table> is reserved and specifies the name of the database table.
+It may appear any where in the file but it is most I<convenient> to have it as
+the first line of the table description.
+
+The value of a C<STR> column specifies the maximum number of characters that
+the database is required to store for that field.  The values for C<S32>,
+C<F32>, C<F64>, & C<BOOL> columns are ignored.
+
+Keys maybe specified as a C<Primary Key> or C<Key>.  Only one C<Primary Key>
+may be specified per table.
+
+The key name C<position> is also reserved as it is used internally by the
+database bindings.
+
+=head1 OPTIONS
+
+=over 4
+
+=item * --input|-i <filename>
+
+File to read table description from.
+
+=item * --output|-o <dirname>
+
+Send the generate files to this directory name.
+
+Defaults to the name of the database table.
+
+=item * --template|-t <dirname>
+
+Directory to load template files from.
+
+Defaults to C<./template>.
+
+=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 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/v0_01/glueforge/templates/psdb/alloc.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc.tt	(revision 22118)
@@ -0,0 +1,40 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+static void [% object_name %]Free([% object_name %] *object);
+
+[% object_name %] *[% object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+const char *[% item.name %]
+[%- ELSE -%]
+[% item.ctype %] [% item.name %]
+[%- END -%]
+[% END -%]
+)
+{
+    [% indented(object_name, "*object") %];
+
+    object = psAlloc(sizeof([% object_name %]));
+    psMemSetDeallocator(object, (psFreeFcn)[% object_name %]Free);
+
+[% FOREACH item = columns -%]
+[% IF item.type == "STR" -%]
+    object->[% item.name %] = psStringCopy([% item.name %]);
+[% ELSE -%]
+    object->[% item.name %] = [% item.name %];
+[% END -%]
+[% END -%]
+
+    return object;
+}
+
+static void [% object_name %]Free([% object_name %] *object)
+{
+[% FOREACH item = columns -%]
+[% IF item.type == "STR" -%]
+    psFree(object->[% item.name %]);
+[% END -%]
+[% END -%]
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_at.tt	(revision 22118)
@@ -0,0 +1,6 @@
+AT_SETUP([[% object_name %]Alloc()])
+AT_KEYWORDS([[% object_name %]Alloc])
+
+AT_CHECK([alloc])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_c.tt	(revision 22118)
@@ -0,0 +1,38 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    [% indented(object_name, "*object") %];
+
+    object = [% object_name %]Alloc(
+[%- SET i = 0 -%]
+[%- FOREACH item = columns -%]
+    [%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+);
+
+    if (!object) {
+        exit(EXIT_FAILURE);
+    }
+
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" or item.type == "BOOL" -%]
+    if (!object->[% item.name %] == [% item.test %]) {
+[% ELSIF item.type == "STR" -%]
+    if (strncmp(object->[% item.name %], [% item.test %], MAX_STRING_LENGTH)) {
+[% END -%]
+        psFree(object);
+        exit(EXIT_FAILURE);
+    }
+[% END -%]
+
+    psFree(object);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/alloc_h.tt	(revision 22118)
@@ -0,0 +1,18 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** Creates a new [% object_name %] object
+ *
+ *  @return A new [% object_name %] object or NULL on failure.
+ */
+
+[% object_name %] *[% object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %],[% END %]
+[% IF item.type == "STR" -%]
+    [% indented("const char", "*$item.name") %]
+[%- ELSE -%]
+    [% indented("$item.ctype", "$item.name") %]
+[%- END -%]
+[% END %]
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/autogen_sh.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/autogen_sh.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/autogen_sh.tt	(revision 22118)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=[% pkg_name %]
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/bootstrap_sh.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/bootstrap_sh.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/bootstrap_sh.tt	(revision 22118)
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+libtoolize --automake --copy \
+&& aclocal \
+&& autoheader \
+&& automake --foreign --add-missing --copy \
+&& autoconf
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup.tt	(revision 22118)
@@ -0,0 +1,4 @@
+void [% namespace %]Cleanup(psDB *dbh)
+{
+    psDBCleanup(dbh);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_at.tt	(revision 22118)
@@ -0,0 +1,6 @@
+AT_SETUP([[% namespace %]Cleanup()])
+AT_KEYWORDS([[% namespace %]Cleanup])
+
+AT_CHECK([cleanup])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_c.tt	(revision 22118)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    [% namespace %]Cleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/cleanup_h.tt	(revision 22118)
@@ -0,0 +1,6 @@
+/** Closes a database connection
+ */
+
+void [% namespace %]Cleanup(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/code.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/code.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/code.tt	(revision 22118)
@@ -0,0 +1,20 @@
+#include "[% namespace %]db.h"
+
+#define [% namespace FILTER upper %]_TABLE_NAME "[% table_name %]"
+#define [% namespace FILTER upper %]_INDEX_NAME "position"
+#define MAX_STRING_LENGTH 1024
+
+[% INCLUDE alloc.tt %]
+[% INCLUDE init.tt %]
+[% INCLUDE cleanup.tt %]
+[% INCLUDE createtable.tt %]
+[% INCLUDE droptable.tt %]
+[% INCLUDE insert.tt %]
+[% INCLUDE pop.tt %]
+[% INCLUDE insertobject.tt %]
+[% INCLUDE popobject.tt %]
+[% INCLUDE insertfits.tt %]
+[% INCLUDE popfits.tt %]
+[% INCLUDE selectrowsfits.tt %]
+[% INCLUDE metadatafromobject.tt %]
+[% INCLUDE objectfrommetadata.tt -%]
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/configure_ac.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/configure_ac.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/configure_ac.tt	(revision 22118)
@@ -0,0 +1,41 @@
+AC_PREREQ(2.59)
+
+AC_INIT([[% pkg_name %]], [0.0.1], [pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([[% pkg_name %].pc.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_CONFIG_TESTDIR([tests])
+AC_CONFIG_FILES([tests/Makefile])
+AM_MISSING_PROG([AUTOM4TE], [autom4te])
+
+AC_LANG(C)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 0.5.0]) 
+
+AC_PATH_PROG([PERL], [perl], [missing])
+if test "$PERL" = "missing" ; then
+  AC_MSG_ERROR([perl is required])
+fi
+
+AC_PATH_PROG([DOXYGEN], [doxygen], [missing])
+if test "$DOXYGEN" = "missing" ; then
+  AC_MSG_ERROR([doxygen is required])
+fi
+
+dnl is this the best was to setup recursive CFLAGS?
+[% pkg_name %]_CFLAGS="-Wall -pedantic -std=c99 -fno-strict-aliasing"
+AC_SUBST([[% pkg_name %]_CFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  [% pkg_name %].pc
+  Doxyfile
+])
+AC_OUTPUT
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable.tt	(revision 22118)
@@ -0,0 +1,39 @@
+bool [% namespace %]CreateTable(psDB *dbh)
+{
+    psMetadata          *md;
+    bool                status;
+
+    md = psMetadataAlloc();
+    psMetadataAdd(md, PS_LIST_TAIL, [% namespace FILTER upper %]_INDEX_NAME, PS_META_S32, "AUTO_INCREMENT", 0.0);
+[% FOREACH item = columns -%]
+[% IF item.comment;
+    SET item.comment = "\"$item.comment\"";
+ELSE;
+    SET item.comment = 'NULL';
+END;
+-%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", [% item.comment %], [% item.value %])) {
+[% ELSIF item.type == "STR" -%]
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", [% item.comment %], "[% item.value %]")) {
+[% ELSIF item.type == "BOOL" -%]
+[% IF item.value == 1;
+    SET item.value = "true";
+ELSE;
+    SET item.value = "false";
+END;
+-%]
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_META_BOOL, [% item.comment %], [% item.value %])) {
+[% END -%]
+        psError(PS_ERR_UNKNOWN, false, "failed to add item [% item.name %]");
+        psFree(md);
+        return false;
+    }
+[% END -%]
+
+    status = psDBCreateTable(dbh, [% namespace FILTER upper %]_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_at.tt	(revision 22118)
@@ -0,0 +1,9 @@
+AT_SETUP([[% namespace %]CreateTable()])
+AT_KEYWORDS([[% namespace %]CreateTable])
+
+# make sure the table is not there.
+AT_CHECK([dbcleanup])
+AT_CHECK([createtable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_c.tt	(revision 22118)
@@ -0,0 +1,21 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    if(![% namespace %]CreateTable(dbh)) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/createtable_h.tt	(revision 22118)
@@ -0,0 +1,8 @@
+/** Creates a new [% table_name %] table
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]CreateTable(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/dbcleanup_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/dbcleanup_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/dbcleanup_c.tt	(revision 22118)
@@ -0,0 +1,18 @@
+#include <pslib.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS [% table_name %]");
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/dbsetup_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/dbsetup_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/dbsetup_c.tt	(revision 22118)
@@ -0,0 +1,21 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // remove the table if it already exists
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS [% table_name %]");
+    [% namespace %]CreateTable(dbh);
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/doxyfile_in.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/doxyfile_in.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/doxyfile_in.tt	(revision 22118)
@@ -0,0 +1,1219 @@
+# Doxyfile 1.4.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = @PACKAGE_NAME@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = @PACKAGE_VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+# @top_builddir@ doesn't work for some reason
+OUTPUT_DIRECTORY       = @builddir@/docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output. 
+# The encoding is not always determined by the language that is chosen, 
+# but also whether or not the output is meant for Windows or non-Windows users. 
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES 
+# forces the Windows encoding (this is the default for the Windows binary), 
+# whereas setting the tag to NO uses a Unix-style encoding (the default for 
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources 
+# only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the progam writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @top_srcdir@/src
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = @PERL@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that a graph may be further truncated if the graph's 
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH 
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), 
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable.tt	(revision 22118)
@@ -0,0 +1,4 @@
+bool [% namespace %]DropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, [% namespace FILTER upper %]_TABLE_NAME);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_at.tt	(revision 22118)
@@ -0,0 +1,9 @@
+AT_SETUP([[% namespace %]DropTable()])
+AT_KEYWORDS([[% namespace %]DropTable])
+
+# make sure the table is not there.
+AT_CHECK([dbsetup])
+AT_CHECK([droptable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_c.tt	(revision 22118)
@@ -0,0 +1,21 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (![% namespace %]DropTable(dbh)) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/droptable_h.tt	(revision 22118)
@@ -0,0 +1,8 @@
+/** Deletes a [% table_name %] table
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]DropTable(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/header.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/header.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/header.tt	(revision 22118)
@@ -0,0 +1,35 @@
+#ifndef [% namespace FILTER upper %]_DB_H
+#define [% namespace FILTER upper %]_DB_H 1
+
+#include <pslib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @addtogroup [% pkg_name %]
+/// @{
+
+[% INCLUDE object_h.tt %]
+[% INCLUDE alloc_h.tt %]
+[% INCLUDE init_h.tt %]
+[% INCLUDE cleanup_h.tt %]
+[% INCLUDE createtable_h.tt %]
+[% INCLUDE droptable_h.tt %]
+[% INCLUDE insert_h.tt %]
+[% INCLUDE pop_h.tt %]
+[% INCLUDE insertobject_h.tt %]
+[% INCLUDE popobject_h.tt %]
+[% INCLUDE insertfits_h.tt %]
+[% INCLUDE popfits_h.tt %]
+[% INCLUDE selectrowsfits_h.tt %]
+[% INCLUDE metadatafromobject_h.tt %]
+[% INCLUDE objectfrommetadata_h.tt -%]
+
+/// @}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // [% namespace FILTER upper %]_DB_H
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init.tt	(revision 22118)
@@ -0,0 +1,4 @@
+psDB *[% namespace %]Init(const char *host, const char *user, const char *passwd, const char *dbname)
+{
+    return psDBInit(host, user, passwd, dbname);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_at.tt	(revision 22118)
@@ -0,0 +1,6 @@
+AT_SETUP([[% namespace %]Init()])
+AT_KEYWORDS([[% namespace %]Init])
+
+AT_CHECK([init])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_c.tt	(revision 22118)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = [% namespace %]Init("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/init_h.tt	(revision 22118)
@@ -0,0 +1,12 @@
+/** Opens a new database connection
+ *
+ *  @return A new psDB object if the database connection is successful or NULL
+ *  on failure.
+ */
+
+psDB *[% namespace %]Init(
+    const char      *host,              ///< Database server hostname
+    const char      *user,              ///< Database username
+    const char      *passwd,            ///< Database password
+    const char      *dbname             ///< Database namespace
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert.tt	(revision 22118)
@@ -0,0 +1,35 @@
+bool [% namespace %]Insert(psDB * dbh,
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %] [% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+const char *[% item.name %]
+[%- ELSE -%]
+[% item.ctype %] [% item.name %]
+[%- END -%]
+[% END -%]
+)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", NULL, [% item.name %])) {
+[% ELSIF item.type == "STR" -%]
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", NULL, psStringCopy([% item.name %]))) {
+[% ELSIF item.type == "BOOL" -%]
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_META_BOOL, NULL, [% item.name %])) {
+[% END -%]
+        psError(PS_ERR_UNKNOWN, false, "failed to add item [% item.name %]");
+        psFree(md);
+        return false;
+    }
+[% END -%]
+
+    status = psDBInsertOneRow(dbh, [% namespace FILTER upper %]_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_at.tt	(revision 22118)
@@ -0,0 +1,8 @@
+AT_SETUP([[% namespace %]Insert()])
+AT_KEYWORDS([[% namespace %]Insert])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insert])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_c.tt	(revision 22118)
@@ -0,0 +1,28 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (![% namespace %]Insert(dbh, 
+[%- SET i = 0 -%]
+[%- FOREACH item = columns -%]
+[%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+)) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insert_h.tt	(revision 22118)
@@ -0,0 +1,21 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]Insert(
+    psDB            *dbh,               ///< Database handle
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %],[% END %]
+[% IF item.type == "STR" -%]
+    [% indented("const char", "*$item.name") -%]
+[% ELSE -%]
+    [% indented("$item.ctype", "$item.name") -%]
+[% END -%]
+[% END %]
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits.tt	(revision 22118)
@@ -0,0 +1,34 @@
+bool [% namespace %]InsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  [% namespace FILTER upper %]_TABLE_NAME
+    if (!psFitsMoveExtName(fits, [% namespace FILTER upper %]_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", [% namespace FILTER upper %]_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, [% namespace FILTER upper %]_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_at.tt	(revision 22118)
@@ -0,0 +1,15 @@
+AT_SETUP([[% namespace %]InsertFits()])
+AT_KEYWORDS([[% namespace %]InsertFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+# run popfis so there is afits file to read
+AT_CHECK([popfits])
+AT_CHECK([insertfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_c.tt	(revision 22118)
@@ -0,0 +1,29 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+    psFits          *fits;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // open a temp
+    fits = psFitsAlloc("./blargh");
+    if (!fits) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (![% namespace %]InsertFits(dbh, fits)) {
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(fits);
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertfits_h.tt	(revision 22118)
@@ -0,0 +1,14 @@
+/** Insert data from a binary FITS table [% object_name %] into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]InsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject.tt	(revision 22118)
@@ -0,0 +1,10 @@
+bool [% namespace %]InsertObject(psDB *dbh, [% object_name %] *object)
+{
+    return [% namespace %]Insert(dbh,
+[%- SET i = 0 -%]
+[%- FOREACH item = columns -%]
+[%- IF i == 0 -%][%- i = 1 -%] [% ELSE %], [% END -%]
+object->[% item.name -%]
+[%- END -%]
+);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_at.tt	(revision 22118)
@@ -0,0 +1,8 @@
+AT_SETUP([[% namespace %]InsertObject()])
+AT_KEYWORDS([[% namespace %]InsertObject])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insertobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_c.tt	(revision 22118)
@@ -0,0 +1,35 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+    [% indented(object_name, "*object") %];
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    object = [% object_name %]Alloc(
+[%- SET i = 0 -%]
+[%- FOREACH item = columns -%]
+    [%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+);
+    if (!object) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (![% namespace %]InsertObject(dbh, object)) {
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(object);
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/insertobject_h.tt	(revision 22118)
@@ -0,0 +1,13 @@
+[% USE format -%]
+[% indented = format('%-15s %-20s') -%]
+/** Insert a single [% object_name %] object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]InsertObject(
+    psDB            *dbh,               ///< Database handle
+    [% indented(object_name, "*object") %]///< [% object_name %] object
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/makefile.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/makefile.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/makefile.tt	(revision 22118)
@@ -0,0 +1,19 @@
+SHELL   = /bin/sh
+CFLAGS  = -g -O0 -pipe -fpic -Wall -pedantic -std=c99
+LIBS    = -lpslib
+CFLAGS += `mysql_config --cflags`
+LIBS   += `mysql_config --libs`
+
+
+objects=[% namespace %]db.o
+
+all: lib[% namespace %]db.so
+
+lib[% namespace %]db.so: $(objects)
+	$(CC) $(CFLAGS) -shared -o lib[% namespace %]db.so $(objects) $(LIBS)
+
+$(objects): %.o : %.c
+	$(CC) $(CFLAGS) -o $@ -c $<
+
+clean:
+	$(RM) *.so *.o core test
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject.tt	(revision 22118)
@@ -0,0 +1,21 @@
+psMetadata *[% namespace %]MetadataFromObject(const [% object_name %] *object)
+{
+    psMetadata      *md;
+
+    md = psMetadataAlloc();
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", NULL, object->[% item.name %])) {
+[% ELSIF item.type == "STR" -%]
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", NULL, psStringCopy(object->[% item.name %]))) {
+[% ELSIF item.type == "BOOL" -%]
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_META_BOOL, NULL, object->[% item.name %])) {
+[% END -%]
+        psError(PS_ERR_UNKNOWN, false, "failed to add item [% item.name %]");
+        psFree(md);
+        return NULL;
+    }
+[% END -%]
+
+    return md;
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_at.tt	(revision 22118)
@@ -0,0 +1,6 @@
+AT_SETUP([[% namespace %]MetadataFromObject()])
+AT_KEYWORDS([[% namespace %]MetadataFromObject])
+
+AT_CHECK([metadatafromobject])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_c.tt	(revision 22118)
@@ -0,0 +1,49 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    psMetadata      *md;
+    [% indented(object_name, "*object") %];
+    bool            status;
+
+    object = [% object_name %]Alloc(
+[%- SET i = 0 -%]
+[%- FOREACH item = columns -%]
+    [%- IF i == 0 -%][%- i = 1 -%][%- ELSE -%], [% END -%] [%- item.test -%]
+[%- END -%]
+);
+    if (!object) {
+        exit(EXIT_FAILURE);
+    }
+
+    md = [% namespace %]MetadataFromObject(object);
+    if (!md) {
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(object);
+
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataLookup[% item.type %](&status, md, "[% item.name %]") == [% item.test %]) {
+[% ELSIF item.type == "STR" -%]
+    if (strncmp(psMetadataLookupPtr(&status, md, "[% item.name %]"), [% item.test %], MAX_STRING_LENGTH)) {
+[% ELSIF item.type == "BOOL" -%]
+    if (!psMetadataLookupBool(&status, md, "[% item.name %]") == [% item.test %]) {
+[% END -%]
+        psFree(md);
+        exit(EXIT_FAILURE);
+    }
+[% END -%]
+
+    psFree(md);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/metadatafromobject_h.tt	(revision 22118)
@@ -0,0 +1,10 @@
+[% USE format -%]
+[% indented = format('%-15s %-20s') -%]
+/** Convert a [% object_name %] into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *[% namespace %]MetadataFromObject(
+    [% indented("const $object_name", "*object") %]///< fooRow to convert into a psMetadata
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/object_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/object_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/object_h.tt	(revision 22118)
@@ -0,0 +1,17 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** [% object_name %] data structure
+ *
+ * Structure for representing a single row of [% table_name %] table data.
+ */
+
+typedef struct {
+[% SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF item.type == "STR" -%]
+    [% indented("char", "*$item.name") %];
+[% ELSE -%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+} [% object_name %];
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata.tt	(revision 22118)
@@ -0,0 +1,34 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+[% object_name %] *[% namespace %]ObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+[% FOREACH item = columns -%]
+[% IF item.type == "STR" -%]
+    [% indented("char", "*$item.name") %];
+[% ELSE-%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    [% item.name %] = psMetadataLookup[% item.type %](&status, md, "[% item.name %]");
+[% ELSIF item.type == "STR" -%]
+    [% item.name %] = psMetadataLookupPtr(&status, md, "[% item.name %]");
+[% ELSIF item.type == "BOOL" -%]
+    [% item.name %] = psMetadataLookupBool(&status, md, "[% item.name %]");
+[% END -%]
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item [% item.name %]");
+        return false;
+    }
+[% END -%]
+
+    return [% object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 -%][%- i = 1 -%][% ELSE %], [% END -%]
+[% item.name %]
+[%- END -%]);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_at.tt	(revision 22118)
@@ -0,0 +1,6 @@
+AT_SETUP([[% namespace %]ObjectFromMetadata()])
+AT_KEYWORDS([[% namespace %]ObjectFromMetadata])
+
+AT_CHECK([objectfrommetadata])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_c.tt	(revision 22118)
@@ -0,0 +1,51 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    psMetadata      *md;
+    [% indented(object_name, "*object") %];
+
+    md = psMetadataAlloc();
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    if (!psMetadataAdd[% item.type %](md, PS_LIST_TAIL, "[% item.name %]", NULL, [% item.test %])) {
+[% ELSIF item.type == "STR" -%]
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "[% item.name %]", NULL, [% item.test %])) {
+[% ELSIF item.type == "BOOL" -%]
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "[% item.name %]", PS_META_BOOL, NULL, [% item.test %])) {
+[% END -%]
+        psFree(md);
+        exit(EXIT_FAILURE);
+    }
+[% END -%]
+
+    object = [% namespace %]ObjectFromMetadata(md);
+    if (!object) {
+        psFree(md);
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(md);
+
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" or item.type == "BOOL" -%]
+    if (!object->[% item.name %] == [% item.test %]) {
+[% ELSIF item.type == "STR" -%]
+    if (strncmp(object->[% item.name %], [% item.test %], MAX_STRING_LENGTH)) {
+[% END -%]
+        psFree(object);
+        exit(EXIT_FAILURE);
+    }
+[% END -%]
+
+    psFree(object);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/objectfrommetadata_h.tt	(revision 22118)
@@ -0,0 +1,8 @@
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A [% object_name %] pointer or NULL on error
+ */
+
+[% object_name %] *[% namespace %]ObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pkgconfig_pc_in.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pkgconfig_pc_in.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pkgconfig_pc_in.tt	(revision 22118)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: [% pkg_name %]
+Description: some library
+Version: @VERSION@
+Requires: pslib
+Libs: -L${libdir} -l[% pkg_name %]
+Cflags: -I${includedir}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop.tt	(revision 22118)
@@ -0,0 +1,69 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+bool [% namespace %]Pop(psDB *dbh, 
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+char **[% item.name -%]
+[% ELSE -%]
+[% item.ctype %] *[% item.name -%]
+[% END -%]
+[% END %])
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, [% namespace FILTER upper %]_TABLE_NAME, NULL, 1);
+    row = psArrayGet(rowSet, 0);
+    // FIXME: psArrayGet should increment the ref count
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, [% namespace FILTER upper %]_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", [% namespace FILTER upper %]_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, [% namespace FILTER upper %]_INDEX_NAME, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, [% namespace FILTER upper %]_TABLE_NAME, popped);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+[% FOREACH item = columns -%]
+[% IF item.type == "S32" or item.type == "F32" or item.type == "F64" -%]
+    *[% item.name %] = psMetadataLookup[% item.type %](&status, row, "[% item.name %]");
+[% ELSIF item.type == "STR" -%]
+    *[% item.name %] = psMetadataLookupPtr(&status, row, "[% item.name %]");
+[% ELSIF item.type == "BOOL" -%]
+    *[% item.name %] = psMetadataLookupBool(&status, row, "[% item.name %]");
+[% END -%]
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item [% item.name %]");
+        psFree(row);
+        return false;
+    }
+[% END -%]
+
+    psFree(row);
+
+    return true;
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_at.tt	(revision 22118)
@@ -0,0 +1,10 @@
+AT_SETUP([[% namespace %]Pop()])
+AT_KEYWORDS([[% namespace %]Pop])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([pop])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_c.tt	(revision 22118)
@@ -0,0 +1,39 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+[% FOREACH item = columns -%]
+[% IF item.type == "STR" -%]
+    [% indented("char", "$item.name[256]") %];
+[% ELSE-%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (![% namespace %]Pop(dbh, 
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+(char **)&[% item.name -%]
+[% ELSE-%]
+&[% item.name -%]
+[% END -%]
+[% END %])) { 
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/pop_h.tt	(revision 22118)
@@ -0,0 +1,19 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+/** Removes the last row from the database and returns it
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]Pop(
+    psDB            *dbh,               ///< Database handle
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %],[% END %]
+[% IF item.type == "STR" -%]
+    [% indented("char", "**$item.name") -%]
+[% ELSE -%]
+    [% indented("$item.ctype", "*$item.name") -%]
+[% END -%]
+[% END %]
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits.tt	(revision 22118)
@@ -0,0 +1,24 @@
+bool [% namespace %]PopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!fooSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                [% namespace FILTER upper %]_TABLE_NAME, [% namespace FILTER upper %]_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_at.tt	(revision 22118)
@@ -0,0 +1,13 @@
+AT_SETUP([[% namespace %]PopFits()])
+AT_KEYWORDS([[% namespace %]PopFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_c.tt	(revision 22118)
@@ -0,0 +1,29 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+    psFits          *fits;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // allocate a temp
+    fits = psFitsAlloc("./blargh");
+    if (!fits) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (![% namespace %]PopFits(dbh, fits, 1)) {
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(fits);
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popfits_h.tt	(revision 22118)
@@ -0,0 +1,13 @@
+/** Removes the last limit row from the database and returns them in a binary FITS table.
+ *
+ * This function assumes an empty psFits object and will create a FITS table as
+ * the first extension. 
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]PopFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    unsigned long long limit            ///< Maximum number of elements to return
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject.tt	(revision 22118)
@@ -0,0 +1,33 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+[% object_name %] *[% namespace %]PopObject(psDB *dbh)
+{
+[% FOREACH item = columns -%]
+[% IF item.type == "STR" -%]
+    [% indented("char", "$item.name[256]") %];
+[% ELSE-%]
+    [% indented("$item.ctype", "$item.name") %];
+[% END -%]
+[% END -%]
+
+    if (![% namespace %]Pop(dbh, 
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 %][% i = 1 %][% ELSE %], [% END -%]
+[% IF item.type == "STR" -%]
+(char **)&[% item.name -%]
+[% ELSE-%]
+&[% item.name -%]
+[% END -%]
+[% END %])) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return [% object_name %]Alloc(
+[%- SET i = 0 -%]
+[% FOREACH item = columns -%]
+[% IF i == 0 -%][%- i = 1 -%][% ELSE %], [% END -%]
+[% item.name %]
+[%- END -%]);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_at.tt	(revision 22118)
@@ -0,0 +1,10 @@
+AT_SETUP([[% namespace %]PopObject()])
+AT_KEYWORDS([[% namespace %]PopObject])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([popobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_c.tt	(revision 22118)
@@ -0,0 +1,26 @@
+[% USE format -%]
+[% indented = format('%-15s %s') -%]
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+    [% indented(object_name, "*object") %];
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    object = [% namespace %]PopObject(dbh);
+    if (!object) {
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(object);
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/popobject_h.tt	(revision 22118)
@@ -0,0 +1,8 @@
+/** Removes the last row from the database and returns it
+ *
+ * @return A new [% object_name %] on success or NULL on failure.
+ */
+
+[% object_name %] *[% namespace %]PopObject(
+    psDB            *dbh                ///< Database handle
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits.tt	(revision 22118)
@@ -0,0 +1,37 @@
+bool [% namespace %]SelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, [% namespace FILTER upper %]_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, [% namespace FILTER upper %]_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", [% namespace FILTER upper %]_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    // FIXME: needs to be after a new ext is written?
+    if (!psFitsSetExtName(fits, [% namespace FILTER upper %]_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set FITS HDU extension name");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_at.tt	(revision 22118)
@@ -0,0 +1,13 @@
+AT_SETUP([[% namespace %]SelectRowsFits()])
+AT_KEYWORDS([[% namespace %]SelectRowsFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([selectrowsfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_c.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_c.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_c.tt	(revision 22118)
@@ -0,0 +1,29 @@
+#include <pslib.h>
+#include <[% pkg_name %].h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+    psFits          *fits;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // FIXME: open a temp file and pass it as a FD
+    fits = psFitsAlloc("./blargh");
+    if (!fits) {
+        exit(EXIT_FAILURE);
+    }
+
+    if (![% namespace %]SelectRowsFits(dbh, fits, NULL, 1)) {
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(fits);
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_h.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_h.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/selectrowsfits_h.tt	(revision 22118)
@@ -0,0 +1,16 @@
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool [% namespace %]SelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/src_makefile_am.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/src_makefile_am.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/src_makefile_am.tt	(revision 22118)
@@ -0,0 +1,10 @@
+AM_CFLAGS = @[% pkg_name %]_CFLAGS@
+
+include_HEADERS = [% pkg_name %].h
+lib_LTLIBRARIES = lib[% pkg_name %].la
+lib[% pkg_name %]_la_CFLAGS  = $(PSLIB_CFLAGS) $(AM_CFLAGS)
+lib[% pkg_name %]_la_LIBS    = $(PSLIB_LIBS) $(AM_LIBS)
+lib[% pkg_name %]_la_LDFLAGS = -release $(PACKAGE_VERSION)
+lib[% pkg_name %]_la_SOURCES = \
+    [% pkg_name %].h \
+    [% pkg_name %].c
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/tests_makefile_am.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/tests_makefile_am.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/tests_makefile_am.tt	(revision 22118)
@@ -0,0 +1,67 @@
+# copied from bison-1.875d
+
+EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4
+
+DISTCLEANFILES       = atconfig $(check_SCRIPTS)
+MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE)
+
+## ------------ ##
+## package.m4.  ##
+## ------------ ##
+
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	{					\
+	  echo '# Signature of the current package.'; \
+	  echo 'm4_define([AT_PACKAGE_NAME],      [@PACKAGE_NAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_TARNAME],   [@PACKAGE_TARNAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_VERSION],   [@PACKAGE_VERSION@])'; \
+	  echo 'm4_define([AT_PACKAGE_STRING],    [@PACKAGE_STRING@])'; \
+	  echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+	} >$(srcdir)/package.m4
+
+## ------------ ##
+## Test suite.  ##
+## ------------ ##
+
+TESTSUITE = $(srcdir)/testsuite
+
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT)
+	$(AUTOTEST) -I $(srcdir) testsuite.at -o $@.tmp
+	mv $@.tmp $@
+
+atconfig: $(top_builddir)/config.status
+	cd $(top_builddir) && ./config.status tests/$@
+
+clean-local:
+	test ! -f $(TESTSUITE) || $(SHELL) $(TESTSUITE) --clean
+
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) $(TESTSUITE)
+
+#
+# END BISON
+#
+
+UNITS = \
+	alloc \
+	init \
+	cleanup \
+	createtable \
+    droptable \
+    insert \
+    pop \
+    insertobject \
+    popobject \
+    insertfits \
+    popfits \
+    selectrowsfits \
+    metadatafromobject \
+    objectfrommetadata
+
+AM_CPPFLAGS = -I$(top_srcdir)/src$
+AM_CFLAGS   = @[% pkg_name %]_CFLAGS@ $(PSLIB_CFLAGS)$
+AM_LDFLAGS  = $(PSLIB_LIBS)$
+LDADD       = $(top_builddir)/src/lib[% pkg_name %].la$
+
+check_PROGRAMS = dbsetup dbcleanup $(UNITS)
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/testsuite_at.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/testsuite_at.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/testsuite_at.tt	(revision 22118)
@@ -0,0 +1,33 @@
+m4_version_prereq([2.59])
+
+AT_INIT
+
+###
+[% INCLUDE alloc_at.tt %]
+###
+[% INCLUDE init_at.tt %]
+###
+[% INCLUDE cleanup_at.tt %]
+###
+[% INCLUDE createtable_at.tt %]
+###
+[% INCLUDE droptable_at.tt %]
+###
+[% INCLUDE insert_at.tt %]
+###
+[% INCLUDE pop_at.tt %]
+###
+[% INCLUDE insertobject_at.tt %]
+###
+[% INCLUDE popobject_at.tt %]
+###
+[% INCLUDE insertfits_at.tt %]
+###
+[% INCLUDE popfits_at.tt %]
+###
+[% INCLUDE selectrowsfits_at.tt %]
+###
+[% INCLUDE metadatafromobject_at.tt %]
+###
+[% INCLUDE objectfrommetadata_at.tt %]
+###
Index: /tags/ipp-1-X/v0_01/glueforge/templates/psdb/top_makefile_am.tt
===================================================================
--- /tags/ipp-1-X/v0_01/glueforge/templates/psdb/top_makefile_am.tt	(revision 22118)
+++ /tags/ipp-1-X/v0_01/glueforge/templates/psdb/top_makefile_am.tt	(revision 22118)
@@ -0,0 +1,16 @@
+SUBDIRS = src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= [% pkg_name %].pc
+
+EXTRA_DIST = [% pkg_name %].pc.in Doxyfile.in
+
+man_MANS = \
+    $(top_builddir)/docs/man/man3/[% pkg_name %].3 \
+    $(top_builddir)/docs/man/man3/[% object_name %].3
+
+docs/man/man3/[% pkg_name %].3 docs/man/man3/[% object_name %].3:
+	$(DOXYGEN)
+
+clean-local:
+	-rm -rf docs
