The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

use Test::Cookbook ;

=head1 Config::Hierarchical cookbook

=head2 Simple usage

=head3	Creating a Config::Hierarchical configuration container

	use Config::Hierarchical ;
	
	# give the config a name to get user friendly errors
	
	my $config = new Config::Hierarchical(NAME => 'Simple usage example config') ;

=begin hidden

	# Config::Hierarchical allows us to get its generated warning and errors
	
	my ($warnings, $die) ;
	$config->{INTERACTION}{WARN} = sub{$warnings = join('', @_) ; use Carp ;carp $warnings; } ;
	$config->{INTERACTION}{DIE} = sub{$die= join('', @_) ; use Carp ;croak $die} ; 

=end hidden

This will create a container with default values. You can modify the container behavior by passing options
to the constructor. See the L<Config::Hierarchical> manual for all initialization options.

=head3	Setting and getting configuration variables

	$config->Set(NAME => 'CC', VALUE => 'gcc') ;
	$config->Set(NAME => 'LD', VALUE => 'ld') ;
	
	print "Value for 'CC' is '" . $config->Get(NAME => 'CC') . "'.\n" ;
	print "'LD' exists.\n" if $config->Exists(NAME => 'LD') ;

Would display:

=begin hidden

	my $cc_value = $config->Get(NAME => 'CC') ;
	is($cc_value, 'gcc', 'Get returns right value') ;
	generate_pod("\tValue for 'CC' is '$cc_value'.\n") ; # note the \t in the string so the pod is presented properly
	generate_pod("\t'LD' exists.\n") if $config->Exists(NAME => 'LD') ;
	generate_pod("\n") ;

=end hidden

=head3 Set the same variable multiple time with different values

	$config->Set(NAME => 'CC', VALUE => 'gcc') ;
	$config->Set(NAME => 'CC', VALUE => 'cl') ;
	
	print "Value for 'CC' is '" . $config->Get(NAME => 'CC') . "'.\n\n" ;

Would display:

=begin hidden

	$cc_value = $config->Get(NAME => 'CC') ;
	is($cc_value, 'cl', 'Get returns right value') ;
	generate_pod("\tValue for 'CC' is '$cc_value'.\n\n") ;

=end hidden

B<Config::Hierarchical> does not generate any warning when you override a variable's value. If you would like to get an error,
lock the variable.

=head4 Locking variables

=begin not_tested

	$config->Set(NAME => 'CC', VALUE => 'gcc') ;
	$config->Lock(NAME => 'CC') ;
	$config->Set(NAME => 'CC', VALUE => 'cl') ;

=end not_tested

This would generate the following error followed by a stack trace.

=begin hidden

# the code above generates an error and dies so we can't run it directly in a common section

	dies_ok
		{
		$config->Set(NAME => 'CC', VALUE => 'gcc') ;
		$config->Lock(NAME => 'CC') ;
		$config->Set(NAME => 'CC', VALUE => 'cl') ;
		} 'Setting locked variable' ;
	
	$die =~ s/^/\t/gm ;
	generate_pod($die . "\n") ;
	
	$cc_value = $config->Get(NAME => 'CC') ;
	is($cc_value, 'gcc', 'Get returns right value') ;

=end hidden

=head4 Setting Locked variables

	$config->Set(FORCE_LOCK => 1, NAME => 'CC', VALUE => 'cl') ;
	print "Value for 'CC' is '" . $config->Get(NAME => 'CC') . "'.\n\n" ;

Would display:

=begin hidden

	$cc_value = $config->Get(NAME => 'CC') ;
	is($cc_value, 'cl', 'Get returns right value') ;
	generate_pod("\t$warnings\n") ;
	generate_pod("\tValue for 'CC' is '$cc_value'.\n\n") ;

=end hidden

=head4 Getting the lock state

	print "'CC' is locked.\n" if $config->IsLocked(NAME => 'CC') ;
	print "'LD' is locked.\n" if $config->IsLocked(NAME => 'LD') ;

Would display:

=begin hidden

	is($config->IsLocked(NAME => 'LD'), 0, 'config not locked') ;
	is($config->IsLocked(NAME => 'CC'), 1, 'config locked') ;
	
	generate_pod("\t'CC' is locked.\n") if $config->IsLocked(NAME => 'CC') ;
	generate_pod("\t'LD' is locked.\n") if $config->IsLocked(NAME => 'LD') ;
	generate_pod("\n") ;

=end hidden

=for POD::Tested reset

=head2 Setting variable  in the constructor

	use Config::Hierarchical ;
	
	my $config = new Config::Hierarchical
				(
				NAME => 'Config initialized in constructor',
				INITIAL_VALUES =>
					[
					{NAME => 'CC', VALUE => 1},
					{NAME => 'CC', VALUE => 2},
					{NAME => 'LD', VALUE => 3, LOCK => 1},
					{NAME => 'AS', VALUE => 4, LOCK => 1},
					],
				) ;

=begin hidden

	my ($warnings, $die) ;
	$config->{INTERACTION}{WARN} = sub{$warnings = join('', @_) ; use Carp ;carp $warnings; } ;
	$config->{INTERACTION}{DIE} = sub{$die= join('', @_) ; use Carp ;croak $die} ; 
	
	is($config->IsLocked(NAME => 'CC'), 0, 'config not locked') ;
	is($config->IsLocked(NAME => 'LD'), 1, 'config locked') ;
	
	is($config->Get(NAME => 'CC'), '2', 'initialized ok') or diag  $config->GetDump();
	is($config->Get(NAME => 'LD'), '3', 'initialized ok') ;
	is($config->Get(CATEGORY => 'CURRENT', NAME => 'AS'), 4, 'initialized ok') ;
	
	is($config->Exists(NAME => 'AS'), 1, 'exist') ;

=end hidden

=head2 Getting a non existing variable value

	my $value = $config->Get(NAME => 'NON_EXISTING') ;
	print "Value for 'NON_EXISTING' is defined\n" if defined $value ;

Would display:

=begin hidden

	generate_pod("\t$warnings\n") ;
	generate_pod("Value for 'NON_EXISTING' is defined\n") if defined $value ;

=end hidden

=head2 Getting multiple variable values

	my @variables = qw(CC LD AS) ;
	my %values ;
	
	@values{@variables} = $config->GetMultiple(@variables) ;
	
	use Data::TreeDumper ;
	
	my $dump = DumpTree \%values, 'Variables', INDENTATION => "\t", DISPLAY_ADDRESS => 0 ; 
	print $dump ;

Would display:

=begin hidden

	is_deeply(\%values, {CC => 2, LD => 3, AS => 4}, 'GetMultiple OK') ;
	
	generate_pod("$dump\n") ;
	generate_pod("\n") ;

=end hidden

=head2 Variable Attribute

You can attach an attribute to a configuration variable. If the same variable exists in different categories,
each the variables have a separate attribute. Changing a variable value doesn't change the attribute.

	$config->SetAttribute(NAME => 'CC', VALUE => 'attribute') ;
	$config->Set(NAME => 'CC', VALUE => 'some_compiler') ;
	
	my ($attribute, $attribute_exists) = $config->GetAttribute(NAME => 'CC') ;

=begin hidden

	is($attribute, 'attribute') ;

=end hidden

You can also  set the attribute and the value at the same time. This also means you can set the attribute 
in the B<INITIAL_VALUES> section of the configuration constructor.

	$config->Set(NAME => 'CC', VALUE => 'gcc', ATTRIBUTE => 'another_attribute') ;

=begin hidden

	($attribute, $attribute_exists) = $config->GetAttribute(NAME => 'CC') ;
	is($attribute, 'another_attribute') ;

=end hidden

=head2 history and comments

B<Config::Hierarchical> will keep an history  for each variable in you config.

	print $config->GetHistoryDump(NAME => 'CC') ;

=begin hidden

	generate_pod($config->GetHistoryDump(NAME => 'CC', DATA_TREEDUMPER_OPTIONS => [INDENTATION => "\t"])) ;
	generate_pod("\n") ;

=end hidden

The reference manual describes a L<Data::TreeDumper> filter that you can use to generate a history in the
following format:

=begin hidden

	sub Compact
	{
	my ($s, $level, $path, $keys, $setup, $arg) = @_ ;
	
	if('ARRAY' eq ref $s)
		{
		my ($index, @replacement, @keys) = (0) ;
		
		for my $entry( @$s)
			{
			if(exists $entry->{EVENT})
				{
				push @replacement, $entry->{EVENT} ; #. 'time: ' . $entry->{TIME};
				push@keys, $index++ ;
				}
			else
				{
				my ($aliased_history_name) = grep {$_ ne 'TIME'} keys %$entry ;
				
				push @replacement, $entry->{$aliased_history_name} ;
				push@keys, [$index, "$index = $aliased_history_name"] ;
				$index++ ;
				}
			}
		
		return('ARRAY', \@replacement, @keys) ;
		}
	}
	
	my $compact_dump = DumpTree $config->GetHistory( NAME => 'CC'), 'CC', DISPLAY_ADDRESS => 0, FILTER => \&Compact, INDENTATION => "\t" ;
	
	generate_pod($compact_dump) ;
	generate_pod("\n") ;

=end hidden

You can also add a comment to the history when you manipulate variables.

	$config->Set(NAME => 'LD', VALUE => 'new LD value', FORCE_LOCK => 1, COMMENT => 'why we forced the lock') ;

Would give this history:

=begin hidden

	generate_pod($config->GetHistoryDump(NAME => 'LD', DATA_TREEDUMPER_OPTIONS => [INDENTATION => "\t"])) ;
	generate_pod("\n") ;

=end hidden

See I<GetHistory> in the manual if you want to handle the history data directly.

=head2 Validators

You can assign validators to variables. If a validator return B<false>, B<Config::Hierarchical> will generate and error.

Validators can be defined in the B<Config::Hierarchical> constructor or can be local in a I<Set> call.

	sub PositiveValueValidator
	{
	my ($value) = @_; 
	return($value >= 0)
	} ;

=begin not_tested

	$config->Set
		(
		NAME => 'CC',
		VALUE => -1, 
		VALIDATORS => {positive_value => \&PositiveValueValidator,},
		) ;	

=end not_tested

Will generate the following error:

=begin hidden

	throws_ok
		{
		$config->Set
			(
			NAME => 'CC',
			VALUE => -1, 
			VALIDATORS => {positive_value => \&PositiveValueValidator,},
			) ;	
		} qr/Invalid value '-1' for variable 'CC'. Local validator 'positive_value' defined at .*/, "local validator" ;
		
	$die =~ s/^/\t/gm ;
	generate_pod($die . "\n") ;

=end hidden

=head2 GetKeys

You can get a list of all the variable names.

	my @variable_names = $config->GetKeys() ;
	
	print 'The config contains the following variables: ' . join(', ', @variable_names) . ".\n" ;

Would display:

=begin hidden

	generate_pod("\tThe config contains the following variables: " . join(', ', @variable_names) . ".\n\n") ;

=end hidden

=head2 Key and value tuples

You can also get a list containing a tuple for each of the config variable. The Tuple is a hash reference. This lets you
write code like :

=begin not_tested

	map
		{
		my $name =  $_->{NAME} ;
		my $value = $_->{VALUE} ;
		
		# your code here
		
		} $config->GetKeyValueTuples() ;

=end not_tested

=head2 Categories

=for POD::Tested reset

=begin not_tested

	my $config = new Config::Hierarchical
				(
				NAME => 'config with categories',
				
				CATEGORY_NAMES   => ['A', 'B'],
				DEFAULT_CATEGORY => 'B',
				
				INITIAL_VALUES  =>
					[
					{CATEGORY => 'A', NAME => 'CC', VALUE => 'A_CC'},
					{CATEGORY => 'B', NAME => 'CC', VALUE => 'B_CC'},
					{CATEGORY => 'A', NAME => 'LD', VALUE => 'A_LD'},
					{CATEGORY => 'B', NAME => 'LD', VALUE => 'B_LD'},
					{CATEGORY => 'A', NAME => 'AS', VALUE => 'A_AS'},
					] ,
				) ;

=end not_tested

=begin hidden

# initialisation above generates warnings we need to catch

	my ($warnings, $die) ;
	
	my $config = new Config::Hierarchical
				(
				NAME => 'config with categories',
				
				CATEGORY_NAMES   => ['A', 'B'],
				DEFAULT_CATEGORY => 'B',
				
				INITIAL_VALUES  =>
					[
					{CATEGORY => 'A', NAME => 'CC', VALUE => 'A_CC'},
					{CATEGORY => 'B', NAME => 'CC', VALUE => 'B_CC'},
					{CATEGORY => 'A', NAME => 'LD', VALUE => 'A_LD'},
					{CATEGORY => 'B', NAME => 'LD', VALUE => 'B_LD'},
					{CATEGORY => 'A', NAME => 'AS', VALUE => 'A_AS'},
					] ,
					
				INTERACTION =>
					{
					WARN => sub{$warnings .= "\t" . join('', @_) . "\n"; use Carp ;carp join('', @_); },
					DIE => sub{$die .= join('', @_) ; use Carp ;croak join('', @_)},
					} ,
				) ;
	
=end hidden

Would generate the following warnings:

=begin hidden

	generate_pod("$warnings\n") ;
	$warnings = '' ;

=end hidden

And the config would be:

=begin hidden

	my $hash = $config->GetHashRef() ;
	
	generate_pod(DumpTree($hash, 'Config contains:', DISPLAY_ADDRESS => 0 , INDENTATION => "\t")) ;
	generate_pod("\n") ;

=end hidden

B<Config::Hierarchical> will display a warning anytime you set a variable and that a higher level configuration takes precedence.

=head3 Lower categories warning

By default, no warning are displayed when a lower category value will be ignored. You can make B<Config::Hierarchical> check
lower categories this way:

  $config->Set(CATEGORY => 'A', NAME => 'CC', VALUE => 'A_CC_2', CHECK_LOWER_LEVEL_CATEGORIES => 1) ;

Would generate the following warnings:

=begin hidden

	generate_pod("$warnings\n") ;
	$warnings = '' ;

=end hidden

The config is now:

=begin hidden

	$hash = $config->GetHashRef() ;
	
	generate_pod(DumpTree($hash, 'Config contains:', DISPLAY_ADDRESS => 0 , INDENTATION => "\t")) ;
	generate_pod("\n") ;

=end hidden

=head3 Overriding a higher level category

Is done this way:

  $config->Set(CATEGORY => 'B', NAME => 'CC', VALUE => 'B_CC_2', OVERRIDE => 1) ;

And would generate the following warnings:

=begin hidden

	generate_pod("$warnings\n") ;
	$warnings = '' ;

=end hidden

The config is now:

=begin hidden

	$hash = $config->GetHashRef() ;
	
	generate_pod(DumpTree($hash, 'Config contains:', DISPLAY_ADDRESS => 0 , INDENTATION => "\t")) ;
	generate_pod("\n") ;

=end hidden

=head3 History from multiple categories

	print $config->GetHistoryDump(NAME => 'CC') ;

=begin hidden

	generate_pod($config->GetHistoryDump(NAME => 'CC', DATA_TREEDUMPER_OPTIONS => [INDENTATION => "\t"])) ;
	generate_pod("\n") ;

=end hidden


=head2 Tie::Readonly

You can tie your configuration to a read only hash. this lets you manipulate your config like a normal hash. Interpolation
in strings is also much easier.

	my %hash ;
	tie %hash, 'Config::Hierarchical::Tie::ReadOnly' => $config ;
	
	my $keys = join(', ', sort keys %hash) ;
	
	print "The config variables are: $keys\n" ;
	
	print "CC's value is '$hash{CC}'\n" ;

Would display:

=begin hidden

	generate_pod("\tThe config variables are: $keys\n") ;
	generate_pod("\tCC's value is '$hash{CC}'\n") ;
	generate_pod("\n") ;

=end hidden

Remember that the hash is read only. Trying to modify a variable is not allowed:

=begin not_tested

	$hash{CC} = 2 ;

=end not_tested

Would generate this error:

=begin hidden

	dies_ok
		{
		$hash{CC} = 2 ;
		} 'setting a read only variable' ;
		
	$die =~ s/^/\t/gm ;
	generate_pod($die . "\n") ;

=end hidden

=head2 Copying data from another config

Use the code below to initialized a category from data copied from another category:

	my $config_2 = new Config::Hierarchical
					(
					NAME => 'config initialized from another config',
					#
					CATEGORY_NAMES         => ['PARENT', 'CURRENT'],
					DEFAULT_CATEGORY       => 'CURRENT',
					#
					INITIAL_VALUES =>
						[
						# Initializing a category from another config
						map
							({
								{
								NAME     => $_->{NAME},
								VALUE    => $_->{VALUE}, 
								CATEGORY => 'PARENT',
								LOCK     => 1,
								HISTORY  => $config->GetHistory(NAME => $_->{NAME}),
								}
							} $config->GetKeyValueTuples()),
						#
						{NAME => 'VALUE_IN_CURRENT_CATEGORY', VALUE => 1},
						],
					) ;

And the config would be:

=begin hidden

	$hash = $config_2->GetHashRef() ;
	
	generate_pod(DumpTree($hash, 'Config 2 contains:', DISPLAY_ADDRESS => 0 , INDENTATION => "\t")) ;
	generate_pod("\n") ;

=end hidden


=head2 Aliasing other configurations

	my $config_3 = new Config::Hierarchical
					(
					NAME => 'config with aliases',
					#
					CATEGORY_NAMES         => ['PARENT', 'CURRENT'],
					DEFAULT_CATEGORY       => 'CURRENT',
					#
					INITIAL_VALUES =>
						[
						{
						CATEGORY       => 'PARENT',
						ALIAS_CATEGORY => $config,
						},
						# more initialization if necessary
						],
					) ;

=begin hidden

	my ($warnings_3, $die_3) ;
	$config_3->{INTERACTION}{WARN} = sub{$warnings_3 = join('', @_) ; use Carp ;carp $warnings_3; } ;
	$config_3->{INTERACTION}{DIE} = sub{$die_3 = join('', @_) ; use Carp ;croak $die_3} ; 

=end hidden

	$config_3->Set(NAME => 'LD', VALUE => 'new LD') ;

Would generate this warning:

=begin hidden

	generate_pod("\t$warnings_3\n") ;

=end hidden

And the config would be:

=begin hidden

	$hash = $config_3->GetHashRef() ;
	
	generate_pod(DumpTree($hash, 'Config 3 contains:', DISPLAY_ADDRESS => 0 , INDENTATION => "\t")) ;
	generate_pod("\n") ;

=end hidden

=head3 History from aliased configuration

B<Config::Hierarchical> will, display aliased categories history.

	print $config_3->GetHistoryDump(NAME => 'LD') ;

=begin hidden

	generate_pod($config_3->GetHistoryDump(NAME => 'LD', DATA_TREEDUMPER_OPTIONS => [INDENTATION => "\t"])) ;
	generate_pod("\n") ;

=end hidden

=cut