Index: ossp-pkg/cfg/ChangeLog RCS File: /v/ossp/cvs/ossp-pkg/cfg/ChangeLog,v rcsdiff -q -kk '-r1.28' '-r1.29' -u '/v/ossp/cvs/ossp-pkg/cfg/ChangeLog,v' 2>/dev/null --- ChangeLog 2004/12/04 12:48:40 1.28 +++ ChangeLog 2004/12/19 19:36:25 1.29 @@ -8,7 +8,14 @@ CHANGELOG - Changes between 0.9.5 and 0.9.6 (27-Nov-2004 to xx-Dec-2004): + Changes between 0.9.6 and 0.9.7 (04-Dec-2004 to 19-Dec-2004): + + *) Fixed cfg_node_get() function after recent introduction + of LOAN/GIFT/COPY argument passing semantics. This + inbreaks the Perl API's unpack() function. + [Ralf S. Engelschall ] + + Changes between 0.9.5 and 0.9.6 (27-Nov-2004 to 04-Dec-2004): *) Plug remaining memory leaks by introducing the usual LOAN/GIFT/COPY argument passing semantics to cfg_node_{set,get} Index: ossp-pkg/cfg/README RCS File: /v/ossp/cvs/ossp-pkg/cfg/README,v rcsdiff -q -kk '-r1.13' '-r1.14' -u '/v/ossp/cvs/ossp-pkg/cfg/README,v' 2>/dev/null --- README 2004/12/04 12:54:41 1.13 +++ README 2004/12/19 19:36:25 1.14 @@ -5,7 +5,7 @@ |_|_|_| \___/|____/____/|_| \___|_| \__, | |___/ OSSP cfg - Configuration Parsing - Version 0.9.6 (04-Dec-2004) + Version 0.9.7 (19-Dec-2004) ABSTRACT Index: ossp-pkg/cfg/cfg_node.c RCS File: /v/ossp/cvs/ossp-pkg/cfg/cfg_node.c,v rcsdiff -q -kk '-r1.23' '-r1.24' -u '/v/ossp/cvs/ossp-pkg/cfg/cfg_node.c,v' 2>/dev/null --- cfg_node.c 2004/12/04 12:48:40 1.23 +++ cfg_node.c 2004/12/19 19:36:25 1.24 @@ -253,7 +253,7 @@ /* dispatch into individual attribute handling */ va_start(ap, attr); - switch (attr) { + switch (attr & ~(CFG_ATTR_LOAN|CFG_ATTR_GIFT|CFG_ATTR_COPY)) { case CFG_NODE_ATTR_PARENT: { cfg_node_t **n = (cfg_node_t **)va_arg(ap, void *); if (n == NULL) Index: ossp-pkg/cfg/cfg_vers.c RCS File: /v/ossp/cvs/ossp-pkg/cfg/cfg_vers.c,v rcsdiff -q -kk '-r1.11' '-r1.12' -u '/v/ossp/cvs/ossp-pkg/cfg/cfg_vers.c,v' 2>/dev/null --- cfg_vers.c 2004/12/04 12:54:41 1.11 +++ cfg_vers.c 2004/12/19 19:36:25 1.12 @@ -8,7 +8,7 @@ #ifndef _CFG_VERS_C_ #define _CFG_VERS_C_ -#define __CFG_VERSION 0x009206 +#define __CFG_VERSION 0x009207 typedef struct { const int v_hex; @@ -32,13 +32,13 @@ #undef _CFG_VERS_C_AS_HEADER_ __cfg_version_t __cfg_version = { - 0x009206, - "0.9.6", - "0.9.6 (04-Dec-2004)", - "This is OSSP cfg, Version 0.9.6 (04-Dec-2004)", - "OSSP cfg 0.9.6 (04-Dec-2004)", - "OSSP cfg/0.9.6", - "@(#)OSSP cfg 0.9.6 (04-Dec-2004)", + 0x009207, + "0.9.7", + "0.9.7 (19-Dec-2004)", + "This is OSSP cfg, Version 0.9.7 (19-Dec-2004)", + "OSSP cfg 0.9.7 (19-Dec-2004)", + "OSSP cfg/0.9.7", + "@(#)OSSP cfg 0.9.7 (19-Dec-2004)", "$Id$" }; Index: ossp-pkg/cfg/perl/cfg.pm RCS File: /v/ossp/cvs/ossp-pkg/cfg/perl/cfg.pm,v co -q -kk -p'1.11' '/v/ossp/cvs/ossp-pkg/cfg/perl/cfg.pm,v' | diff -u /dev/null - -L'ossp-pkg/cfg/perl/cfg.pm' 2>/dev/null --- ossp-pkg/cfg/perl/cfg.pm +++ - 2024-05-11 14:16:29.857394808 +0200 @@ -0,0 +1,635 @@ +## +## OSSP cfg - Configuration Parsing +## Copyright (c) 2002-2004 Ralf S. Engelschall +## Copyright (c) 2002-2004 The OSSP Project +## Copyright (c) 2002-2004 Cable & Wireless +## +## This file is part of OSSP cfg, a configuration parsing library which +## can be found at http://www.ossp.org/pkg/lib/cfg/. +## +## Permission to use, copy, modify, and distribute this software for +## any purpose with or without fee is hereby granted, provided that +## the above copyright notice and this permission notice appear in all +## copies. +## +## THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +## WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +## IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR +## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +## USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +## OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +## OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +## SUCH DAMAGE. +## +## cfg.pm: Perl Binding (Perl part) +## + +package OSSP::cfg; + +use 5.008; +use strict; +use warnings; +use Carp; +use XSLoader; +use Exporter; + +## +## API Definition +## + +# API version +our $VERSION = do { my @v = ('0.9.7' =~ m/\d+/g); sprintf("%d.".("%02d"x$#v), @v); }; + +# API inheritance +our @ISA = qw(Exporter); + +# API symbols +my $symbols = { + 'const' => [qw( + CFG_OK + CFG_ERR_ARG + CFG_ERR_USE + CFG_ERR_MEM + CFG_ERR_SYS + CFG_ERR_FMT + CFG_ERR_INT + CFG_ERR_SYN + CFG_ERR_NDE + CFG_FMT_CFG + CFG_FMT_XML + CFG_NODE_TYPE_SEQ + CFG_NODE_TYPE_DIR + CFG_NODE_TYPE_OPT + CFG_NODE_TYPE_ARG + CFG_NODE_ATTR_PARENT + CFG_NODE_ATTR_LBROTH + CFG_NODE_ATTR_RBROTH + CFG_NODE_ATTR_CHILD1 + CFG_NODE_ATTR_CHILDL + CFG_NODE_ATTR_CHILDS + CFG_NODE_ATTR_NODES + CFG_NODE_ATTR_DEPTH + CFG_NODE_ATTR_SRCNAME + CFG_NODE_ATTR_SRCPOS + CFG_NODE_ATTR_TYPE + CFG_NODE_ATTR_TOKEN + CFG_NODE_ATTR_DATA + CFG_DATA_TYPE_PTR + CFG_DATA_TYPE_STR + CFG_DATA_TYPE_INT + CFG_DATA_TYPE_FLT + CFG_DATA_CTRL_CLONE + CFG_DATA_CTRL_DESTROY + CFG_DATA_ATTR_TYPE + CFG_DATA_ATTR_VALUE + CFG_DATA_ATTR_CTRL + )], + 'func' => [qw( + cfg_create + cfg_destroy + cfg_error + cfg_version + cfg_import + cfg_export + cfg_node_create + cfg_node_destroy + cfg_node_clone + cfg_node_set + cfg_node_get + cfg_node_root + cfg_node_select + cfg_node_find + cfg_node_apply + cfg_node_cmp + cfg_node_link + cfg_node_unlink + cfg_data_set + cfg_data_get + cfg_data_ctrl + )] +}; + +# API symbol exportation +our %EXPORT_TAGS = ( + 'all' => [ @{$symbols->{'const'}}, @{$symbols->{'func'}} ], + 'const' => [ @{$symbols->{'const'}} ], + 'func' => [ @{$symbols->{'func'}} ] +); +our @EXPORT_OK = @{$EXPORT_TAGS{'all'}}; +our @EXPORT = (); + +## +## High-Level Perl Module OO-style API +## (just an OO wrapper around the C-style API) +## + +sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = {}; + bless ($self, $class); + $self->{-cfg} = undef; + $self->{-rc} = $self->CFG_OK; + my $rc = cfg_create($self->{-cfg}); + if ($rc != $self->CFG_OK) { + croak(sprintf("OSSP::cfg::new: cfg_create: %s (%d)", $self->error($rc), $rc)); + } + return $self; +} + +sub DESTROY ($) { + my ($self) = @_; + $self->{-rc} = cfg_destroy($self->{-cfg}) if (defined($self->{-cfg})); + if ($self->{-rc} != $self->CFG_OK) { + carp(sprintf("OSSP::cfg::DESTROY: cfg_destroy: %s (%d)", $self->error($self->{-rc}), $self->{-rc})); + return; + } + $self->{-cfg} = undef; + $self->{-rc} = undef; + return; +} + +sub import { # ($$$$) + # ATTENTION: The OSSP cfg API function "import" conflicts with + # the standardized "import" method the Perl world expects from + # their modules. In order to keep the Perl binding consist + # with the C API, we solve the conflict under run-time by + # distinguishing between the two types of "import" calls. + if (defined($_[0]) and ref($_[0]) =~ m/^OSSP::cfg/) { + # the regular OSSP::cfg "import" method + my ($self, $node, $fmt, $in) = @_; + $self->{-rc} = cfg_import($self->{-cfg}, $node, $fmt, $in, length($in)); + return ($self->{-rc} == $self->CFG_OK); + } + else { + # the special Perl "import" method + # (usually inherited from the Exporter) + no strict "refs"; + return OSSP::cfg->export_to_level(1, @_); + } +} + +sub export { # ($$$) + # ATTENTION: The OSSP cfg API function "export" conflicts with + # the standardized "export" method the Perl world expects from + # their modules. In order to keep the Perl binding consist + # with the C API, we solve the conflict under run-time by + # distinguishing between the two types of "export" calls. + if (defined($_[0]) and ref($_[0]) =~ m/^OSSP::cfg/) { + # the regular OSSP::cfg "export" method + my ($self, $node, $fmt) = @_; + my $out; + $self->{-rc} = cfg_export($self->{-cfg}, $node, $fmt, $out, 0); + return ($self->{-rc} == $self->CFG_OK ? $out : undef); + } + else { + # the special Perl "export" method + # (usually inherited from the Exporter) + return Exporter::export(@_); + } +} + +sub error ($;$) { + my ($self, $rc) = @_; + $rc = $self->{-rc} if (not defined($rc)); + my $error; + if (cfg_error($self->{-cfg}, $rc, $error) != $self->CFG_OK) { + croak("OSSP::cfg::error: cfg_error: INTERNAL ERROR"); + } + return wantarray ? ($error, $rc) : $error; +} + +sub version () { + my ($self) = @_; + return cfg_version(); +} + +sub node_create ($) { + my ($self) = @_; + my $node; + $self->{-rc} = cfg_node_create($self->{-cfg}, $node); + return ($self->{-rc} == $self->CFG_OK ? $node : undef); +} + +sub node_destroy ($$) { + my ($self, $node) = @_; + $self->{-rc} = cfg_node_destroy($self->{-cfg}, $node); + return ($self->{-rc} == $self->CFG_OK); +} + +sub node_clone ($$) { + my ($self, $node) = @_; + my $node2; + $self->{-rc} = cfg_node_clone($self->{-cfg}, $node, $node2); + return ($self->{-rc} == $self->CFG_OK ? $node2 : undef); +} + +sub node_set ($$$$) { + my ($self, $node, $attr, $arg) = @_; + $self->{-rc} = cfg_node_set($self->{-cfg}, $node, $attr, $arg); + return ($self->{-rc} == $self->CFG_OK); +} + +sub node_get ($$$) { + my ($self, $node, $attr) = @_; + my $arg; + $self->{-rc} = cfg_node_get($self->{-cfg}, $node, $attr, $arg); + return ($self->{-rc} == $self->CFG_OK ? $arg : undef); +} + +sub node_root ($;$) { + my ($self, $node_new) = @_; + $node_new ||= \0; + my $node_old; + $self->{-rc} = cfg_node_root($self->{-cfg}, $node_new, $node_old); + return ($self->{-rc} == $self->CFG_OK ? $node_old : undef); +} + +sub node_select ($$$) { + my ($self, $node, $spec) = @_; + my $result; + $self->{-rc} = cfg_node_select($self->{-cfg}, $node, $result, $spec); + return ($self->{-rc} == $self->CFG_OK ? $result : undef); +} + +sub node_find ($$$$) { + my ($self, $node, $cb_fct_cmp, $cb_ctx_cmp) = @_; + my $cont; + $self->{-rc} = cfg_node_find($self->{-cfg}, $node, $cb_fct_cmp, $cb_ctx_cmp, $cont); + return ($self->{-rc} == $self->CFG_OK ? $cont : undef); +} + +sub node_apply ($$$$) { + my ($self, $node, $cb_fct_cmp, $cb_ctx_cmp, $cb_fct_cb, $cb_ctx_cb) = @_; + $self->{-rc} = cfg_node_apply($self->{-cfg}, $node, $cb_fct_cmp, $cb_ctx_cmp, $cb_fct_cb, $cb_ctx_cb); + return ($self->{-rc} == $self->CFG_OK); +} + +sub node_cmp ($$$) { + my ($self, $node, $token) = @_; + $self->{-rc} = cfg_node_cmp($self->{-cfg}, $node, $token); + return ($self->{-rc} == $self->CFG_OK); +} + +sub node_link ($$$$) { + my ($self, $node, $id, $node2) = @_; + $self->{-rc} = cfg_node_link($self->{-cfg}, $node, $id, $node2); + return ($self->{-rc} == $self->CFG_OK); +} + +sub node_unlink ($$) { + my ($self, $node) = @_; + $self->{-rc} = cfg_node_unlink($self->{-cfg}, $node); + return ($self->{-rc} == $self->CFG_OK); +} + +sub data_set ($$$$) { + my ($self, $data, $attr, $value) = @_; + $self->{-rc} = cfg_data_set($data, $attr, $value); + return ($self->{-rc} == $self->CFG_OK); +} + +sub data_get ($$$) { + my ($self, $data, $attr) = @_; + my $value; + $self->{-rc} = cfg_data_get($data, $attr, $value); + return ($self->{-rc} == $self->CFG_OK ? $value : undef); +} + +sub data_ctrl ($$$;$) { + my ($self, $data, $ctrl, $value) = @_; + $self->{-rc} = cfg_data_ctrl($data, $ctrl, $value); + return ($self->{-rc} == $self->CFG_OK); +} + +## +## Low-Level Perl XS C-style API +## (actually just the activation of the XS part) +## + +# auto-loading constants +sub AUTOLOAD { + my $constname; + our $AUTOLOAD; + ($constname = $AUTOLOAD) =~ s/.*:://; + croak "&OSSP::cfg::constant not defined" if ($constname eq 'constant'); + my ($error, $val) = constant($constname); + croak $error if ($error); + { no strict 'refs'; *$AUTOLOAD = sub { $val }; } + goto &$AUTOLOAD; +} + +# static-loading functions +XSLoader::load('OSSP::cfg', $VERSION); + +## +## Highest-Level Perl Convenience API +## (based on OSSP::cfg Perl API above) +## + +package OSSP::cfg::simple; + +use 5.008; +use strict; +use warnings; +use Carp; +use IO::File; + +# object constructor +sub new ($;$) { + my ($proto, $input) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + bless ($self, $class); + $self->{-cfg} = new OSSP::cfg + or croak "failed to create OSSP::cfg object"; + $self->parse($input) if (defined($input)); + return $self; +} + +# object destructor +sub DESTROY ($) { + my ($self) = @_; + undef $self->{-cfg}; + return; +} + +# parse textual syntax into node tree +sub parse ($$) { + my ($self, $input) = @_; + my $cfg = $self->{-cfg}; + if (length($input) < 1024 and $input !~ m|[{}&\n\s]|s) { + if (-f $input) { + my $io = new IO::File "<" . $input + or croak "unable to read from configuration file \"".$input."\""; + { local $/; $input = <$io>; } + $io->close(); + } + } + $cfg->import(\0, $cfg->CFG_FMT_CFG, $input) + or die "failed to parse configuration: " . $cfg->error(); + return $self; +} + +# format textual syntax from node tree +sub format ($$) { + my ($self) = @_; + my $cfg = $self->{-cfg}; + my $root = $cfg->node_root(); + my $str = $cfg->export($root, $cfg->CFG_FMT_CFG) + or die "failed to format configuration: " . $cfg->error(); + return $str; +} + +# pack Perl complex data structure into node tree +sub pack ($$) { + my ($self, $tree) = @_; + + # destroy old and create new OSSP::cfg object + undef $self->{-cfg}; + my $cfg = new OSSP::cfg + or croak "failed to create OSSP::cfg object"; + $self->{-cfg} = $cfg; + + # start parsing with a sequence + my $node = &parse_seq($cfg, $tree); + + # parse a sequence (reference) scalar + sub parse_seq { + my ($cfg, $seq) = @_; + my $node = undef; + if (ref($seq) eq 'ARRAY') { + $node = $cfg->node_create(); + $cfg->node_set($node, $cfg->CFG_NODE_ATTR_TYPE, $cfg->CFG_NODE_TYPE_SEQ); + if (@{$seq} > 0) { + my $child = &parse_dir($cfg, $seq->[0]); + $cfg->node_set($node, $cfg->CFG_NODE_ATTR_CHILD1, $child); + my $sibling = $child; + foreach my $dir (@{$seq}[1..$#{$seq}]) { + $child = &parse_dir($cfg, $dir); + $cfg->node_set($sibling, $cfg->CFG_NODE_ATTR_RBROTH, $child); + $sibling = $child; + } + } + } + return $node; + } + + # parse a directive (reference) scalar + sub parse_dir { + my ($cfg, $dir) = @_; + my $node = undef; + if (ref($dir) eq 'ARRAY') { + $node = $cfg->node_create(); + $cfg->node_set($node, $cfg->CFG_NODE_ATTR_TYPE, $cfg->CFG_NODE_TYPE_DIR); + if (@{$dir} > 0) { + my $child = &parse_tok($cfg, $dir->[0]); + $cfg->node_set($node, $cfg->CFG_NODE_ATTR_CHILD1, $child); + my $sibling = $child; + foreach my $tok (@{$dir}[1..$#{$dir}]) { + $child = &parse_tok($cfg, $tok); + $cfg->node_set($sibling, $cfg->CFG_NODE_ATTR_RBROTH, $child); + $sibling = $child; + } + } + } + return $node; + } + + # parse a token scalar + sub parse_tok { + my ($cfg, $tok) = @_; + my $node; + if (ref($tok) eq 'ARRAY') { + # token is actually a sub-sequence + $node = &parse_seq($cfg, $tok); # recursion + } + else { + # token is a plain one + $node = $cfg->node_create(); + $cfg->node_set($node, $cfg->CFG_NODE_ATTR_TYPE, $cfg->CFG_NODE_TYPE_ARG); + $cfg->node_set($node, $cfg->CFG_NODE_ATTR_TOKEN, $tok); + } + return $node; + } + + # set generated node tree into OSSP::cfg object + $cfg->node_root($node); + + return $self; +} + +# unpack Perl complex data structure from node tree +sub unpack ($;%) { + my ($self, %arg) = @_; + my $cfg = $self->{-cfg}; + + # start traversing the node tree from the root node + my $root = $cfg->node_root(); + my $tree = &traverse($cfg, \%arg, $root); + + # node processing function + sub traverse { + my ($cfg, $arg, $node) = @_; + return if (not(ref($node) and $$node != 0)); + my $type = $cfg->node_get($node, $cfg->CFG_NODE_ATTR_TYPE); + if ($type == $cfg->CFG_NODE_TYPE_SEQ) { + # node is a sequence + my $seq = []; + $node = $cfg->node_get($node, $cfg->CFG_NODE_ATTR_CHILD1); + while (ref($node) and $$node != 0) { + my $tree = &traverse($cfg, $arg, $node); # recursion + push(@{$seq}, $tree) if (defined($tree)); + $node = $cfg->node_get($node, $cfg->CFG_NODE_ATTR_RBROTH); + } + # optional post-processing of data structure + $seq = &unpack_postproc_merge ($seq, $arg->{-merge}) if (defined($arg->{-merge})); + $seq = &unpack_postproc_index ($seq, $arg->{-index}) if (defined($arg->{-index})); + $seq = &unpack_postproc_strip ($seq, $arg->{-strip}) if (defined($arg->{-strip})); + $seq = &unpack_postproc_flatten ($seq, $arg->{-flatten}) if (defined($arg->{-flatten})); + return $seq; + } + elsif ($type == $cfg->CFG_NODE_TYPE_DIR) { + # node is a directive + my $dir = []; + $node = $cfg->node_get($node, $cfg->CFG_NODE_ATTR_CHILD1); + while (ref($node) and $$node != 0) { + my $tree = &traverse($cfg, $arg, $node); # recursion + push(@{$dir}, $tree) if (defined($tree)); + $node = $cfg->node_get($node, $cfg->CFG_NODE_ATTR_RBROTH); + } + return $dir; + } + else { + # node is a token + my $token = $cfg->node_get($node, $cfg->CFG_NODE_ATTR_TOKEN); + return $token; + } + } + return $tree; +} + +# unpack helper function: +# optionally merge arguments of DIRectives starting with same TOKen. +# complexity: O(n) (actually 3*n) +sub unpack_postproc_merge { + my ($seq, $re) = @_; + + # pass 1: determine directive names and their + # occurences in sequence. complexity: O(n) + my $dir = {}; + for (my $i = 0; $i < @{$seq}; $i++) { + if ( ref($seq->[$i]) eq 'ARRAY' + and not ref($seq->[$i]->[0]) + and $seq->[$i]->[0] =~ m/^$re$/s) { + my $name = $seq->[$i]->[0]; + $dir->{$name} = [] if (not defined($dir->{$name})); + push(@{$dir->{$name}}, $i); + } + } + + # pass 2: append arguments of second and following sequences + # to arguments of first directive. complexity: O(n) + foreach my $name (keys(%{$dir})) { + if (@{$dir->{$name}} > 1) { + my $f = $dir->{$name}->[0]; + foreach my $n (@{$dir->{$name}}[1..$#{$dir->{$name}}]) { + foreach my $tok (@{$seq->[$n]}[1..$#{$seq->[$n]}]) { + push(@{$seq->[$f]}, $tok); + } + $seq->[$n] = undef; + } + } + } + + # pass 3: shrink sequence by eliminating removing entries + # complexity: O(n) + for (my $i = 0; $i < @{$seq}; $i++) { + if (not defined($seq->[$i])) { + splice(@{$seq}, $i, 1); + $i--; # compensate for splice operation + } + } + + return $seq; +} + +# unpack helper function: +# optionally annotate SEQuence nodes with a DIRective name indexing hash +# complexity: O(n) +sub unpack_postproc_index { + my ($seq, $re) = @_; + + # create an index hash + my $idx = {}; + for (my $i = 0; $i < @{$seq}; $i++) { + if (ref($seq->[$i]) eq 'ARRAY') { + # determine name of directive, i.e., + # first non-sub-sequence element + my $name = {}; + for (my $j = 0; $j < @{$seq->[$j]}; $j++) { + $name = $seq->[$i]->[$j]; + last if (not ref($name)); + } + if ( not ref($name) + and $name =~ m/^$re$/s + and not defined($idx->{$name})) { + # remember position of directive's + # first occurrence in sequence + $idx->{$name} = ($i + 1); + } + } + } + + # insert index at first position of sequence array following + # perlref(1)'s "Pseudo-hashes: Using an array as a hash" approach. + unshift(@{$seq}, $idx); + + return $seq; +} + +# unpack helper function: +# strip directive command/start token +# complexity: O(n) +sub unpack_postproc_strip { + my ($seq, $re) = @_; + + foreach my $dir (@{$seq}) { + if ( ref($dir) eq 'ARRAY' + and $dir->[0] =~ m/^$re$/s) { + # remove first token of directive array + shift(@{$dir}); + } + } + + return $seq; +} + +# unpack helper function: +# flatten empty and single token directives +sub unpack_postproc_flatten { + my ($seq, $empty) = @_; + + for (my $i = 0; $i < @{$seq}; $i++) { + if (ref($seq->[$i]) eq 'ARRAY') { + if (@{$seq->[$i]} == 0) { + # replace empty directive array + # (can exist after "dirstrip" processing!) + $seq->[$i] = $empty; + } + elsif (@{$seq->[$i]} == 1) { + # simplify single token directive array + $seq->[$i] = $seq->[$i]->[0]; + } + } + } + + return $seq; +} + +1; +