package Spike::Config; use strict; use warnings; use feature 'state'; sub new { my $proto = shift; my $class = ref $proto || $proto; my %args = @_; my $handler = sub { state $config ||= bless { %args }, "${class}::Section"; if (@_) { my $section = shift; return $config->$section(@_); } return $config; }; return bless $handler, "${class}::Accessor"; } package Spike::Config::Parse; sub set { $_[0] = $_[1] } sub defined { $_[0] // $_[1] // '' } sub def { $_[0] // $_[1] // '' } sub integer { no warnings 'numeric'; CORE::int($_[0] // $_[1] // 0) } sub int { no warnings 'numeric'; CORE::int($_[0] // $_[1] // 0) } sub number { no warnings 'numeric'; 0 + ($_[0] // $_[1] // 0) } sub num { no warnings 'numeric'; 0 + ($_[0] // $_[1] // 0) } sub boolean { !!($_[0] // $_[1]) } sub bool { !!($_[0] // $_[1]) } sub string { ''.($_[0] // $_[1] // '') } sub str { ''.($_[0] // $_[1] // '') } package Spike::Config::Accessor; our $AUTOLOAD; sub AUTOLOAD { my $self = shift; (my $key = $AUTOLOAD) =~ s/^.*:://; (my $class = ref $self) =~ s/::[^:]*$//; return $self->()->$key(@_); } sub DESTROY {} package Spike::Config::Section; our $AUTOLOAD; sub AUTOLOAD { my $self = shift; (my $key = $AUTOLOAD) =~ s/^.*:://; (my $class = ref $self) =~ s/::[^:]*$//; my $section = bless $self->{$key} ||= {}, "${class}::Value"; if (@_) { my $value = shift; return $section->$value(@_); } return $section; } sub DESTROY {} package Spike::Config::Value; use Carp; our $AUTOLOAD; sub AUTOLOAD { my $self = shift; (my $key = $AUTOLOAD) =~ s/^.*:://; (my $class = ref $self) =~ s/::[^:]*$//; if (@_) { my $format = shift; my $method = "${class}::Parse::${format}"; if (defined *{$method}) { no strict 'refs'; return $method->($self->{$key}, @_); } else { carp "Unknown format: $format"; } } return $self->{$key}; } sub DESTROY {} 1;