package String::Substitute;

our $VERSION = '0.004';

use Exporter::Easy (
    OK => [qw/get_all_substitutes/],
);
use Regexp::Genex;
use List::Gather;
use Data::Munge qw(elem);
use Params::Validate qw(:all);
use strictures 2;

sub get_all_substitutes {
    my %params = validate(
        @_, {
            string => { type => SCALAR },
            substitutions => { type => HASHREF },
        }
    );


    my @results;
    push @results, $params{string};

    my %subs = %{$params{substitutions}};
    my @substitutable_chars = keys %subs;

    # Build a regex for Regexp::Genex.
    my @regex_parts = gather {
        my @chars = split //, $params{string};
        for my $char (@chars) {
            if (elem $char => \@substitutable_chars) {
                my @char_possible_subs = split(//, $subs{$char});
                my @quoted_literals = map { quotemeta($_) } @char_possible_subs;
                # Build an alternatives group, e.g. (A|B|C), from @quoted_literals
                take sprintf("(%s)", join('|', @quoted_literals));
            }
            else {
                take quotemeta($char);
            }
        }
    };
    my $regex = join '', @regex_parts;

    # NOTE: Regexp::Genex admits to relying on experimental features and avoiding optimizations in the regex engine.
    #       Therefore care should be taken if upgrading the Perl interpreter - make sure you run the tests!
    return Regexp::Genex::strings($regex);
}


1;

# ABSTRACT: generate strings using different combinations of subsitute characters

__END__

=pod

=encoding UTF-8

=head1 NAME

String::Substitute - generate strings using different combinations of subsitute characters

=head1 VERSION

version 0.004

=head1 SYNOPSIS

    use String::Substitute qw(get_all_substitutes);

    my @results = get_all_substitutes(
        string => 'ABC',
        substitutions => {
            A => 'Aa',
            B => 'Bb',
        },
    );

    say for @results;

would print (with nondeterministic order):

    ABC
    aBC
    AbC
    abC

As a one-liner it might look like this:

    perl -Ilib -MString::Substitute=get_all_substitutes -E 'say for get_all_substitutes(string => "ABC", substitutions => { A => "Aa", B => "Bb" })'

=head1 DESCRIPTION

This module is all about generating strings given an initial string and a set of allowed character substitutions.

=head1 NAME

String::Substitute - generate strings using different combinations of subsitute characters

=head1 EXPORTS

=head2 get_all_substitutes

Return, as a list, all possible strings that can be generated by applying different combinations of substitutions to
the string passed as the 'string' argument.  See SYNOPSIS for an example.

The order in which the different candidates are returned is not guaranteed.

Takes only keyword arguments:

=over 8

=item string

The string you want to apply the substitutions on.

=item substitutions

A HASHREF mapping each substitutable character to a string of different characters that it may be replaced with.

e.g. {A => "Aa", B => "Bb"} means that each occurrence of A will be replaced with an 'A', or an 'a', in the returned substitutions.

=back

=head1 STABILITY

Experimental, mostly because this depends on L<Regex::Genex> which itself admits to relying on experimental or changeable
aspects of the Perl interpreter.

Tested on perl 5.20.3 - be sure to run the tests on newer versions if you will be relying on this

=head1 SUPPORT

If you require assistance, support, or further development of this software, please contact OpusVL using the details below:

Telephone: +44 (0)1788 298 410

Email: community@opusvl.com

Web: http://opusvl.com

=head1 AUTHOR

Nicholas Booker <nick.booker@opusvl.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by OpusVL - www.opusvl.com.

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

=cut
