Created
August 20, 2021 04:51
-
-
Save manchicken/4262ab6fe063bc10f9b0097aaf14d1fb to your computer and use it in GitHub Desktop.
This is a piece of code that my dad sent me to review, roughly a year before he died.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package CfgFile; | |
use strict; | |
use JSON; | |
################################################################################ | |
=head1 NAME | |
CfgFile - Extended JSON formatted flat configuration file utility | |
=head1 SYNOPSIS | |
use JSON; | |
use CfgFile; | |
# Creating a CfgFile object to use | |
my $cfg = CfgFile->new(); | |
# Registering an specializes error handling routine (optional) | |
$cfg->errHandler(\&myErrHandler); | |
# Open (and create if new) the configuration file | |
my $cfgFile = "mySettings.cfg"; | |
$cfg->open($cfgFile); | |
# Open (and create if new) the configuration file and indicating | |
# desired chmod value to be used each time the "store" is called | |
$cfg->open($cfgFile, $chmod); | |
# Storing configuration information | |
my %cfg = ( COLOR => 'red', FLOWER => 'daisy' ); | |
$cfg->store(%cfg); | |
# Loading configuration information | |
%cfg = $cfg->load(); | |
# Close configuration file | |
$cfg->close(); | |
# Storing JSON string instead of a hash. This is internally | |
# called by the "store" method. | |
my $json = encode_json(\%cfg); | |
$cfg->store_json($json); | |
=head1 DESCRIPTION | |
This package provides a simple text file to store configuration and other small | |
to medimum data sets. The format is a JSON object that has been reformatted by | |
indenting the JSON components allowing for easier hand editting of the file | |
if required. The "load" method slurps the entire file into a single hash and | |
the "store" method overwrites the file with the contents of that single hash. | |
The file can only contain a single hash. | |
=back | |
=cut | |
################################################################################ | |
=pod | |
=head1 new( ) | |
Creates a new CfgFile object | |
=cut | |
sub new | |
{ | |
my ($class) = @_; | |
my $self = {}; | |
$self->{_FILENAME} = ""; | |
$self->{_ERRHANDLER} = \&defErrHandler; | |
$self->{_CHMOD} = -1; | |
bless($self, $class); | |
return $self; | |
} | |
################################################################################ | |
=pod | |
=head1 open($filename) | |
=head1 open($filename, $chmod) | |
Opens and creates (if required) the configuration file | |
Arguments: | |
=over | |
=item * B<$filename> - Name of the configuration file | |
=item * B<$chmod> - String to use with the 'chmod' command each time "store" | |
is called. This must be numeric value, generally octal | |
in a format like 0755 for exacutables (see "perldoc chmod" | |
for more details) | |
=back | |
=cut | |
sub open | |
{ | |
my ($self, $filename, $chmod) = @_; | |
# Remember the chmod setting if supplied | |
if ($chmod != undef) | |
{ | |
$self->{_CHMOD} = $chmod; | |
} | |
# If the file exists, just make sure we can open it by opening it | |
if (-f $filename) | |
{ | |
CORE::open (FIL,"<",$filename) | |
|| return $self->{_ERRHANDLER}("?CfgFile::open failed to open file '$filename' - $!"); | |
close FIL; | |
$self->{_FILENAME} = $filename; | |
} | |
# If the file does not exists, create it by storing an empty hash | |
else | |
{ | |
$self->{_FILENAME} = $filename; | |
my %nothing = (); | |
store($self, %nothing); | |
} | |
} | |
################################################################################ | |
=pod | |
=head1 store(%hash) | |
Store the contents of the hash variable in the configuration file | |
Arguments: | |
=over | |
=item * B<%hash> - Perl hash to be stored | |
=back | |
=cut | |
sub store | |
{ | |
my ($self, %data) = @_; | |
# JSON encode the hash and call the json store routine | |
store_json($self, encode_json(\%data)); | |
} | |
################################################################################ | |
=pod | |
=head1 store_json($json) | |
Store the a JSON encoded string into the configuration file. | |
Arguments: | |
=over | |
=item * B<$json> - JSON encoded string to store in the file | |
=back | |
=cut | |
sub store_json | |
{ | |
my ($self, $json) = @_; | |
# No filename means the open() method has not been called yet | |
if ($self->{_FILENAME} eq "") | |
{ | |
return $self->{_ERRHANDLER}("?CfgFile::store called before CfgFile::open"); | |
} | |
my $filename = $self->{_FILENAME}; | |
# Open the file | |
CORE::open (FIL,">",$filename) | |
|| return $self->{_ERRHANDLER}("?CfgFile::store failed to open file '$filename' - $!"); | |
# Split the JSON encoded string into peices at natural indention points | |
my @parts = (); | |
for my $part (split(/([\{\}\[\]\"\,\:\\])/,$json)) | |
{ | |
if ($part ne "") { push(@parts,$part); } | |
} | |
# Build a blank padding string for the indenting | |
# (TODO: is 100 enough?) | |
my @pad = (""); | |
for (1..100) { push(@pad,$pad[$#pad]." "); } | |
my $depth = 0; | |
my $nl = ""; | |
my $quote = 0; | |
my $last = ""; | |
# Write the parts split apart in an indented way | |
while ($#parts >= 0) | |
{ | |
my $part = shift @parts; | |
if ($part eq "\\") | |
{ | |
print FIL $part; | |
$part = shift @parts; | |
print FIL $part; | |
} | |
elsif ($part eq '"') | |
{ | |
if ($quote) | |
{ | |
print FIL $part; | |
$quote = 0; | |
if ($parts[0] eq ",") | |
{ | |
$nl = ""; | |
} | |
else | |
{ | |
$nl = "\n"; | |
} | |
} | |
else | |
{ | |
print FIL $nl.$pad[$depth].$part; | |
$quote = 1; | |
} | |
} | |
elsif ($quote) | |
{ | |
print FIL $part; | |
} | |
elsif ($part eq "{") | |
{ | |
print FIL $nl.$pad[$depth++]."$part\n"; | |
$nl = ""; | |
} | |
elsif ($part eq "}") | |
{ | |
print FIL $nl.$pad[--$depth]."$part"; | |
if ($parts[0] ne ",") { print FIL "\n"; } | |
$nl = ""; | |
} | |
elsif ($part eq "[") | |
{ | |
print FIL $nl.$pad[$depth++]."$part\n"; | |
$nl = ""; | |
} | |
elsif ($part eq "]") | |
{ | |
print FIL $nl.$pad[--$depth]."$part\n"; | |
if ($parts[0] ne ",") { print FIL "\n"; } | |
$nl = ""; | |
} | |
elsif ($part eq ":") | |
{ | |
print FIL $part; | |
if (($parts[0] eq "{") || ($parts[0] eq "[")) | |
{ | |
$nl = "\n"; | |
} | |
else | |
{ | |
$nl = ""; | |
} | |
} | |
elsif ($part eq ",") | |
{ | |
print FIL $part; | |
$nl = "\n"; | |
} | |
else | |
{ | |
print FIL $part; | |
} | |
$last = $part; | |
} | |
print FIL "\n"; | |
close FIL; | |
# If a chmod value was supplied, use it | |
if ($self->{_CHMOD} != -1) | |
{ | |
my $chmod = $self->{_CHMOD}; | |
chmod $chmod, $filename | |
|| return $self->{_ERRHANDLER}("?CfgFile::store cannot 'chmod' file '$filename' - $!"); | |
} | |
} | |
################################################################################ | |
=pod | |
=head1 load( ) | |
Loads the reformatted JSON string from the configuration file and returns a | |
perl hash | |
Returns: | |
=over | |
=item * B<%hash> - Perl hash variable of the contents from the file | |
=back | |
=cut | |
sub load | |
{ | |
my ($self) = @_; | |
# No filename means the open() method has not been called yet | |
if ($self->{_FILENAME} eq "") | |
{ | |
return $self->{_ERRHANDLER}("?CfgFile::load called before CfgFile::open"); | |
} | |
my $filename = $self->{_FILENAME}; | |
# Open the configuration file | |
CORE::open (FIL,"<",$filename) | |
|| return $self->{_ERRHANDLER}("?CfgFile::load failed to open file '$filename' - $!"); | |
# Read the entire file, removing all indents and blank lines | |
my @lines = (); | |
while (my $line = <FIL>) | |
{ | |
chomp $line; | |
$line =~ s/^\s*//; | |
$line =~ s/\s*$//; | |
if ($line ne "") | |
{ | |
push(@lines, $line); | |
} | |
} | |
close FIL; | |
# If no lines were read from the return nothing | |
if (scalar @lines < 1) | |
{ | |
return; | |
} | |
# Join the lines into a single JSON string, decode it and return the hash | |
return %{decode_json(join("",@lines))}; | |
} | |
################################################################################ | |
=pod | |
=head1 close( ) | |
Close the configuration file and clears the CfgFile object. | |
=cut | |
sub close | |
{ | |
my ($self) = @_; | |
$self->{_FILENAME} = ""; | |
$self->{_CHMOD} = -1; | |
} | |
################################################################################ | |
=pod | |
=head1 errHandler(\&routine) | |
This method resgisters an error handling routine to be called whenever CfgFile | |
encounters an error. | |
=over | |
=item * B<\&routine> - Error handler routine name | |
=back | |
The default error handler is 'die'. The error handler routine will be passed | |
a single message string. Any returned value is ignored since it is expected | |
the error handler supplied is just a more graceful 'die'. | |
=cut | |
sub errHandler | |
{ | |
my ($self, $routine) = @_; | |
$self->{_ERRHANDLER} = $routine; | |
} | |
################################################################################ | |
sub defErrHandler | |
################################################################################ | |
{ | |
my ($self, $msg) = @_; | |
die "$msg\n"; | |
} | |
1 | |
__END__ | |
=head1 DIAGNOSTICS | |
-- to do -- | |
=head1 DEPENDENCIES | |
use JSON; | |
=head1 AUTHOR | |
Michael Stemle, Sr |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment