/usr/local/CPAN/hub-standard/Hub/Data/Address.pm


package Hub::Data::Address;
use Hub qw/:lib/;
our $VERSION = '4.00043';
our @EXPORT = qw//;
our @EXPORT_OK = qw/
  keydepth
  vartype
  varroot
  varname
  varparent
  dotaddr
  expand
  collapse
/;
our $DELIMS         = ':/';

# ------------------------------------------------------------------------------
# keydepth
# 
# For sorting parents and children, this simply lets you know how deep the key
# is named.
# ------------------------------------------------------------------------------
#|test(match,4) keydepth( 'and:then:came:the:rain' )
# ------------------------------------------------------------------------------

sub keydepth {
  defined $_[0] ? $_[0] =~ tr':/'' : 0;
}#keydepth

# ------------------------------------------------------------------------------
# vartype VARADDR, [DEFAULT]
#
# Return a variables type (or a default value).
# ------------------------------------------------------------------------------
#|test(match)           vartype( );
#|test(match,clr)       vartype( "clr-bg" );
#|test(match,clr)       vartype( "clr-bg", "default" );
#|test(match,default)   vartype( "whatev", "default" );
#|test(match)           vartype( "whatev" );
#|test(match)           vartype( "a:b:c" );
#|test(match,x)         vartype( "x-a:b:c" );
#|test(match,x)         vartype( "a:b:x-c" );
# ------------------------------------------------------------------------------

sub vartype {
  my $str = defined $_[0] ? $_[0] : '';
  my $def = defined $_[1] ? $_[1] : '';
  my ($type) = $str =~ /[_]?([^-]+)-/;
  $type = '' unless defined $type;
  $type =~ s/.*://;
  return $type || $def;
}#vartype

#-------------------------------------------------------------------------------
# varroot VARADDR
#
# The root portion of the address.
#-------------------------------------------------------------------------------
#|test(match,p001)   varroot( "p001:items:1002:text-description" );
#|test(match,p001)   varroot( "p001" );
#-------------------------------------------------------------------------------

sub varroot {
  my $given = defined $_[0] ? $_[0] : '';
  my ($root) = ( $given =~ /([^$DELIMS]+)/ );
  return $root || '';
}#varroot

#-------------------------------------------------------------------------------
# varname VARADDR
#
#-------------------------------------------------------------------------------
#|test(match,text-desc)     varname( "p001:items:1002:text-desc" );
#|test(match,p001)          varname( "p001" );
#-------------------------------------------------------------------------------

sub varname {
  my $given = defined $_[0] ? $_[0] : '';
  my ($name,$end) = ( $given =~ /.*[$DELIMS]([^$DELIMS]+)([$DELIMS])?$/ );
  return defined $end ? '' : $name || $given;
}#varname

#-------------------------------------------------------------------------------
# varparent VARADDR
#
# Parent address.
#-------------------------------------------------------------------------------
#|test(match,p001:items:12)         varparent( "p001:items:12:1000" );
#|test(match,p001:items:10:subs)    varparent( "p001:items:10:subs:100" );
#|test(match)                       varparent( "p001" );
#-------------------------------------------------------------------------------

sub varparent {
  my $given = defined $_[0] ? $_[0] : '';
  my ($container) = ( $given =~ /(.*)[$DELIMS]/ );
  return $container || '';
}#varparent

# ------------------------------------------------------------------------------
# dotaddr VARADDR
# 
# Replace address separators with dots.  In essence, protecting the address
# from expansion.
# ------------------------------------------------------------------------------
#|test(match,p004.proj.1000)        dotaddr("p004:proj:1000");
#|test(match,p004.proj.1000.name)   dotaddr("p004:proj:1000:name");
#|test(match,p001)                  dotaddr("p001");
#|test(!defined)                    dotaddr("");
# ------------------------------------------------------------------------------

sub dotaddr {
  my $address = shift || return;
  $address =~ s/:/./g;
  return $address;
}#dotaddr

# ------------------------------------------------------------------------------
# expand HASHREF, [OPTIONS]
#
# Expands keys which are formatted as names (see naming.txt) into subhashes
# and subarrays as necessary.
#
# OPTIONS:
#
#   meta    => 1                # add '.address' and '.id' metadata to hashes
#   root    => SCALAR           # use this as a prefix for '.address'
# 
# Returns HASHREF
# ------------------------------------------------------------------------------

sub expand {
  my $src = shift || return; # source data
  my $new = {}; # destination data
  my %ops = @_;
  my %meta = ();
  if( ref($src) eq 'HASH' ) {
    foreach my $k ( sort Hub::keydepth_sort keys %$src ) {
      my $v = $$src{$k};
      my @addr = split /[$DELIMS]/, $k;
      my @nest = map { "->{'$_'}" } @addr;
      my $dest = "\$new@nest";
      eval( "$dest = \$v" );
      # Create metadata
      if( $ops{'meta'} ) {
        pop @addr; # remove field key
        if( @addr ) {
          my $meta_addr = join ':', @addr;
          unshift( @addr, $ops{'root'} ) if $ops{'root'};
          my $meta_addr_val = join ':', @addr;
          $meta{"$meta_addr:.address"} = $meta_addr_val;
          $meta{"$meta_addr:.id"} = pop @addr;
        }#if
      }#if
    }#foreach
  }#if
  if( %meta ) {
    my $metadata = Hub::expand( \%meta );
    Hub::merge( $new, $metadata );
  }#if
  return $new;
}#expand

# ------------------------------------------------------------------------------
# collapse - Collapse a nested structure into key/value pairs
# collapse ?ref, [options]
#
# options
#
#   -containers=1        Just return containers
#
# Returns a hash reference.
# ------------------------------------------------------------------------------

sub collapse {
  my ($opts, $ref, $addr, $result) = Hub::opts(\@_, {'containers'=>0});
  croak "Provide a reference" unless ref($ref);
# my $addr = shift || '';
# my $result = shift;
  $addr ||= '';
  unless (defined $result) {
    my %sh; tie %sh, 'Hub::Knots::SortedHash';
    $result = \%sh;
  }
  if (isa($ref, 'HASH')) {
    $addr .= '/' if $addr;
    foreach my $k (keys %$ref) {
      if (ref($$ref{$k})) {
        $$result{$addr.$k} = $ref if $$opts{'containers'};
        collapse($$ref{$k}, $addr.$k, $result, -opts => $opts);
      } else {
        $$result{$addr.$k} = $$ref{$k}
          unless $$opts{'containers'};
      }
    }
  } elsif (isa($ref, 'ARRAY')) {
    for (my $idx = 0; $idx <= @$ref; $idx++) {
      if (ref($$ref[$idx])) {
        $$result{"$addr/$idx"} = $ref if $$opts{'containers'};
        collapse($$ref[$idx], "$addr/$idx", $result, -opts => $opts);
      } else {
        $$result{"$addr/$idx"} = $$ref[$idx]
          unless $$opts{'containers'};
      }
    }
  } elsif (isa($ref, 'SCALAR')) {
      $$result{$addr} = $$ref
        unless $$opts{'containers'};
  } else {
    die "Cannot collapse: $ref";
  }
  return $result;
}

1;