package ACME::AutoRedact; use strict; use warnings; # VERSION use Scope::Upper qw(localize :words); use Scalar::Util qw(blessed); # ABSTRACT: string-like object that can redact its value out of derived strings =head1 SYNOPSIS my $password_redacted = ACME::AutoRedact->new($password); my $complicated_string = "some text with $password_redacted"; # this gets the password do_thing_with($complicated_string); # this gets asterisks { ACME::AutoRedact->redact; log($complicated_string) } =cut use overload q{""} => \&stringify, q{.} => \&concat, fallback => 1, ; sub _build_redacted { my ($self) = @_; return q{*} x length($self->{value}); } =attr C The "revealed" string value of this object. =attr C The "redacted" string value of this object. Defaults to a sequence of C<*> as long as the L. =attr C Either C or C (the default). What the object should do when stringifying, absent any specific directive (see L<< /C >> and L<< /C >>). =method C ACME::AutoRedact->new($string); ACME::AutoRedact->new({ value => $string }); ACME::AutoRedact->new({ value => $string, redacted => $other_string, default_behaviour => 'redact', }); Constructs a new object; the L version defaults to a sequence of C<*> as long as the L, the L is "reveal". =cut sub new { my ($class,@args) = @_; my $self; if (@args==1 and not ref $args[0]) { $self = { value => $args[0] }; } elsif (@args==1) { $self = $args[0]; } else { $self = { @args }; } bless $self,$class; $self->{redacted} //= $self->_build_redacted; return $self; } our $requested_behaviour; ## no critic(ProhibitPackageVars) =method C ACME::AutoRedact->redact; This sets (locally to the scope in which it's called) the L to be "redact". =cut sub redact { localize *requested_behaviour, \'redact', UP; } =method C ACME::AutoRedact->reveal; This sets (locally to the scope in which it's called) the L to be "reveal". =cut sub reveal { localize *requested_behaviour, \'reveal', UP; } =method C Overloaded stringification method. Returns either the normal L, or the L one, depending on the current behaviour. =cut sub stringify { my ($self) = @_; my $behaviour = $requested_behaviour // $self->{default_behaviour} // 'reveal'; if ($behaviour eq 'reveal') { return $self->{value}; } else { return $self->{redacted}; } } =method C Overloaded concatenation method. Returns a new object with both normal L and L value being the (appropriate) concatenation of the arguments. =cut sub concat { my ($self,$other,$swap) = @_; my %new; if ((blessed($other)//'') eq __PACKAGE__) { ($self,$other) = ($other,$self) if $swap; $new{value} = $self->{value} . $other->{value}; $new{redacted} = $self->{redacted} . $other->{redacted}; $new{default_behaviour} = $self->{default_behaviour}; } else { for my $k (qw(value redacted)) { $new{$k} = $swap ? "$other" . $self->{$k} : $self->{$k} . "$other"; } $new{default_behaviour} = $self->{default_behaviour}; } return ref($self)->new(\%new); } 1;