summaryrefslogtreecommitdiff
path: root/Master/tlpkg/tlperl0/lib/Hash/Util.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Master/tlpkg/tlperl0/lib/Hash/Util.pm')
-rwxr-xr-xMaster/tlpkg/tlperl0/lib/Hash/Util.pm520
1 files changed, 520 insertions, 0 deletions
diff --git a/Master/tlpkg/tlperl0/lib/Hash/Util.pm b/Master/tlpkg/tlperl0/lib/Hash/Util.pm
new file mode 100755
index 00000000000..0fa14690243
--- /dev/null
+++ b/Master/tlpkg/tlperl0/lib/Hash/Util.pm
@@ -0,0 +1,520 @@
+package Hash::Util;
+
+require 5.007003;
+use strict;
+use Carp;
+use warnings;
+use warnings::register;
+use Scalar::Util qw(reftype);
+
+require Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(
+ fieldhash fieldhashes
+
+ all_keys
+ lock_keys unlock_keys
+ lock_value unlock_value
+ lock_hash unlock_hash
+ lock_keys_plus hash_locked
+ hidden_keys legal_keys
+
+ lock_ref_keys unlock_ref_keys
+ lock_ref_value unlock_ref_value
+ lock_hashref unlock_hashref
+ lock_ref_keys_plus hashref_locked
+ hidden_ref_keys legal_ref_keys
+
+ hash_seed hv_store
+
+ );
+our $VERSION = 0.07;
+require DynaLoader;
+local @ISA = qw(DynaLoader);
+bootstrap Hash::Util $VERSION;
+
+sub import {
+ my $class = shift;
+ if ( grep /fieldhash/, @_ ) {
+ require Hash::Util::FieldHash;
+ Hash::Util::FieldHash->import(':all'); # for re-export
+ }
+ unshift @_, $class;
+ goto &Exporter::import;
+}
+
+
+=head1 NAME
+
+Hash::Util - A selection of general-utility hash subroutines
+
+=head1 SYNOPSIS
+
+ # Restricted hashes
+
+ use Hash::Util qw(
+ hash_seed all_keys
+ lock_keys unlock_keys
+ lock_value unlock_value
+ lock_hash unlock_hash
+ lock_keys_plus hash_locked
+ hidden_keys legal_keys
+ );
+
+ %hash = (foo => 42, bar => 23);
+ # Ways to restrict a hash
+ lock_keys(%hash);
+ lock_keys(%hash, @keyset);
+ lock_keys_plus(%hash, @additional_keys);
+
+ # Ways to inspect the properties of a restricted hash
+ my @legal = legal_keys(%hash);
+ my @hidden = hidden_keys(%hash);
+ my $ref = all_keys(%hash,@keys,@hidden);
+ my $is_locked = hash_locked(%hash);
+
+ # Remove restrictions on the hash
+ unlock_keys(%hash);
+
+ # Lock individual values in a hash
+ lock_value (%hash, 'foo');
+ unlock_value(%hash, 'foo');
+
+ # Ways to change the restrictions on both keys and values
+ lock_hash (%hash);
+ unlock_hash(%hash);
+
+ my $hashes_are_randomised = hash_seed() != 0;
+
+=head1 DESCRIPTION
+
+C<Hash::Util> and C<Hash::Util::FieldHash> contain special functions
+for manipulating hashes that don't really warrant a keyword.
+
+C<Hash::Util> contains a set of functions that support
+L<restricted hashes|/"Restricted hashes">. These are described in
+this document. C<Hash::Util::FieldHash> contains an (unrelated)
+set of functions that support the use of hashes in
+I<inside-out classes>, described in L<Hash::Util::FieldHash>.
+
+By default C<Hash::Util> does not export anything.
+
+=head2 Restricted hashes
+
+5.8.0 introduces the ability to restrict a hash to a certain set of
+keys. No keys outside of this set can be added. It also introduces
+the ability to lock an individual key so it cannot be deleted and the
+ability to ensure that an individual value cannot be changed.
+
+This is intended to largely replace the deprecated pseudo-hashes.
+
+=over 4
+
+=item B<lock_keys>
+
+=item B<unlock_keys>
+
+ lock_keys(%hash);
+ lock_keys(%hash, @keys);
+
+Restricts the given %hash's set of keys to @keys. If @keys is not
+given it restricts it to its current keyset. No more keys can be
+added. delete() and exists() will still work, but will not alter
+the set of allowed keys. B<Note>: the current implementation prevents
+the hash from being bless()ed while it is in a locked state. Any attempt
+to do so will raise an exception. Of course you can still bless()
+the hash before you call lock_keys() so this shouldn't be a problem.
+
+ unlock_keys(%hash);
+
+Removes the restriction on the %hash's keyset.
+
+B<Note> that if any of the values of the hash have been locked they will not be unlocked
+after this sub executes.
+
+Both routines return a reference to the hash operated on.
+
+=cut
+
+sub lock_ref_keys {
+ my($hash, @keys) = @_;
+
+ Internals::hv_clear_placeholders %$hash;
+ if( @keys ) {
+ my %keys = map { ($_ => 1) } @keys;
+ my %original_keys = map { ($_ => 1) } keys %$hash;
+ foreach my $k (keys %original_keys) {
+ croak "Hash has key '$k' which is not in the new key set"
+ unless $keys{$k};
+ }
+
+ foreach my $k (@keys) {
+ $hash->{$k} = undef unless exists $hash->{$k};
+ }
+ Internals::SvREADONLY %$hash, 1;
+
+ foreach my $k (@keys) {
+ delete $hash->{$k} unless $original_keys{$k};
+ }
+ }
+ else {
+ Internals::SvREADONLY %$hash, 1;
+ }
+
+ return $hash;
+}
+
+sub unlock_ref_keys {
+ my $hash = shift;
+
+ Internals::SvREADONLY %$hash, 0;
+ return $hash;
+}
+
+sub lock_keys (\%;@) { lock_ref_keys(@_) }
+sub unlock_keys (\%) { unlock_ref_keys(@_) }
+
+=item B<lock_keys_plus>
+
+ lock_keys_plus(%hash,@additional_keys)
+
+Similar to C<lock_keys()>, with the difference being that the optional key list
+specifies keys that may or may not be already in the hash. Essentially this is
+an easier way to say
+
+ lock_keys(%hash,@additional_keys,keys %hash);
+
+Returns a reference to %hash
+
+=cut
+
+
+sub lock_ref_keys_plus {
+ my ($hash,@keys)=@_;
+ my @delete;
+ Internals::hv_clear_placeholders(%$hash);
+ foreach my $key (@keys) {
+ unless (exists($hash->{$key})) {
+ $hash->{$key}=undef;
+ push @delete,$key;
+ }
+ }
+ Internals::SvREADONLY(%$hash,1);
+ delete @{$hash}{@delete};
+ return $hash
+}
+
+sub lock_keys_plus(\%;@) { lock_ref_keys_plus(@_) }
+
+
+=item B<lock_value>
+
+=item B<unlock_value>
+
+ lock_value (%hash, $key);
+ unlock_value(%hash, $key);
+
+Locks and unlocks the value for an individual key of a hash. The value of a
+locked key cannot be changed.
+
+Unless %hash has already been locked the key/value could be deleted
+regardless of this setting.
+
+Returns a reference to the %hash.
+
+=cut
+
+sub lock_ref_value {
+ my($hash, $key) = @_;
+ # I'm doubtful about this warning, as it seems not to be true.
+ # Marking a value in the hash as RO is useful, regardless
+ # of the status of the hash itself.
+ carp "Cannot usefully lock values in an unlocked hash"
+ if !Internals::SvREADONLY(%$hash) && warnings::enabled;
+ Internals::SvREADONLY $hash->{$key}, 1;
+ return $hash
+}
+
+sub unlock_ref_value {
+ my($hash, $key) = @_;
+ Internals::SvREADONLY $hash->{$key}, 0;
+ return $hash
+}
+
+sub lock_value (\%$) { lock_ref_value(@_) }
+sub unlock_value (\%$) { unlock_ref_value(@_) }
+
+
+=item B<lock_hash>
+
+=item B<unlock_hash>
+
+ lock_hash(%hash);
+
+lock_hash() locks an entire hash, making all keys and values read-only.
+No value can be changed, no keys can be added or deleted.
+
+ unlock_hash(%hash);
+
+unlock_hash() does the opposite of lock_hash(). All keys and values
+are made writable. All values can be changed and keys can be added
+and deleted.
+
+Returns a reference to the %hash.
+
+=cut
+
+sub lock_hashref {
+ my $hash = shift;
+
+ lock_ref_keys($hash);
+
+ foreach my $value (values %$hash) {
+ Internals::SvREADONLY($value,1);
+ }
+
+ return $hash;
+}
+
+sub unlock_hashref {
+ my $hash = shift;
+
+ foreach my $value (values %$hash) {
+ Internals::SvREADONLY($value, 0);
+ }
+
+ unlock_ref_keys($hash);
+
+ return $hash;
+}
+
+sub lock_hash (\%) { lock_hashref(@_) }
+sub unlock_hash (\%) { unlock_hashref(@_) }
+
+=item B<lock_hash_recurse>
+
+=item B<unlock_hash_recurse>
+
+ lock_hash_recurse(%hash);
+
+lock_hash() locks an entire hash and any hashes it references recursively,
+making all keys and values read-only. No value can be changed, no keys can
+be added or deleted.
+
+B<Only> recurses into hashes that are referenced by another hash. Thus a
+Hash of Hashes (HoH) will all be restricted, but a Hash of Arrays of Hashes
+(HoAoH) will only have the top hash restricted.
+
+ unlock_hash_recurse(%hash);
+
+unlock_hash_recurse() does the opposite of lock_hash_recurse(). All keys and
+values are made writable. All values can be changed and keys can be added
+and deleted. Identical recursion restrictions apply as to lock_hash_recurse().
+
+Returns a reference to the %hash.
+
+=cut
+
+sub lock_hashref_recurse {
+ my $hash = shift;
+
+ lock_ref_keys($hash);
+ foreach my $value (values %$hash) {
+ if (reftype($value) eq 'HASH') {
+ lock_hashref_recurse($value);
+ }
+ Internals::SvREADONLY($value,1);
+ }
+ return $hash
+}
+
+sub unlock_hashref_recurse {
+ my $hash = shift;
+
+ foreach my $value (values %$hash) {
+ if (reftype($value) eq 'HASH') {
+ unlock_hashref_recurse($value);
+ }
+ Internals::SvREADONLY($value,1);
+ }
+ unlock_ref_keys($hash);
+ return $hash;
+}
+
+sub lock_hash_recurse (\%) { lock_hashref_recurse(@_) }
+sub unlock_hash_recurse (\%) { unlock_hashref_recurse(@_) }
+
+
+=item B<hash_unlocked>
+
+ hash_unlocked(%hash) and print "Hash is unlocked!\n";
+
+Returns true if the hash and its keys are unlocked.
+
+=cut
+
+sub hashref_unlocked {
+ my $hash=shift;
+ return Internals::SvREADONLY($hash)
+}
+
+sub hash_unlocked(\%) { hashref_unlocked(@_) }
+
+=for demerphqs_editor
+sub legal_ref_keys{}
+sub hidden_ref_keys{}
+sub all_keys{}
+
+=cut
+
+sub legal_keys(\%) { legal_ref_keys(@_) }
+sub hidden_keys(\%){ hidden_ref_keys(@_) }
+
+=item B<legal_keys>
+
+ my @keys = legal_keys(%hash);
+
+Returns the list of the keys that are legal in a restricted hash.
+In the case of an unrestricted hash this is identical to calling
+keys(%hash).
+
+=item B<hidden_keys>
+
+ my @keys = hidden_keys(%hash);
+
+Returns the list of the keys that are legal in a restricted hash but
+do not have a value associated to them. Thus if 'foo' is a
+"hidden" key of the %hash it will return false for both C<defined>
+and C<exists> tests.
+
+In the case of an unrestricted hash this will return an empty list.
+
+B<NOTE> this is an experimental feature that is heavily dependent
+on the current implementation of restricted hashes. Should the
+implementation change, this routine may become meaningless, in which
+case it will return an empty list.
+
+=item B<all_keys>
+
+ all_keys(%hash,@keys,@hidden);
+
+Populates the arrays @keys with the all the keys that would pass
+an C<exists> tests, and populates @hidden with the remaining legal
+keys that have not been utilized.
+
+Returns a reference to the hash.
+
+In the case of an unrestricted hash this will be equivalent to
+
+ $ref = do {
+ @keys = keys %hash;
+ @hidden = ();
+ \%hash
+ };
+
+B<NOTE> this is an experimental feature that is heavily dependent
+on the current implementation of restricted hashes. Should the
+implementation change this routine may become meaningless in which
+case it will behave identically to how it would behave on an
+unrestricted hash.
+
+=item B<hash_seed>
+
+ my $hash_seed = hash_seed();
+
+hash_seed() returns the seed number used to randomise hash ordering.
+Zero means the "traditional" random hash ordering, non-zero means the
+new even more random hash ordering introduced in Perl 5.8.1.
+
+B<Note that the hash seed is sensitive information>: by knowing it one
+can craft a denial-of-service attack against Perl code, even remotely,
+see L<perlsec/"Algorithmic Complexity Attacks"> for more information.
+B<Do not disclose the hash seed> to people who don't need to know it.
+See also L<perlrun/PERL_HASH_SEED_DEBUG>.
+
+=cut
+
+sub hash_seed () {
+ Internals::rehash_seed();
+}
+
+=item B<hv_store>
+
+ my $sv = 0;
+ hv_store(%hash,$key,$sv) or die "Failed to alias!";
+ $hash{$key} = 1;
+ print $sv; # prints 1
+
+Stores an alias to a variable in a hash instead of copying the value.
+
+=back
+
+=head2 Operating on references to hashes.
+
+Most subroutines documented in this module have equivalent versions
+that operate on references to hashes instead of native hashes.
+The following is a list of these subs. They are identical except
+in name and in that instead of taking a %hash they take a $hashref,
+and additionally are not prototyped.
+
+=over 4
+
+=item lock_ref_keys
+
+=item unlock_ref_keys
+
+=item lock_ref_keys_plus
+
+=item lock_ref_value
+
+=item unlock_ref_value
+
+=item lock_hashref
+
+=item unlock_hashref
+
+=item lock_hashref_recurse
+
+=item unlock_hashref_recurse
+
+=item hash_ref_unlocked
+
+=item legal_ref_keys
+
+=item hidden_ref_keys
+
+=back
+
+=head1 CAVEATS
+
+Note that the trapping of the restricted operations is not atomic:
+for example
+
+ eval { %hash = (illegal_key => 1) }
+
+leaves the C<%hash> empty rather than with its original contents.
+
+=head1 BUGS
+
+The interface exposed by this module is very close to the current
+implementation of restricted hashes. Over time it is expected that
+this behavior will be extended and the interface abstracted further.
+
+=head1 AUTHOR
+
+Michael G Schwern <schwern@pobox.com> on top of code by Nick
+Ing-Simmons and Jeffrey Friedl.
+
+hv_store() is from Array::RefElem, Copyright 2000 Gisle Aas.
+
+Additional code by Yves Orton.
+
+=head1 SEE ALSO
+
+L<Scalar::Util>, L<List::Util> and L<perlsec/"Algorithmic Complexity Attacks">.
+
+L<Hash::Util::FieldHash>.
+
+=cut
+
+1;