Last active
February 21, 2025 14:32
-
-
Save LukeSavefrogs/c41a5193fef11fc426b9acd9adb63917 to your computer and use it in GitHub Desktop.
Perl Text template formatters
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
# ------------------------------------------------------------------------------ | |
# Text::Template | |
# ------------------------------------------------------------------------------ | |
# This module provides a simple way to perform Python-like string formatting | |
# using a dictionary of key-value pairs. The template string should contain | |
# placeholders in the form of '%(key)s'. | |
# | |
package Text::Template; | |
use v5.16; | |
use strict; | |
use warnings; | |
use English qw(-no_match_vars); | |
# Perform a Python-like string formatting using a dictionary of key-value pairs. | |
# The template string should contain placeholders in the form of '%(key)s'. | |
# | |
# Parameters: | |
# $template: The template string to format | |
# $data: The dictionary of key-value pairs to use for the formatting | |
# $config: Optional configuration hash | |
# - throwOnMissingKey: If true, an exception is thrown if a key is not found in the data dictionary | |
# - defaultValue: The default value to use if the key is not found in the data dictionary | |
# - formatRegex: The regex pattern to use for the placeholders | |
# | |
# Returns: | |
# The formatted string | |
# | |
# Example: | |
# my $template = Text::Template->new(template => "test1:%(test1)s test2:%(test2)s"); | |
# my $result = $template->format({ test1 => 10 }); | |
# print($result); # Output: test1:10 test2:'' | |
# | |
sub new { | |
my ($class, %args) = @_; | |
my $self = { | |
template => $args{template} // "", | |
config => { | |
throwOnMissingKey => 0, | |
defaultValue => "", | |
formatRegex => '%\((?<key>[^)]+)\)s', | |
%{ $args{config} // {} }, | |
}, | |
}; | |
bless $self, $class; | |
return $self; | |
} | |
# Format the template string using the provided data dictionary. | |
# | |
# Parameters: | |
# $data: The dictionary of key-value pairs to use for the formatting | |
# | |
# Returns: | |
# The formatted string | |
# | |
# Example: | |
# my $result = $template->format({ test1 => 10 }); | |
# print($result); # Output: test1:10 test2:'' | |
# | |
sub format { | |
my ($self, $data) = @_; | |
my $result = $self->{template}; | |
my $config = $self->{config}; | |
while ($result =~ m/$config->{formatRegex}/g) { | |
die "Unexpected number of capture groups found in the format regex (expected 1, found $#+).\n" | |
if ($#+ != 1); | |
my $key = $1; | |
if (!exists $data->{$key} && $config->{throwOnMissingKey}) { | |
die "Missing key '$key' in the data dictionary.\n"; | |
} | |
my $value = $data->{$key} // $config->{defaultValue}; | |
substr($result, $-[0], $+[0] - $-[0], $value); | |
} | |
return $result; | |
} | |
# Get the template string. | |
# | |
sub get_template { | |
my ($self) = @_; | |
return $self->{template}; | |
} | |
# Get the configuration hash. | |
# | |
sub get_config { | |
my ($self) = @_; | |
return $self->{config}; | |
} | |
# Get the groups found in the template string. | |
# | |
# Example: | |
# my %groups = $template->get_groups(); | |
# print(Dumper(\%groups)); # Output: { 'test1' => '%(test1)s', 'test2' => '%(test2)s' } | |
# | |
sub get_groups { | |
my ($self) = @_; | |
my %groups = (); | |
while ($self->{template} =~ m/(?<full>$self->{config}->{formatRegex})/g) { | |
die "Unexpected number of capture groups found in the format regex (expected 2, found $#+).\n" | |
if ($#+ != 2); | |
my $key = $+{key} // $2; | |
$groups{$key} = $+{full}; | |
} | |
return %groups; | |
} | |
# https://stackoverflow.com/a/3395759/8965861 | |
__PACKAGE__->run( @ARGV ) unless caller; | |
sub run { | |
my( $class, @args ) = @_; | |
use Data::Dumper; | |
use Test::More; | |
ok( | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s")->get_template() eq "test1:%(test1)s test2:%(test2)s", | |
"Retrieve template" | |
); | |
ok( | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s")->get_config()->{throwOnMissingKey} == 0, | |
"Default configuration value: 'throwOnMissingKey'" | |
); | |
ok( | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s")->get_config()->{defaultValue} eq "", | |
"Default configuration value: 'defaultValue'" | |
); | |
ok( | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s")->get_config()->{formatRegex} eq '%\((?<key>[^)]+)\)s', | |
"Default configuration value: 'formatRegex'" | |
); | |
my %groups = Text::Template->new(template => "test1:%(test1)s test2:%(test2)s")->get_groups(); | |
is_deeply( | |
\%groups, | |
{ test1 => '%(test1)s', test2 => '%(test2)s' }, | |
"Get template capturing groups" | |
); | |
ok( | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s")->format({ test1 => 10 }) eq "test1:10 test2:", | |
"Missing key" | |
); | |
ok( | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s")->format({ test1 => 10, test2 => 20 }) eq "test1:10 test2:20", | |
"All keys" | |
); | |
ok( | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s", config => { defaultValue => "N/A" })->format({ test1 => 10 }) eq "test1:10 test2:N/A", | |
"Default value" | |
); | |
ok( | |
Text::Template->new(template => "test1:{{test1}} test2:{{test2}}", config => { formatRegex => '\{\{(?<key>[^}]+)\}\}' })->format({ test1 => 10 }) eq "test1:10 test2:", | |
"Custom format regex" | |
); | |
eval { | |
Text::Template->new(template => "test1:%(test1)s test2:%(test2)s", config => { throwOnMissingKey => 1 })->format({ test1 => 10 }); | |
fail("Expected an exception to be thrown."); | |
1; | |
} or do { | |
my $eval_error = $@ || "error"; | |
ok($eval_error eq "Missing key 'test2' in the data dictionary.\n", "Expect exception on missing key: $eval_error"); | |
}; | |
done_testing(); | |
} | |
1; |
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
use v5.16; | |
use strict; | |
use warnings; | |
use English qw(-no_match_vars); | |
# Perform a Python-like string formatting using a dictionary of key-value pairs. | |
# The template string should contain placeholders in the form of '%(key)s'. | |
# | |
# Parameters: | |
# $template: The template string to format | |
# $data: The dictionary of key-value pairs to use for the formatting | |
# $config: Optional configuration hash | |
# - throwOnMissingKey: If true, an exception is thrown if a key is not found in the data dictionary | |
# - defaultValue: The default value to use if the key is not found in the data dictionary | |
# - formatRegex: The regex pattern to use for the placeholders | |
# | |
# Returns: | |
# The formatted string | |
# | |
# Example: | |
# my $result = formatter("test1:%(test1)s test2:'%(test2)s'", { test1 => 10 }); | |
# print($result); # Output: test1:10 test2:'' | |
# | |
sub formatter { | |
my ($template, $data, $config) = @_; | |
die "Missing mandatory 'template' parameter (type=scalar).\n" if (!defined $template); | |
die "Missing mandatory 'data' parameter (type=hashref).\n" if (!defined $data); | |
if (ref($data) ne "HASH") { | |
die "Invalid 'data' parameter (type=" . ref($data) . "). Expected type: hashref.\n"; | |
} | |
if (defined $config && ref($config) ne "HASH") { | |
die "Invalid 'config' parameter (type=" . ref($config) . "). Expected type: hashref.\n"; | |
} | |
my $result = $template; | |
$config = { | |
throwOnMissingKey => 0, | |
defaultValue => "", | |
formatRegex => '%\((?<key>[^)]+)\)s', | |
%{ $config // {} }, | |
}; | |
while ($result =~ m/$config->{formatRegex}/g) { | |
# print("Capture groups: $#+\n"); | |
die "Unexpected number of capture groups found in the format regex (expected 1, found $#+).\n" | |
if ($#+ != 1); | |
my $key = $1; | |
if (!exists $data->{$key} && $config->{throwOnMissingKey}) { | |
die "Missing key '$key' in the data dictionary.\n"; | |
} | |
my $value = $data->{$key} // $config->{defaultValue}; | |
substr($result, $-[0], $+[0] - $-[0], $value); | |
} | |
return $result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment