The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#####################################################################
# Hash.pm 
# Copyright (c) 1999, 2000 by Markus Winand       <mws@fatalmind.com>
#
# Class for reading config files into a hash 
#
# $Id: Hash.pm,v 1.10 2000/06/25 17:08:56 mws Exp $
#

package CONFIG::Hash;

use strict;
use CONFIG::Plain;

# the base class....
@CONFIG::Hash::ISA = qw(CONFIG::Plain);


#####################################################################
# new
#
# creates a new object from the class
#
# paramters: same as CONFIG::Plain->new 
sub new {
        my $proto    = shift;
        my $class    = ref($proto) || $proto;
	my $self;
	my $removetrailingblanks;

	if (ref($_[0]) eq "HASH") {
		$removetrailingblanks = $_[0]->{REMOVETRAILINGBLANKS};
		$_[0]->{REMOVETRAILINGBLANKS} = "0";
	} else {
		if (! defined $_[1]) {
			$_[1] = {};
		}
		$removetrailingblanks = $_[1]->{REMOVETRAILINGBLANKS};
		$_[1]->{REMOVETRAILINGBLANKS} = "0";
	}

        $self     = $class->SUPER::new(@_);

	if (! defined $removetrailingblanks) {
		$removetrailingblanks = "1";
	}

	$self->{COMMON}->{CONFIG}->{REMOVETRAILINGBLANKS} = $removetrailingblanks;

	bless ($self, $class);

	if ($self->reparse) {
		if (! defined $self->{COMMON}->{CONFIG}->{KEYREGEXP}) {
			$self->{COMMON}->{CONFIG}->{KEYREGEXP} = "^(\\S+)";
		}
		if (! defined $self->{COMMON}->{CONFIG}->{HASHREGEXP}) {
			$self->{COMMON}->{CONFIG}->{HASHREGEXP} = "\\s+(.*)\$";
		}

		$self->read_hash();

		if (ref($self->{COMMON}->{CONFIG}->{DEFAULT}) eq "HASH") {
			$self->make_defaults();
		}
	
		if (ref($self->{COMMON}->{CONFIG}->{REQUIRE}) eq "ARRAY") {
			$self->check_require();
		}
	}

	$self->{COMMON}->{'_CODE_TYPE'} = "Hash";	

	return $self;
}

#####################################################################
# read_hash
#
# reads the file linewhise into a hash
#
# parameters: 1st -> object
sub read_hash {
	my ($self) = @_;
	my %HASH   = ();	
	my %LINE   = ();
	my %FILE   = ();
	my ($line, $longline);
	my ($key, $value, $hlp);

	# this variables stores the start point of a KEY.
	# since a KEY/VALUE pair will no parsed until the next KEY or EOF
	# is found, this variables are needed to store the point where
	# the first KEY was found (for error reporting,...)
	my (    $lineno,     $file,     $line_cursor);
	my ($longlineno, $longfile, $longline_cursor);

	$longline = "";
	$longlineno = 0;
	$longfile = "unknown";
	$longline_cursor =0;
	while (defined ($line = $self->getline()) || (defined $longline && $longline ne "")) {
		if ( ! defined $line || $line =~ m/$self->{COMMON}->{CONFIG}->{KEYREGEXP}/s ) {
			$lineno = $self->getline_number;
			$file   = $self->getline_file;
			$line_cursor = $self->getline_cursor;
			if ($longline =~ m/$self->{COMMON}->{CONFIG}->{KEYREGEXP}$self->{COMMON}->{CONFIG}->{HASHREGEXP}/s) {	
				$key   = $1;
				if (defined $self->{COMMON}->{CONFIG}->{CASEINSENSITIVE}) {
					$key   =~ tr /A-Z/a-z/;
				}
				$value = $2;
				if (defined $HASH{$key}) {
					if ($self->{COMMON}->{CONFIG}->{ALLOWREDEFINE}){
						$HASH{$key} = $value;
					} else {
						# generate error
						$self->setline_error("Key <$key> already defined", $longline_cursor);
					}
				} else {
					$HASH{$key} = $value;
				}	
		
				# get complete include path
				push @{$LINE{$key}}, $longlineno;
				push @{$FILE{$key}}, $longfile;
				while (defined ($hlp = $self->getline_number)) {
					push @{$LINE{$key}}, $hlp; 
					push @{$FILE{$key}},$self->getline_file;
				}
			}
			if ($self->{COMMON}->{CONFIG}->{REMOVETRAILINGBLANKS} &&
			    defined $line) {
		                $line =~ s/^\s*//;
		                $line =~ s/\s*\n/\n/;
			}
			if (defined $self->{COMMON}->{CONFIG}->{SUBSTITUTENEWLINE} &&
			    defined $line) {
				$line =~ s/\n/$self->{COMMON}->{CONFIG}->{SUBSTITUTENEWLINE}/;
			}
			$longline = $line;
			$longlineno = $lineno;
			$longfile = $file;
			$longline_cursor = $line_cursor;
		} else {
			if ($self->{COMMON}->{CONFIG}->{REMOVETRAILINGBLANKS} &&
			    defined $line) {
		                $line =~ s/^\s*//;
		                $line =~ s/\s*\n/\n/;
			}
			if (defined $self->{COMMON}->{CONFIG}->{SUBSTITUTENEWLINE} &&
			    defined $line) {
				$line =~ s/\n/$self->{COMMON}->{CONFIG}->{SUBSTITUTENEWLINE}/;
			}
			# multi line, or error 
			$longline .= $line;
		}
	}

	$self->{COMMON}->{"Hash.pm"}->{HASH} = \%HASH;
	$self->{COMMON}->{"Hash.pm"}->{LINE} = \%LINE;
	$self->{COMMON}->{"Hash.pm"}->{FILE} = \%FILE;
}

#####################################################################
# make_defaults
#
# inserts the DEFUALT values into the local stored data
#
# parameters: 1st -> object
sub make_defaults {
	my ($self) = @_;
	my $key;

	foreach $key (keys (%{$self->{COMMON}->{CONFIG}->{DEFAULT}})) {
		if (defined $self->{COMMON}->{CONFIG}->{CASEINSENSITIVE}) {
			$key   =~ tr /A-Z/a-z/;
		}
		if (! defined $self->{COMMON}->{"Hash.pm"}->{HASH}->{$key}) {
			$self->{COMMON}->{"Hash.pm"}->{HASH}->{$key} = 
				$self->{COMMON}->{CONFIG}->{DEFAULT}->{$key};
			$self->{COMMON}->{"Hash.pm"}->{FILE}->{$key} = 
				['DEFAULT'];
			$self->{COMMON}->{"Hash.pm"}->{LINE}->{$key} = [0];
		}
	}

}

#####################################################################
# check_require
#
# checks for the required keys
#
# parameters: 1st -> object
sub check_require {
	my ($self) = @_;
	my $key;

	foreach $key (@{$self->{COMMON}->{CONFIG}->{REQUIRE}}) {
		if (defined $self->{COMMON}->{CONFIG}->{CASEINSENSITIVE}) {
			$key   =~ tr /A-Z/a-z/;
		}
		if (! defined $self->{COMMON}->{"Hash.pm"}->{HASH}->{$key}) {
			$self->setglobal_error("Required key <$key> not found.");
		}
	}
}
#####################################################################
# get
#
# returns the value to a given key, or a __reference__ to the hash
#
# parameters: 1st -> object
#             2nd -> (optional) key 
sub get {
	my ($self, $key) = @_;

	if (defined $key &&
                    $key ne "") {
		return $self->{COMMON}->{"Hash.pm"}->{HASH}->{$key};
	} else {
		return $self->{COMMON}->{"Hash.pm"}->{HASH};
	}
}


#####################################################################
# get_line
#
# returns the linenumber where the key was found. 
# Call often to get include path
#
# parameters: 1st -> object
#             2nd -> key
sub get_line {
	my ($self, $key) = @_;

	if (! defined $key) {
		$self->{CURSORS}->{get_line_LASTKEY} = "";
		return undef;
	}

	if (defined $self->{CURSORS}->{get_line_LASTKEY} &&
		    $self->{CURSORS}->{get_line_LASTKEY} ne $key) {
		$self->{CURSORS}->{get_line} = 0;
		$self->{CURSORS}->{get_line_LASTKEY} = $key;
	}

	return $self->{COMMON}->{"Hash.pm"}->{LINE}->{$key}->
			[$self->{CURSORS}->{get_line}++];	
}

#####################################################################
# get_file
#
# returns the filename where the key was found.
# Call often to get include path
#
# parameters: 1st -> object
#             2nd -> key
sub get_file ($$) {
	my ($self, $key) = @_;

	if (! defined $key) {
		$self->{CURSORS}->{get_file_LASTKEY} = "";
		return undef;
	}

	if ($self->{CURSORS}->{get_file_LASTKEY} ne $key) {
		$self->{CURSORS}->{get_file} = 0;
		$self->{CURSORS}->{get_file_LASTKEY} = $key;
	}	

	return $self->{COMMON}->{"Hash.pm"}->{FILE}->{$key}->
			[$self->{CURSORS}->{get_file}++];	
}
1;


__END__

=head1 NAME

CONFIG::Hash - Class to read 2-column files into a hash

=head1 SYNOPSIS

   use CONFIG::Hash;

   my $file = CONFIG::Hash->new($filename, \%config);

   $hash_ref = $file->get();

   $value = $file->get($key);

=head1 DESCRIPTION

Parses a two-column formated file into a hash. The module uses the
CONFIG::Plain class so you can use all features of the Plain module. 

=head1 METHODS

=head2 new - parse file (read via CONFIG::Plain) into hash

Configuration Options:

   -> all described in CONFIG::Plain are known


   KEYREGEXP

	Scalar holding a regular expression which must match every key.

	DEFAULT: "^(\\S+)"

	HINT: Since the first character of a line has to be a non-white-space
	      character it is possible to make multi-line values.
	      Have a look at the examples.

   HASHREGEXP

	Scalar holding a regular expression which matches the content.

	DEFAULT: "\\s+(.*)\$"

   SUBSTITUTENEWLINE

	If defined all NewLine characters in the value will be substituted
	with this scalar.

	DEFAULT: "\n"
	
   REQUIRE

	Reference to a Array which holds list of required variables.

	DEFAULT: []

   DEFAULT
	
	Reference to Hash holding default Values.

	DEFAULT: {}

   ALLOWREDEFINE

	Scalar switch to suppress error messages if the same key is
	redefined at a later point in file.

	DEFAULT: 1

   CASEINSENSITIVE

	All keys are convertet into lower case if this option was defined.

	DEFAULT: undef

=head2 get - get a reference to the hash or a specified field

   $hash_ref = $file->get();

      Returns a reference to the hash holding all data from file.

   $value = $file->get($key);

      Returns the value to the specified key.

=head2 get_line - get the linenumber where this key was found

   $line_nr = $file->get_line($key);
 
      Returns a scalar holding the line number. Call often to get
      include path.

=head2 get_file - get the filename where this key was found

   $filename = $file->get_file($key);

      Returns a scalar holding the filename. Call often to get
      include path.

=head1 EXAMPLES

	Assumes default configuration

	>KEY	This is a very stupid text
	>	but it shows the functionality \
	>	of this module

	Will get into
	'KEY' => "This is a very stupid text\nbut it shows the functionality of this module"

	With the config setting 'SUBSTITUTENEWLINE' => ' '
	
	>INSERT	insert into 
	>	table dummy
	>		(col1, col2, col3)
	>	values
	>		(1, "value", "value2");

	Will get into
	'INSERT' => 'insert into table dummy (col1, col2, col3) values (1, "value", "value2");'

=head1 SEE ALSO

CONFIG::Plain(3pm)

The CONFIG:: Guide at http://www.fatalmind.com/programs/CONFIG/

=head1 COPYRIGHT

    Copyright (C) 1999, 2000 by Markus Winand <mws@fatalmind.com>

    This program is free software; you can redistribute it and/or
    modify it under the same terms as Perl itself.