diff --git a/bin/pt-archiver b/bin/pt-archiver index 54aa0158..287aa4af 100755 --- a/bin/pt-archiver +++ b/bin/pt-archiver @@ -14,8 +14,12 @@ use warnings FATAL => 'all'; BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo OptionParser - Mo TableParser DSNParser VersionParser @@ -47,6 +51,639 @@ our $VERSION = '2.1.8'; # End Percona::Toolkit package # ########################################################################### +# ########################################################################### +# Lmo::Utils package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + +BEGIN { + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} + +{ + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && looks_like_number($_[0]) && $_[0] == 1) }, + Num => sub { defined $_[0] && looks_like_number($_[0]) }, + Int => sub { defined $_[0] && looks_like_number($_[0]) && $_[0] == int($_[0]) }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && blessed($_[0]) }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} + +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + +my %export_for; +sub import { + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; + } + + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } + } + goto &$original_method + }; + } + + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + _install_coderef "${caller}::$attribute" => $method; + + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } + + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } +} + +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +sub _set_package_isa { + my ($package, @new_isa) = @_; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; +} + +sub _set_inherited_metadata { + my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); + %new_metadata = ( + %new_metadata, + %$isa_metadata, + ); + } + %$class_metadata = %new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +} +1; +} +# ########################################################################### +# End Lmo package +# ########################################################################### + # ########################################################################### # OptionParser package # This package is a copy without comments from the original. The original @@ -1072,457 +1709,6 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### -# ########################################################################### -# Mo package -# This package is a copy without comments from the original. The original -# with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t -# See https://launchpad.net/percona-toolkit for more information. -# ########################################################################### -{ -BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. - -{ - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - sub _stash_for { - return \%{ shift() . "::" }; - } -} - -use strict; -use warnings qw( FATAL all ); - -use Carp (); -use Scalar::Util qw(looks_like_number blessed); - - -our %TYPES = ( - Bool => sub { !$_[0] || (defined $_[0] && looks_like_number($_[0]) && $_[0] == 1) }, - Num => sub { defined $_[0] && looks_like_number($_[0]) }, - Int => sub { defined $_[0] && looks_like_number($_[0]) && $_[0] == int($_[0]) }, - Str => sub { defined $_[0] }, - Object => sub { defined $_[0] && blessed($_[0]) }, - FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, - - map { - my $type = /R/ ? $_ : uc $_; - $_ . "Ref" => sub { ref $_[0] eq $type } - } qw(Array Code Hash Regexp Glob Scalar) -); - -our %metadata_for; -{ - package Mo::Object; - - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); - - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; - } - - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always - } - else { - $ref = { @_ }; - } - return $ref; - } -} - -my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); - - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); - } - } - - return if $exports{M}; - - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; - } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; - - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } - } - }, - %exports, - ); - - $export_for{$caller} = [ keys %exports ]; - - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; - -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) -} - -sub _has_handles { - my ($caller, $attribute, $args) = @_; - my $handles = $args->{handles}; - - my $ref = ref $handles; - my $kv; - if ( $ref eq ref [] ) { - $kv = { map { $_,$_ } @{$handles} }; - } - elsif ( $ref eq ref {} ) { - $kv = $handles; - } - elsif ( $ref eq ref qr// ) { - Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") - unless $args->{isa}; - my $target_class = $args->{isa}; - $kv = { - map { $_, $_ } - grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } - keys %{ _stash_for $target_class } - }; - } - else { - Carp::confess("handles for $ref not yet implemented"); - } - - while ( my ($method, $target) = each %{$kv} ) { - my $name = _glob_for "${caller}::$method"; - Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") - if defined &$name; - - my ($target, @curried_args) = ref($target) ? @$target : $target; - *$name = sub { - my $self = shift; - my $delegate_to = $self->$attribute(); - my $error = "Cannot delegate $method to $target because the value of $attribute"; - Carp::confess("$error is not defined") unless $delegate_to; - Carp::confess("$error is not an object (got '$delegate_to')") - unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); - return $delegate_to->$target(@curried_args, @_); - } - } -} - -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - -sub _set_package_isa { - my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; -} - -sub _set_inherited_metadata { - my $class = shift; - my $linearized_isa = mro::get_linear_isa($class); - my %new_metadata; - - for my $isa_class (reverse @$linearized_isa) { - %new_metadata = ( - %new_metadata, - %{ $metadata_for{$isa_class} || {} }, - ); - } - $metadata_for{$class} = \%new_metadata; -} - -sub unimport { - my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; -} - -sub Dumper { - require Data::Dumper; - local $Data::Dumper::Indent = 0; - local $Data::Dumper::Sortkeys = 0; - local $Data::Dumper::Quotekeys = 0; - local $Data::Dumper::Terse = 1; - - Data::Dumper::Dumper(@_) -} - -BEGIN { - if ($] >= 5.010) { - { local $@; require mro; } - } - else { - local $@; - eval { - require MRO::Compat; - } or do { - *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { - no strict 'refs'; - - my $classname = shift; - - my @lin = ($classname); - my %stored; - foreach my $parent (@{"$classname\::ISA"}) { - my $plin = mro::get_linear_isa_dfs($parent); - foreach (@$plin) { - next if exists $stored{$_}; - push(@lin, $_); - $stored{$_} = 1; - } - } - return \@lin; - }; - } - } -} - -} -1; -} -# ########################################################################### -# End Mo package -# ########################################################################### - # ########################################################################### # TableParser package # This package is a copy without comments from the original. The original @@ -2322,7 +2508,7 @@ sub _d { { package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -2498,7 +2684,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/bin/pt-deadlock-logger b/bin/pt-deadlock-logger index 601fdba2..d20c49e5 100755 --- a/bin/pt-deadlock-logger +++ b/bin/pt-deadlock-logger @@ -15,7 +15,11 @@ BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo VersionParser Quoter DSNParser @@ -1070,18 +1074,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1094,6 +1104,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1115,240 +1313,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1370,8 +1604,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1397,80 +1631,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1517,8 +1703,9 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### + # ########################################################################### # VersionParser package # This package is a copy without comments from the original. The original @@ -1530,7 +1717,7 @@ BEGIN { { package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -1706,7 +1893,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/bin/pt-diskstats b/bin/pt-diskstats index dc5dc822..0e9abd89 100755 --- a/bin/pt-diskstats +++ b/bin/pt-diskstats @@ -20,7 +20,6 @@ BEGIN { Diskstats DiskstatsGroupByAll DiskstatsGroupByDisk - DiskstatsGroupBySample DiskstatsMenu VersionCheck HTTPMicro @@ -2799,190 +2798,6 @@ sub compute_in_progress { # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { - -package DiskstatsGroupBySample; - -use warnings; -use strict; -use English qw(-no_match_vars); -use constant PTDEBUG => $ENV{PTDEBUG} || 0; - -use base qw( Diskstats ); - -use POSIX qw( ceil ); - -sub new { - my ( $class, %args ) = @_; - my $self = $class->SUPER::new(%args); - $self->{_iterations} = 0; - $self->{_save_curr_as_prev} = 0; - return $self; -} - -sub group_by { - my ( $self, %args ) = @_; - my @optional_args = qw( header_callback rows_callback ); - my ( $header_callback, $rows_callback ) = $args{ @optional_args }; - - $self->clear_state() unless $self->interactive(); - - $self->parse_from( - sample_callback => $self->can("_sample_callback"), - filehandle => $args{filehandle}, - filename => $args{filename}, - data => $args{data}, - ); - - return; -} - -sub _sample_callback { - my ( $self, $ts, %args ) = @_; - my $printed_a_line = 0; - - if ( $self->has_stats() ) { - $self->{_iterations}++; - } - - my $elapsed = ($self->curr_ts() || 0) - - ($self->prev_ts() || 0); - - if ( $ts > 0 && ceil($elapsed) >= $self->sample_time() ) { - - $self->print_deltas( - max_device_length => 6, - header_callback => sub { - my ( $self, $header, @args ) = @_; - - if ( $self->force_header() ) { - my $method = $args{header_callback} || "print_header"; - $self->$method( $header, @args ); - $self->set_force_header(undef); - } - }, - rows_callback => sub { - my ( $self, $format, $cols, $stat ) = @_; - my $method = $args{rows_callback} || "print_rows"; - $self->$method( $format, $cols, $stat ); - $printed_a_line = 1; - } - ); - } - if ( $self->{_iterations} == 1 || $printed_a_line == 1 ) { - $self->{_save_curr_as_prev} = 1; - $self->_save_curr_as_prev( $self->stats_for() ); - $self->set_prev_ts_line( $self->curr_ts_line() ); - $self->{_save_curr_as_prev} = 0; - } - return; -} - -sub delta_against { - my ( $self, $dev ) = @_; - return $self->prev_stats_for($dev); -} - -sub ts_line_for_timestamp { - my ($self) = @_; - return $self->prev_ts_line(); -} - -sub delta_against_ts { - my ( $self ) = @_; - return $self->prev_ts(); -} - -sub clear_state { - my ( $self, @args ) = @_; - $self->{_iterations} = 0; - $self->{_save_curr_as_prev} = 0; - $self->SUPER::clear_state(@args); -} - -sub compute_devs_in_group { - my ($self) = @_; - my $stats = $self->stats_for(); - return scalar grep { - $stats->{$_} && $self->_print_device_if($_) - } $self->ordered_devs; -} - -sub compute_dev { - my ( $self, $devs ) = @_; - $devs ||= $self->compute_devs_in_group(); - return "{" . $devs . "}" if $devs > 1; - return (grep { $self->_print_device_if($_) } $self->ordered_devs())[0]; -} - -sub _calc_stats_for_deltas { - my ( $self, $elapsed ) = @_; - - my $delta_for; - - foreach my $dev ( grep { $self->_print_device_if($_) } $self->ordered_devs() ) { - my $curr = $self->stats_for($dev); - my $against = $self->delta_against($dev); - - next unless $curr && $against; - - my $delta = $self->_calc_delta_for( $curr, $against ); - $delta->{ios_in_progress} = $curr->[Diskstats::IOS_IN_PROGRESS]; - while ( my ( $k, $v ) = each %$delta ) { - $delta_for->{$k} += $v; - } - } - - return unless $delta_for && %{$delta_for}; - - my $in_progress = $delta_for->{ios_in_progress}; - my $tot_in_progress = 0; - my $devs_in_group = $self->compute_devs_in_group() || 1; - - my %stats = ( - $self->_calc_read_stats( - delta_for => $delta_for, - elapsed => $elapsed, - devs_in_group => $devs_in_group, - ), - $self->_calc_write_stats( - delta_for => $delta_for, - elapsed => $elapsed, - devs_in_group => $devs_in_group, - ), - in_progress => - $self->compute_in_progress( $in_progress, $tot_in_progress ), - ); - - my %extras = $self->_calc_misc_stats( - delta_for => $delta_for, - elapsed => $elapsed, - devs_in_group => $devs_in_group, - stats => \%stats, - ); - - @stats{ keys %extras } = values %extras; - - $stats{dev} = $self->compute_dev( $devs_in_group ); - - $self->{_first_time_magic} = undef; - if ( @{$self->{_nochange_skips}} ) { - my $devs = join ", ", @{$self->{_nochange_skips}}; - PTDEBUG && _d("Skipping [$devs], haven't changed from the first sample"); - $self->{_nochange_skips} = []; - } - - return \%stats; -} - -sub compute_line_ts { - my ($self, %args) = @_; - if ( $self->show_timestamps() ) { - @args{ qw( first_ts curr_ts ) } = @args{ qw( curr_ts first_ts ) } - } - return $self->SUPER::compute_line_ts(%args); -} - -1; } # ########################################################################### # End DiskstatsGroupBySample package diff --git a/bin/pt-kill b/bin/pt-kill index d6fa3869..abc11d04 100755 --- a/bin/pt-kill +++ b/bin/pt-kill @@ -15,7 +15,11 @@ BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo DSNParser Daemon Transformers @@ -1077,18 +1081,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1101,6 +1111,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1122,240 +1320,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1377,8 +1611,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1404,80 +1638,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1524,7 +1710,7 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### diff --git a/bin/pt-online-schema-change b/bin/pt-online-schema-change index d65a8ee7..5d7f935c 100755 --- a/bin/pt-online-schema-change +++ b/bin/pt-online-schema-change @@ -15,7 +15,11 @@ BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo VersionParser DSNParser Daemon @@ -1084,19 +1088,26 @@ if ( PTDEBUG ) { # End OptionParser package # ########################################################################### + # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1109,6 +1120,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1130,241 +1329,277 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + override => \&override, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - override => \&override, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1386,8 +1621,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1413,80 +1648,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1543,7 +1730,7 @@ sub override { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### @@ -1557,7 +1744,7 @@ sub override { { package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -1733,7 +1920,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/bin/pt-slave-delay b/bin/pt-slave-delay index 2c702d12..c6c0bc5e 100755 --- a/bin/pt-slave-delay +++ b/bin/pt-slave-delay @@ -15,7 +15,11 @@ BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo DSNParser Daemon Transformers @@ -1070,18 +1074,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1094,6 +1104,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1115,240 +1313,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1370,8 +1604,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1397,80 +1631,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1517,7 +1703,7 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### diff --git a/bin/pt-slave-find b/bin/pt-slave-find index 32fa41a6..672a6524 100755 --- a/bin/pt-slave-find +++ b/bin/pt-slave-find @@ -14,7 +14,11 @@ use warnings FATAL => 'all'; BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo DSNParser MasterSlave Daemon @@ -1049,18 +1053,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1073,6 +1083,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1094,240 +1292,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1349,8 +1583,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1376,80 +1610,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1496,7 +1682,7 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### @@ -2823,7 +3009,7 @@ sub _d { { package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -2999,7 +3185,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/bin/pt-slave-restart b/bin/pt-slave-restart index bd04d04a..a0eec3c1 100755 --- a/bin/pt-slave-restart +++ b/bin/pt-slave-restart @@ -16,7 +16,11 @@ BEGIN { Percona::Toolkit Quoter OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo VersionParser DSNParser MasterSlave @@ -1197,18 +1201,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1221,6 +1231,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1242,240 +1440,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1497,8 +1731,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1524,80 +1758,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1644,7 +1830,7 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### @@ -1658,7 +1844,7 @@ BEGIN { { package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -1834,7 +2020,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/bin/pt-table-checksum b/bin/pt-table-checksum index 574a03a4..8feeebd9 100755 --- a/bin/pt-table-checksum +++ b/bin/pt-table-checksum @@ -19,7 +19,11 @@ BEGIN { Pingback DSNParser OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo Cxn Percona::XtraDB::Cluster Quoter @@ -2770,18 +2774,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -2794,6 +2804,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -2815,240 +3013,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -3070,8 +3304,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -3097,80 +3331,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -3217,7 +3403,7 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### @@ -3580,7 +3766,7 @@ sub deserialize_list { { package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -3756,7 +3942,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/bin/pt-table-sync b/bin/pt-table-sync index e27d5713..4a1296b8 100755 --- a/bin/pt-table-sync +++ b/bin/pt-table-sync @@ -15,7 +15,11 @@ BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo Quoter DSNParser VersionParser @@ -1086,18 +1090,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1110,6 +1120,172 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +my %metadata_for; + +sub new { + shift; + return Lmo::Meta::Class->new(@_); +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1131,240 +1307,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1386,8 +1598,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1413,80 +1625,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1533,7 +1697,7 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### diff --git a/bin/pt-variable-advisor b/bin/pt-variable-advisor index 0732fa2c..86236c2e 100755 --- a/bin/pt-variable-advisor +++ b/bin/pt-variable-advisor @@ -15,7 +15,11 @@ BEGIN { $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( Percona::Toolkit OptionParser - Mo + Lmo::Utils + Lmo::Meta + Lmo::Object + Lmo::Types + Lmo DSNParser VersionParser Daemon @@ -1074,18 +1078,24 @@ if ( PTDEBUG ) { # ########################################################################### # ########################################################################### -# Mo package +# Lmo::Utils package # This package is a copy without comments from the original. The original # with comments and its test file can be found in the Bazaar repository at, -# lib/Mo.pm -# t/lib/Mo.t +# lib/Lmo/Utils.pm +# t/lib/Lmo/Utils.t # See https://launchpad.net/percona-toolkit for more information. # ########################################################################### { +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} { no strict 'refs'; @@ -1098,6 +1108,194 @@ our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. } } +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; +} +# ########################################################################### +# End Lmo::Utils package +# ########################################################################### + +# ########################################################################### +# Lmo::Meta package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Meta.pm +# t/lib/Lmo/Meta.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} +# ########################################################################### +# End Lmo::Meta package +# ########################################################################### + +# ########################################################################### +# Lmo::Object package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Object.pm +# t/lib/Lmo/Object.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + $sub->( $self, @args); + } + return $self; +} + +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; +} +# ########################################################################### +# End Lmo::Object package +# ########################################################################### + +# ########################################################################### +# Lmo::Types package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo/Types.pm +# t/lib/Lmo/Types.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +package Lmo::Types; + use strict; use warnings qw( FATAL all ); @@ -1119,240 +1317,276 @@ our %TYPES = ( } qw(Array Code Hash Regexp Glob Scalar) ); -our %metadata_for; -{ - package Mo::Object; +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $inner_types = _nested_constraints($1, $2); + } + else { + $inner_types = $TYPES{$type}; } - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + return 1 if ! defined($value); + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; } - else { - $ref = { @_ }; - } - return $ref; + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); } } +1; +} +# ########################################################################### +# End Lmo::Types package +# ########################################################################### + +# ########################################################################### +# Lmo package +# This package is a copy without comments from the original. The original +# with comments and its test file can be found in the Bazaar repository at, +# lib/Lmo.pm +# t/lib/Lmo.t +# See https://launchpad.net/percona-toolkit for more information. +# ########################################################################### +{ +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + my %export_for; -sub Mo::import { - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); +sub import { + warnings->import(qw(FATAL all)); + strict->import(); - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + confess => \&Carp::confess, + ); + + $export_for{$caller} = \%exports; + + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; } - } - return if $exports{M}; + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; + goto &$original_method + }; + } - $metadata_for{$caller}{$attribute} = (); - - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) } - }, - %exports, - ); + goto &$original_method; + } + } - $export_for{$caller} = [ keys %exports ]; + _install_coderef "${caller}::$attribute" => $method; - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } } sub _has_handles { @@ -1374,8 +1608,8 @@ sub _has_handles { $kv = { map { $_, $_ } grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { $_ ne 'has' && $_ ne 'extends' } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } keys %{ _stash_for $target_class } }; } @@ -1401,80 +1635,32 @@ sub _has_handles { } } -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $inner_types = _nested_constraints($1, $2); - } - else { - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - return 1 if ! defined($value); - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - sub _set_package_isa { my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + @{*$package_isa} = @new_isa; } sub _set_inherited_metadata { my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); my $linearized_isa = mro::get_linear_isa($class); my %new_metadata; for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); %new_metadata = ( %new_metadata, - %{ $metadata_for{$isa_class} || {} }, + %$isa_metadata, ); } - $metadata_for{$class} = \%new_metadata; + %$class_metadata = %new_metadata; } sub unimport { my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); } sub Dumper { @@ -1521,7 +1707,7 @@ BEGIN { 1; } # ########################################################################### -# End Mo package +# End Lmo package # ########################################################################### # ########################################################################### @@ -1912,7 +2098,7 @@ sub _d { { package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -2088,7 +2274,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/lib/Lmo.pm b/lib/Lmo.pm new file mode 100644 index 00000000..c6f688a0 --- /dev/null +++ b/lib/Lmo.pm @@ -0,0 +1,379 @@ +# This program is copyright 2007-2011 Baron Schwartz, 2012 Percona Ireland Ltd. +# Feedback and improvements are welcome. +# +# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar +# systems, you can issue `man perlgpl' or `man perlartistic' to read these +# licenses. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 59 Temple +# Place, Suite 330, Boston, MA 02111-1307 USA. +# ########################################################################### +# Lmo package +# ########################################################################### +# Package: Lmo +# Lmo provides a miniature object system in the style of Moose and Moo. +BEGIN { +$INC{"Lmo.pm"} = __FILE__; +package Lmo; +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. + + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +use Lmo::Meta; +use Lmo::Object; +use Lmo::Types; + +use Lmo::Utils; + +my %export_for; +sub import { + # Set warnings and strict for the caller. + warnings->import(qw(FATAL all)); + strict->import(); + + my $caller = scalar caller(); # Caller's package + my %exports = ( + extends => \&extends, + has => \&has, + with => \&with, + override => \&override, + confess => \&Carp::confess, + ); + + # We keep this so code doing 'no Mo;' actually does a cleanup. + $export_for{$caller} = \%exports; + + # Export has, extends and sosuch. + for my $keyword ( keys %exports ) { + _install_coderef "${caller}::$keyword" => $exports{$keyword}; + } + + # Set up our caller's ISA, unless they already set it manually themselves, + # in which case we assume they know what they are doing. + # XXX weird syntax here because we want to call the classes' extends at + # least once, to avoid warnings. + if ( !@{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] } ) { + @_ = "Lmo::Object"; + goto *{ _glob_for "${caller}::extends" }{CODE}; + } +} + +sub extends { + my $caller = scalar caller(); + for my $class ( @_ ) { + _load_module($class); + } + _set_package_isa($caller, @_); + _set_inherited_metadata($caller); +} + +sub _load_module { + my ($class) = @_; + + # Try loading the class, but don't croak if we fail. + (my $file = $class) =~ s{::|'}{/}g; + $file .= '.pm'; + { local $@; eval { require "$file" } } # or warn $@; + return; +} + +sub with { + my $package = scalar caller(); + require Role::Tiny; + for my $role ( @_ ) { + _load_module($role); + _role_attribute_metadata($package, $role); + } + Role::Tiny->apply_roles_to_package($package, @_); +} + +sub _role_attribute_metadata { + my ($package, $role) = @_; + + my $package_meta = Lmo::Meta->metadata_for($package); + my $role_meta = Lmo::Meta->metadata_for($role); + + # The role metadata always comes first, since it shouldn't redefine + # metadata defined in the class itself. + %$package_meta = (%$role_meta, %$package_meta); +} + +sub has { + my $names = shift; + my $caller = scalar caller(); + + my $class_metadata = Lmo::Meta->metadata_for($caller); + + for my $attribute ( ref $names ? @$names : $names ) { + my %args = @_; + my $method = ($args{is} || '') eq 'ro' + ? sub { + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller}::${attribute}") + if $#_; + return $_[0]{$attribute}; + } + : sub { + return $#_ + ? $_[0]{$attribute} = $_[1] + : $_[0]{$attribute}; + }; + + $class_metadata->{$attribute} = (); + + # isa => Constaint, + if ( my $type_check = $args{isa} ) { + my $check_name = $type_check; + + if ( my ($aggregate_type, $inner_type) = $type_check =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + $type_check = Lmo::Types::_nested_constraints($attribute, $aggregate_type, $inner_type); + } + + my $check_sub = sub { + my ($new_val) = @_; + Lmo::Types::check_type_constaints($attribute, $type_check, $check_name, $new_val); + }; + + $class_metadata->{$attribute}{isa} = [$check_name, $check_sub]; + my $orig_method = $method; + $method = sub { + $check_sub->($_[1]) if $#_; + goto &$orig_method; + }; + } + + # XXX TODO: Inline builder and default into the actual method, for speed. + # builder => '_builder_method', + if ( my $builder = $args{builder} ) { + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$builder + : goto &$original_method + }; + } + + # default => CodeRef, + if ( my $code = $args{default} ) { + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") + unless ref($code) eq 'CODE'; + my $original_method = $method; + $method = sub { + $#_ + ? goto &$original_method + : ! exists $_[0]{$attribute} + ? $_[0]{$attribute} = $_[0]->$code + : goto &$original_method + }; + } + + # does => 'Role', + if ( my $role = $args{does} ) { + my $original_method = $method; + $method = sub { + if ( $#_ ) { + Carp::confess(qq) + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } + } + goto &$original_method + }; + } + + # coerce => CodeRef, + if ( my $coercion = $args{coerce} ) { + $class_metadata->{$attribute}{coerce} = $coercion; + my $original_method = $method; + $method = sub { + if ( $#_ ) { + return $original_method->($_[0], $coercion->($_[1])) + } + goto &$original_method; + } + } + + # Actually put the attribute's accessor in the class + _install_coderef "${caller}::$attribute" => $method; + + if ( $args{required} ) { + $class_metadata->{$attribute}{required} = 1; + } + + if ($args{clearer}) { + _install_coderef "${caller}::$args{clearer}" + => sub { delete shift->{$attribute} } + } + + if ($args{predicate}) { + _install_coderef "${caller}::$args{predicate}" + => sub { exists shift->{$attribute} } + } + + if ($args{handles}) { + _has_handles($caller, $attribute, \%args); + } + + if (exists $args{init_arg}) { + $class_metadata->{$attribute}{init_arg} = $args{init_arg}; + } + } +} + +# handles handles +sub _has_handles { + my ($caller, $attribute, $args) = @_; + my $handles = $args->{handles}; + + my $ref = ref $handles; + my $kv; + if ( $ref eq ref [] ) { + # handles => [ ... list of methods ... ], + $kv = { map { $_,$_ } @{$handles} }; + } + elsif ( $ref eq ref {} ) { + # handles => { 'method_to_install' => 'original_method' | [ 'original_method', ... curried arguments ... ], }, + $kv = $handles; + } + elsif ( $ref eq ref qr// ) { + # handles => qr/PAT/, + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") + unless $args->{isa}; + my $target_class = $args->{isa}; + $kv = { + map { $_, $_ } + grep { $_ =~ $handles } + grep { !exists $Lmo::Object::{$_} && $target_class->can($_) } + grep { !$export_for{$target_class}->{$_} } + keys %{ _stash_for $target_class } + }; + } + else { + Carp::confess("handles for $ref not yet implemented"); + } + + while ( my ($method, $target) = each %{$kv} ) { + my $name = _glob_for "${caller}::$method"; + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") + if defined &$name; + + # If we have an arrayref, they are currying some arguments. + my ($target, @curried_args) = ref($target) ? @$target : $target; + *$name = sub { + my $self = shift; + my $delegate_to = $self->$attribute(); + my $error = "Cannot delegate $method to $target because the value of $attribute"; + Carp::confess("$error is not defined") unless $delegate_to; + Carp::confess("$error is not an object (got '$delegate_to')") + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); + return $delegate_to->$target(@curried_args, @_); + } + } +} + +# Sets a package's @ISA to the list passed in. Overwrites any previous values. +sub _set_package_isa { + my ($package, @new_isa) = @_; + my $package_isa = \*{ _glob_for "${package}::ISA" }; + # This somewhat weirder syntax is here to work around a Perl 5.10.0 bug; + # For whatever reason, some other variants weren't setting ISA. + @{*$package_isa} = @new_isa; +} + +# Each class has its own metadata. When a class inhyerits attributes, +# it should also inherit the attribute metadata. +sub _set_inherited_metadata { + my $class = shift; + my $class_metadata = Lmo::Meta->metadata_for($class); + my $linearized_isa = mro::get_linear_isa($class); + my %new_metadata; + + # Walk @ISA in reverse, grabbing the metadata for each + # class. Attributes with the same name defined in more + # specific classes override their parent's attributes. + for my $isa_class (reverse @$linearized_isa) { + my $isa_metadata = Lmo::Meta->metadata_for($isa_class); + %new_metadata = ( + %new_metadata, + %$isa_metadata, + ); + } + %$class_metadata = %new_metadata; +} + +sub unimport { + my $caller = scalar caller(); + my $target = caller; + _unimport_coderefs($target, keys %{$export_for{$caller}}); +} + +sub Dumper { + require Data::Dumper; + local $Data::Dumper::Indent = 0; + local $Data::Dumper::Sortkeys = 0; + local $Data::Dumper::Quotekeys = 0; + local $Data::Dumper::Terse = 1; + + Data::Dumper::Dumper(@_) +} + +BEGIN { + # mro is the method resolution order. The module itself is core in + # recent Perls; In older Perls it's available from MRO::Compat from + # CPAN, and in case that isn't available to us, we inline the barest + # funcionality. + if ($] >= 5.010) { + { local $@; require mro; } + } + else { + local $@; + eval { + require MRO::Compat; + } or do { + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { + no strict 'refs'; + + my $classname = shift; + + my @lin = ($classname); + my %stored; + foreach my $parent (@{"$classname\::ISA"}) { + my $plin = mro::get_linear_isa_dfs($parent); + foreach (@$plin) { + next if exists $stored{$_}; + push(@lin, $_); + $stored{$_} = 1; + } + } + return \@lin; + }; + } + } +} + +sub override { + my ($methods, $code) = @_; + my $caller = scalar caller; + + for my $method ( ref($methods) ? @$methods : $methods ) { + my $full_method = "${caller}::${method}"; + *{_glob_for $full_method} = $code; + } +} + +} +1; +# ########################################################################### +# End Lmo package +# ########################################################################### diff --git a/lib/Lmo/Meta.pm b/lib/Lmo/Meta.pm new file mode 100644 index 00000000..ee2aeb75 --- /dev/null +++ b/lib/Lmo/Meta.pm @@ -0,0 +1,45 @@ +{ +package Lmo::Meta; +use strict; +use warnings qw( FATAL all ); + +my %metadata_for; + +sub new { + my $class = shift; + return bless { @_ }, $class +} + +sub metadata_for { + my $self = shift; + my ($class) = @_; + + return $metadata_for{$class} ||= {}; +} + +sub class { shift->{class} } + +sub attributes { + my $self = shift; + return keys %{$self->metadata_for($self->class)} +} + +sub attributes_for_new { + my $self = shift; + my @attributes; + + my $class_metadata = $self->metadata_for($self->class); + while ( my ($attr, $meta) = each %$class_metadata ) { + if ( exists $meta->{init_arg} ) { + push @attributes, $meta->{init_arg} + if defined $meta->{init_arg}; + } + else { + push @attributes, $attr; + } + } + return @attributes; +} + +1; +} \ No newline at end of file diff --git a/lib/Lmo/Object.pm b/lib/Lmo/Object.pm new file mode 100644 index 00000000..2f587172 --- /dev/null +++ b/lib/Lmo/Object.pm @@ -0,0 +1,97 @@ +# Mo::Object is the parent of every Mo-derived object. Here's where new +# and BUILDARGS gets inherited from. +package Lmo::Object; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(blessed); + +use Lmo::Meta; +use Lmo::Utils qw(_glob_for); + +sub new { + my $class = shift; + my $args = $class->BUILDARGS(@_); + + my $class_metadata = Lmo::Meta->metadata_for($class); + + my @args_to_delete; + while ( my ($attr, $meta) = each %$class_metadata ) { + next unless exists $meta->{init_arg}; + my $init_arg = $meta->{init_arg}; + + # If init_arg is defined, then we + if ( defined $init_arg ) { + $args->{$attr} = delete $args->{$init_arg}; + } + else { + push @args_to_delete, $attr; + } + } + + delete $args->{$_} for @args_to_delete; + + for my $attribute ( keys %$args ) { + # coerce + if ( my $coerce = $class_metadata->{$attribute}{coerce} ) { + $args->{$attribute} = $coerce->($args->{$attribute}); + } + # isa + if ( my $isa_check = $class_metadata->{$attribute}{isa} ) { + my ($check_name, $check_sub) = @$isa_check; + $check_sub->($args->{$attribute}); + } + } + + while ( my ($attribute, $meta) = each %$class_metadata ) { + next unless $meta->{required}; + Carp::confess("Attribute ($attribute) is required for $class") + if ! exists $args->{$attribute} + } + + my $self = bless $args, $class; + + # BUILD + my @build_subs; + my $linearized_isa = mro::get_linear_isa($class); + + for my $isa_class ( @$linearized_isa ) { + unshift @build_subs, *{ _glob_for "${isa_class}::BUILD" }{CODE}; + } + # If &class::BUILD exists, for every class in + # the linearized ISA, call it. + # XXX I _think_ that this uses exists correctly, since + # a class could define a stub for BUILD and then AUTOLOAD + # the body. Should check what Moose does. + my @args = %$args; + for my $sub (grep { defined($_) && exists &$_ } @build_subs) { + # @args must be defined outside of this loop, + # as changes to @_ in one BUILD should propagate to another + $sub->( $self, @args); + } + return $self; +} + +# Base BUILDARGS. +sub BUILDARGS { + shift; # No need for the classname + if ( @_ == 1 && ref($_[0]) ) { + Carp::confess("Single parameters to new() must be a HASH ref, not $_[0]") + unless ref($_[0]) eq ref({}); + return {%{$_[0]}} # We want a new reference, always + } + else { + return { @_ }; + } +} + +sub meta { + my $class = shift; + $class = Scalar::Util::blessed($class) || $class; + return Lmo::Meta->new(class => $class); +} + + +1; diff --git a/lib/Lmo/Role.pm b/lib/Lmo/Role.pm new file mode 100644 index 00000000..411f7da3 --- /dev/null +++ b/lib/Lmo/Role.pm @@ -0,0 +1,72 @@ +package Lmo::Role; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use constant PTDEBUG => $ENV{PTDEBUG} || 0; + +use Lmo (); +use base qw(Role::Tiny); + +use Lmo::Utils qw(_install_coderef _unimport_coderefs _stash_for); + +BEGIN { *INFO = \%Role::Tiny::INFO } + +our %INFO; + +sub _install_tracked { + my ($target, $name, $code) = @_; + $INFO{$target}{exports}{$name} = $code; + _install_coderef "${target}::${name}" => $code; +} + +sub import { + my $target = caller; + my ($me) = @_; + # Set warnings and strict for the caller. + warnings->import(qw(FATAL all)); + strict->import(); + +=begin + if ($Moo::MAKERS{$target} and $Moo::MAKERS{$target}{is_class}) { + die "Cannot import Moo::Role into a Moo class"; + } +=cut + return if $INFO{$target}; # already exported into this package + $INFO{$target} = { is_role => 1 }; + # get symbol table reference_unimport_coderefs + my $stash = _stash_for $target; + + _install_tracked $target => has => \*Lmo::has; + + # install before/after/around subs + foreach my $type (qw(before after around)) { + _install_tracked $target => $type => sub { + require Class::Method::Modifiers; + push @{$INFO{$target}{modifiers}||=[]}, [ $type => @_ ]; + }; + } + + _install_tracked $target => requires => sub { + push @{$INFO{$target}{requires}||=[]}, @_; + }; + + _install_tracked $target => with => \*Lmo::with; + + # grab all *non-constant* (stash slot is not a scalarref) subs present + # in the symbol table and store their refaddrs (no need to forcibly + # inflate constant subs into real subs) - also add '' to here (this + # is used later) with a map to the coderefs in case of copying or re-use + my @not_methods = ('', map { *$_{CODE}||() } grep !ref($_), values %$stash); + @{$INFO{$target}{not_methods}={}}{@not_methods} = @not_methods; + # a role does itself + $Role::Tiny::APPLIED_TO{$target} = { $target => undef }; + +} + +sub unimport { + my $target = caller; + _unimport_coderefs($target, keys %{$INFO{$target}{exports}}); +} + +1; diff --git a/lib/Lmo/Types.pm b/lib/Lmo/Types.pm new file mode 100644 index 00000000..12d8b540 --- /dev/null +++ b/lib/Lmo/Types.pm @@ -0,0 +1,98 @@ +package Lmo::Types; + +use strict; +use warnings qw( FATAL all ); + +use Carp (); +use Scalar::Util qw(looks_like_number blessed); + +# Basic types for isa. If you want a new type, either add it here, +# or give isa a coderef. + +our %TYPES = ( + Bool => sub { !$_[0] || (defined $_[0] && looks_like_number($_[0]) && $_[0] == 1) }, + Num => sub { defined $_[0] && looks_like_number($_[0]) }, + Int => sub { defined $_[0] && looks_like_number($_[0]) && $_[0] == int($_[0]) }, + Str => sub { defined $_[0] }, + Object => sub { defined $_[0] && blessed($_[0]) }, + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, + + map { + my $type = /R/ ? $_ : uc $_; + $_ . "Ref" => sub { ref $_[0] eq $type } + } qw(Array Code Hash Regexp Glob Scalar) +); + +sub check_type_constaints { + my ($attribute, $type_check, $check_name, $val) = @_; + ( ref($type_check) eq 'CODE' + ? $type_check->($val) + : (ref $val eq $type_check + || ($val && $val eq $type_check) + || (exists $TYPES{$type_check} && $TYPES{$type_check}->($val))) + ) + || Carp::confess( + qq + . qq + . (defined $val ? Lmo::Dumper($val) : 'undef') ) +} + +# Nested (or parametized) constraints look like this: ArrayRef[CONSTRAINT] or +# Maybe[CONSTRAINT]. This function returns a coderef that implements one of +# these. +sub _nested_constraints { + my ($attribute, $aggregate_type, $type) = @_; + + my $inner_types; + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { + # If the inner constraint -- the part within brackets -- is also a parametized + # constraint, then call this function recursively. + $inner_types = _nested_constraints($1, $2); + } + else { + # Otherwise, try checking if it's one of the built-in types. + $inner_types = $TYPES{$type}; + } + + if ( $aggregate_type eq 'ArrayRef' ) { + return sub { + my ($val) = @_; + return unless ref($val) eq ref([]); + + if ($inner_types) { + for my $value ( @{$val} ) { + return unless $inner_types->($value) + } + } + else { + # $inner_types isn't set, we are dealing with a class name. + for my $value ( @{$val} ) { + return unless $value && ($value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type))); + } + } + return 1; + }; + } + elsif ( $aggregate_type eq 'Maybe' ) { + return sub { + my ($value) = @_; + # For Maybe, undef is valid + return 1 if ! defined($value); + # Otherwise, defer to the inner type + if ($inner_types) { + return unless $inner_types->($value) + } + else { + return unless $value eq $type + || (Scalar::Util::blessed($value) && $value->isa($type)); + } + return 1; + } + } + else { + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); + } +} + +1; diff --git a/lib/Lmo/Utils.pm b/lib/Lmo/Utils.pm new file mode 100644 index 00000000..4cd89565 --- /dev/null +++ b/lib/Lmo/Utils.pm @@ -0,0 +1,46 @@ +package Lmo::Utils; +use strict; +use warnings qw( FATAL all ); +require Exporter; +our (@ISA, @EXPORT, @EXPORT_OK); + +BEGIN { + @ISA = qw(Exporter); + @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for); +} + +{ + # Gets the glob from a given string. + no strict 'refs'; + sub _glob_for { + return \*{shift()} + } + + # Gets the stash from a given string. + # A stash is a symbol table hash; rough explanation on + # http://perldoc.perl.org/perlguts.html#Stashes-and-Globs + # But the gist of it is that we can use a hash-like thing to + # refer to a class and modify it. + sub _stash_for { + return \%{ shift() . "::" }; + } +} + +sub _install_coderef { + my ($to, $code) = @_; + + return *{ _glob_for $to } = $code; +} + +sub _unimport_coderefs { + my ($target, @names) = @_; + return unless @names; + my $stash = _stash_for($target); + foreach my $name (@names) { + if ($stash->{$name} and defined(&{$stash->{$name}})) { + delete $stash->{$name}; + } + } +} + +1; diff --git a/lib/Mo.pm b/lib/Mo.pm deleted file mode 100644 index c2468b2b..00000000 --- a/lib/Mo.pm +++ /dev/null @@ -1,532 +0,0 @@ -# This program is copyright 2007-2011 Baron Schwartz, 2012 Percona Ireland Ltd. -# Feedback and improvements are welcome. -# -# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED -# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF -# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. -# -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar -# systems, you can issue `man perlgpl' or `man perlartistic' to read these -# licenses. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., 59 Temple -# Place, Suite 330, Boston, MA 02111-1307 USA. -# ########################################################################### -# Mo package -# ########################################################################### -# Package: Mo -# Mo provides a miniature object system in the style of Moose and Moo. -BEGIN { -$INC{"Mo.pm"} = __FILE__; -package Mo; -our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. - -{ - # Gets the glob from a given string. - no strict 'refs'; - sub _glob_for { - return \*{shift()} - } - - # Gets the stash from a given string. A larger explanation about hashes in Mo::Percona - sub _stash_for { - return \%{ shift() . "::" }; - } -} - -use strict; -use warnings qw( FATAL all ); - -use Carp (); -use Scalar::Util qw(looks_like_number blessed); - -# Basic types for isa. If you want a new type, either add it here, -# or give isa a coderef. - -our %TYPES = ( - Bool => sub { !$_[0] || (defined $_[0] && looks_like_number($_[0]) && $_[0] == 1) }, - Num => sub { defined $_[0] && looks_like_number($_[0]) }, - Int => sub { defined $_[0] && looks_like_number($_[0]) && $_[0] == int($_[0]) }, - Str => sub { defined $_[0] }, - Object => sub { defined $_[0] && blessed($_[0]) }, - FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, - - map { - my $type = /R/ ? $_ : uc $_; - $_ . "Ref" => sub { ref $_[0] eq $type } - } qw(Array Code Hash Regexp Glob Scalar) -); - -our %metadata_for; -{ - # Mo::Object is the parent of every Mo-derived object. Here's where new - # and BUILDARGS gets inherited from. - package Mo::Object; - - sub new { - my $class = shift; - my $args = $class->BUILDARGS(@_); - - my @args_to_delete; - while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { - next unless exists $meta->{init_arg}; - my $init_arg = $meta->{init_arg}; - - # If init_arg is defined, then we - if ( defined $init_arg ) { - $args->{$attr} = delete $args->{$init_arg}; - } - else { - push @args_to_delete, $attr; - } - } - - delete $args->{$_} for @args_to_delete; - - for my $attribute ( keys %$args ) { - # coerce - if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { - $args->{$attribute} = $coerce->($args->{$attribute}); - } - # isa - if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { - ( (my $I_name), $I ) = @{$I}; - Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); - } - } - - while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { - next unless $meta->{required}; - Carp::confess("Attribute ($attribute) is required for $class") - if ! exists $args->{$attribute} - } - - @_ = %$args; - my $self = bless $args, $class; - - # BUILD - my @build_subs; - my $linearized_isa = mro::get_linear_isa($class); - - for my $isa_class ( @$linearized_isa ) { - unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; - } - # If &class::BUILD exists, for every class in - # the linearized ISA, call it. - # XXX I _think_ that this uses exists correctly, since - # a class could define a stub for BUILD and then AUTOLOAD - # the body. Should check what Moose does. - exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; - return $self; - } - - # Base BUILDARGS. - sub BUILDARGS { - shift; - my $ref; - if ( @_ == 1 && ref($_[0]) ) { - Carp::confess("Single parameters to new() must be a HASH ref") - unless ref($_[0]) eq ref({}); - $ref = {%{$_[0]}} # We want a new reference, always - } - else { - $ref = { @_ }; - } - return $ref; - } -} - -my %export_for; -%Mo::Internal::Keyword = map { $_ => 1 } qw(has extends override); - -sub Mo::import { - # Set warnings and strict for the caller. - warnings->import(qw(FATAL all)); - strict->import(); - - my $caller = scalar caller(); # Caller's package - my $caller_pkg = $caller . "::"; # Caller's package with :: at the end - my (%exports, %options); - - # Load each feature and call its &e. - my (undef, @features) = @_; - my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); - for my $feature (grep { !$ignore{$_} } @features) { - { local $@; require "Mo/$feature.pm"; } - { - no strict 'refs'; - &{"Mo::${feature}::e"}( - $caller_pkg, - \%exports, - \%options, - \@_ - ); - } - } - - return if $exports{M}; - - %exports = ( - extends => sub { - for my $class ( map { "$_" } @_ ) { - # Try loading the class, but don't croak if we fail. - $class =~ s{::|'}{/}g; - { local $@; eval { require "$class.pm" } } # or warn $@; - } - _set_package_isa($caller, @_); - _set_inherited_metadata($caller); - }, - override => \&override, - has => sub { - my $names = shift; - for my $attribute ( ref $names ? @$names : $names ) { - my %args = @_; - my $method = ($args{is} || '') eq 'ro' - ? sub { - Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") - if $#_; - return $_[0]{$attribute}; - } - : sub { - return $#_ - ? $_[0]{$attribute} = $_[1] - : $_[0]{$attribute}; - }; - - $metadata_for{$caller}{$attribute} = (); - - # isa => Constaint, - if ( my $I = $args{isa} ) { - my $orig_I = $I; - my $type; - if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - $I = _nested_constraints($attribute, $1, $2); - } - $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; - my $orig_method = $method; - $method = sub { - if ( $#_ ) { - Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); - } - goto &$orig_method; - }; - } - - # XXX TODO: Inline builder and default into the actual method, for speed. - # builder => '_builder_method', - if ( my $builder = $args{builder} ) { - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$builder - : goto &$original_method - }; - } - - # default => CodeRef, - if ( my $code = $args{default} ) { - Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") - unless ref($code) eq 'CODE'; - my $original_method = $method; - $method = sub { - $#_ - ? goto &$original_method - : ! exists $_[0]{$attribute} - ? $_[0]{$attribute} = $_[0]->$code - : goto &$original_method - }; - } - - # does => 'Role', - if ( my $role = $args{does} ) { - my $original_method = $method; - $method = sub { - if ( $#_ ) { - Carp::confess(qq) - unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } - } - goto &$original_method - }; - } - - # coerce => CodeRef, - if ( my $coercion = $args{coerce} ) { - $metadata_for{$caller}{$attribute}{coerce} = $coercion; - my $original_method = $method; - $method = sub { - if ( $#_ ) { - return $original_method->($_[0], $coercion->($_[1])) - } - goto &$original_method; - } - } - - # Call the extra features; that is, things loaded from - # the Mo::etc namespace, and not implemented here. - $method = $options{$_}->($method, $attribute, @_) - for sort keys %options; - - # Actually put the attribute's accessor in the class - *{ _glob_for "${caller}::$attribute" } = $method; - - if ( $args{required} ) { - $metadata_for{$caller}{$attribute}{required} = 1; - } - - if ($args{clearer}) { - *{ _glob_for "${caller}::$args{clearer}" } - = sub { delete shift->{$attribute} } - } - - if ($args{predicate}) { - *{ _glob_for "${caller}::$args{predicate}" } - = sub { exists shift->{$attribute} } - } - - if ($args{handles}) { - _has_handles($caller, $attribute, \%args); - } - - if (exists $args{init_arg}) { - $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; - } - } - }, - %exports, - ); - - # We keep this so code doing 'no Mo;' actually does a cleanup. - $export_for{$caller} = [ keys %exports ]; - - # Export has, extends and sosuch. - for my $keyword ( keys %exports ) { - *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} - } - # Set up our caller's ISA, unless they already set it manually themselves, - # in which case we assume they know what they are doing. - # XXX weird syntax here because we want to call the classes' extends at - # least once, to avoid warnings. - *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) - unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; -}; - -sub _check_type_constaints { - my ($attribute, $I, $I_name, $val) = @_; - ( ref($I) eq 'CODE' - ? $I->($val) - : (ref $val eq $I - || ($val && $val eq $I) - || (exists $TYPES{$I} && $TYPES{$I}->($val))) - ) - || Carp::confess( - qq - . qq - . (defined $val ? Mo::Dumper($val) : 'undef') ) -} - -# handles handles -sub _has_handles { - my ($caller, $attribute, $args) = @_; - my $handles = $args->{handles}; - - my $ref = ref $handles; - my $kv; - if ( $ref eq ref [] ) { - # handles => [ ... list of methods ... ], - $kv = { map { $_,$_ } @{$handles} }; - } - elsif ( $ref eq ref {} ) { - # handles => { 'method_to_install' => 'original_method' | [ 'original_method', ... curried arguments ... ], }, - $kv = $handles; - } - elsif ( $ref eq ref qr// ) { - # handles => qr/PAT/, - Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") - unless $args->{isa}; - my $target_class = $args->{isa}; - $kv = { - map { $_, $_ } - grep { $_ =~ $handles } - grep { !exists $Mo::Object::{$_} && $target_class->can($_) } - grep { !$Mo::Internal::Keyword{$_} } - keys %{ _stash_for $target_class } - }; - } - else { - Carp::confess("handles for $ref not yet implemented"); - } - - while ( my ($method, $target) = each %{$kv} ) { - my $name = _glob_for "${caller}::$method"; - Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") - if defined &$name; - - # If we have an arrayref, they are currying some arguments. - my ($target, @curried_args) = ref($target) ? @$target : $target; - *$name = sub { - my $self = shift; - my $delegate_to = $self->$attribute(); - my $error = "Cannot delegate $method to $target because the value of $attribute"; - Carp::confess("$error is not defined") unless $delegate_to; - Carp::confess("$error is not an object (got '$delegate_to')") - unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); - return $delegate_to->$target(@curried_args, @_); - } - } -} - -# Nested (or parametized) constraints look like this: ArrayRef[CONSTRAINT] or -# Maybe[CONSTRAINT]. This function returns a coderef that implements one of -# these. -sub _nested_constraints { - my ($attribute, $aggregate_type, $type) = @_; - - my $inner_types; - if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { - # If the inner constraint -- the part within brackets -- is also a parametized - # constraint, then call this function recursively. - $inner_types = _nested_constraints($1, $2); - } - else { - # Otherwise, try checking if it's one of the built-in types. - $inner_types = $TYPES{$type}; - } - - if ( $aggregate_type eq 'ArrayRef' ) { - return sub { - my ($val) = @_; - return unless ref($val) eq ref([]); - - if ($inner_types) { - for my $value ( @{$val} ) { - return unless $inner_types->($value) - } - } - else { - # $inner_types isn't set, we are dealing with a class name. - for my $value ( @{$val} ) { - return unless $value && ($value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type))); - } - } - return 1; - }; - } - elsif ( $aggregate_type eq 'Maybe' ) { - return sub { - my ($value) = @_; - # For Maybe, undef is valid - return 1 if ! defined($value); - # Otherwise, defer to the inner type - if ($inner_types) { - return unless $inner_types->($value) - } - else { - return unless $value eq $type - || (Scalar::Util::blessed($value) && $value->isa($type)); - } - return 1; - } - } - else { - Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); - } -} - -# Sets a package's @ISA to the list passed in. Overwrites any previous values. -sub _set_package_isa { - my ($package, @new_isa) = @_; - - *{ _glob_for "${package}::ISA" } = [@new_isa]; -} - -# Each class has its own metadata. When a class inhyerits attributes, -# it should also inherit the attribute metadata. -sub _set_inherited_metadata { - my $class = shift; - my $linearized_isa = mro::get_linear_isa($class); - my %new_metadata; - - # Walk @ISA in reverse, grabbing the metadata for each - # class. Attributes with the same name defined in more - # specific classes override their parent's attributes. - for my $isa_class (reverse @$linearized_isa) { - %new_metadata = ( - %new_metadata, - %{ $metadata_for{$isa_class} || {} }, - ); - } - $metadata_for{$class} = \%new_metadata; -} - -sub unimport { - my $caller = scalar caller(); - my $stash = _stash_for( $caller ); - - delete $stash->{$_} for @{$export_for{$caller}}; -} - -sub Dumper { - require Data::Dumper; - local $Data::Dumper::Indent = 0; - local $Data::Dumper::Sortkeys = 0; - local $Data::Dumper::Quotekeys = 0; - local $Data::Dumper::Terse = 1; - - Data::Dumper::Dumper(@_) -} - -BEGIN { - # mro is the method resolution order. The module itself is core in - # recent Perls; In older Perls it's available from MRO::Compat from - # CPAN, and in case that isn't available to us, we inline the barest - # funcionality. - if ($] >= 5.010) { - { local $@; require mro; } - } - else { - local $@; - eval { - require MRO::Compat; - } or do { - *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { - no strict 'refs'; - - my $classname = shift; - - my @lin = ($classname); - my %stored; - foreach my $parent (@{"$classname\::ISA"}) { - my $plin = mro::get_linear_isa_dfs($parent); - foreach (@$plin) { - next if exists $stored{$_}; - push(@lin, $_); - $stored{$_} = 1; - } - } - return \@lin; - }; - } - } -} - -sub override { - my ($methods, $code) = @_; - my $caller = scalar caller; - - for my $method ( ref($methods) ? @$methods : $methods ) { - my $full_method = "${caller}::${method}"; - *{_glob_for $full_method} = $code; - } -} - -} -1; -# ########################################################################### -# End Mo package -# ########################################################################### diff --git a/lib/VersionParser.pm b/lib/VersionParser.pm index 0417a2f2..cefc0e94 100644 --- a/lib/VersionParser.pm +++ b/lib/VersionParser.pm @@ -22,7 +22,7 @@ # VersionParser parses a MySQL version string. package VersionParser; -use Mo; +use Lmo; use Scalar::Util qw(blessed); use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; @@ -213,7 +213,7 @@ sub _d { print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } -no Mo; +no Lmo; 1; } # ########################################################################### diff --git a/t/lib/Mo/Bar.pm b/t/lib/Lmo/Bar.pm similarity index 77% rename from t/lib/Mo/Bar.pm rename to t/lib/Lmo/Bar.pm index 1a4a2410..91b7cc7b 100644 --- a/t/lib/Mo/Bar.pm +++ b/t/lib/Lmo/Bar.pm @@ -1,4 +1,4 @@ package Bar; -use Mo; +use Lmo; extends 'Foo'; 1; diff --git a/t/lib/Mo/Boo.pm b/t/lib/Lmo/Boo.pm similarity index 76% rename from t/lib/Mo/Boo.pm rename to t/lib/Lmo/Boo.pm index b6a716ee..8096c926 100644 --- a/t/lib/Mo/Boo.pm +++ b/t/lib/Lmo/Boo.pm @@ -1,5 +1,5 @@ package Boo; -use Mo; +use Lmo; has 'buff'; diff --git a/t/lib/Mo/Foo.pm b/t/lib/Lmo/Foo.pm similarity index 77% rename from t/lib/Mo/Foo.pm rename to t/lib/Lmo/Foo.pm index 728da69d..d2c896c8 100644 --- a/t/lib/Mo/Foo.pm +++ b/t/lib/Lmo/Foo.pm @@ -1,5 +1,5 @@ package Foo; -use Mo; +use Lmo; has 'stuff'; diff --git a/t/lib/Mo/build.t b/t/lib/Lmo/build.t similarity index 78% rename from t/lib/Mo/build.t rename to t/lib/Lmo/build.t index 1aa70c0b..2cc201fc 100644 --- a/t/lib/Mo/build.t +++ b/t/lib/Lmo/build.t @@ -14,31 +14,31 @@ use Test::More; $main::count = 1; package Foo; -use Mo 'build'; +use Lmo 'build'; has 'foo' => (is => 'rw'); sub BUILD { my $self = shift; - ::is_deeply(\@_, [qw(stuff 1)], "Foo's BUILD doesn't get the class name"); + ::is_deeply([sort @_], [sort qw(stuff 1)], "Foo's BUILD doesn't get the class name"); $self->foo($main::count++); } package Bar; -use Mo; +use Lmo; extends 'Foo'; has 'bar' => (is => 'rw'); package Baz; -use Mo; +use Lmo; extends 'Bar'; has 'baz' => (is => 'rw'); sub BUILD { my $self = shift; - ::is_deeply(\@_, [qw(stuff 1)], "Baz's BUILD doesn't get the class name"); + ::is_deeply([sort @_], [sort qw(stuff 1)], "Baz's BUILD doesn't get the class name"); $self->baz($main::count++); } package Gorch; -use Mo; +use Lmo; extends 'Baz'; has 'gorch' => (is => 'rw'); diff --git a/t/lib/Mo/buildargs.t b/t/lib/Lmo/buildargs.t similarity index 93% rename from t/lib/Mo/buildargs.t rename to t/lib/Lmo/buildargs.t index 403bd55d..ddc660da 100644 --- a/t/lib/Mo/buildargs.t +++ b/t/lib/Lmo/buildargs.t @@ -15,13 +15,13 @@ $main::count = 0; { package Nothing; - use Mo; + use Lmo; has nothing_special => ( is => 'rw' ); } ok(Nothing->can("BUILDARGS"), "Every class automatically gets buildargs"); package Foo; -use Mo; +use Lmo; has 'foo' => (is => 'rw'); sub BUILDARGS { my $class = shift; @@ -30,12 +30,12 @@ sub BUILDARGS { } package Bar; -use Mo; +use Lmo; extends 'Foo'; has 'bar' => (is => 'rw'); package Baz; -use Mo; +use Lmo; extends 'Bar'; has 'baz' => (is => 'rw'); sub BUILDARGS { @@ -45,7 +45,7 @@ sub BUILDARGS { } package Gorch; -use Mo; +use Lmo; extends 'Baz'; has 'gorch' => (is => 'rw'); @@ -53,7 +53,7 @@ package main; $main::count = 0; my $g = Foo->new; -is $main::count, 1, "A class with no explicit parent inherits SUPER::BUILDARGS from Mo::Object"; +is $main::count, 1, "A class with no explicit parent inherits SUPER::BUILDARGS from Lmo::Object"; $main::count = 0; $g = Gorch->new; diff --git a/t/lib/Mo/coerce.t b/t/lib/Lmo/coerce.t similarity index 98% rename from t/lib/Mo/coerce.t rename to t/lib/Lmo/coerce.t index 8af02b08..5aa1234f 100644 --- a/t/lib/Mo/coerce.t +++ b/t/lib/Lmo/coerce.t @@ -12,7 +12,7 @@ use English qw(-no_match_vars); use Test::More; package Foo::coerce; -use Mo; +use Lmo; has 'stuff' => (coerce => sub { uc $_[0] }); diff --git a/t/lib/Mo/extends.t b/t/lib/Lmo/extends.t similarity index 91% rename from t/lib/Mo/extends.t rename to t/lib/Lmo/extends.t index 30eb8731..a46ff3df 100644 --- a/t/lib/Mo/extends.t +++ b/t/lib/Lmo/extends.t @@ -11,7 +11,7 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Mo"; +use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Lmo"; use Bar; my $b = Bar->new; diff --git a/t/lib/Mo/handles.t b/t/lib/Lmo/handles.t similarity index 93% rename from t/lib/Mo/handles.t rename to t/lib/Lmo/handles.t index 653c396f..af6e822a 100644 --- a/t/lib/Mo/handles.t +++ b/t/lib/Lmo/handles.t @@ -21,14 +21,14 @@ use Test::More; { package Foo; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'bar' => (is => 'rw', default => sub { 10 }); sub baz { 42 } package Bar; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'foo' => ( is => 'rw', @@ -99,13 +99,13 @@ is($bar->foo_bar, 20, '... correctly curried a single argument'); { package Engine; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); sub go { 'Engine::go' } sub stop { 'Engine::stop' } package Car; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'engine' => ( is => 'rw', @@ -137,14 +137,14 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); { package Baz; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); sub foo { 'Baz::foo' } sub bar { 'Baz::bar' } sub boo { 'Baz::boo' } package Baz::Proxy1; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'baz' => ( is => 'ro', @@ -154,7 +154,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); ); package Baz::Proxy2; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'baz' => ( is => 'ro', @@ -164,7 +164,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); ); package Baz::Proxy3; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'baz' => ( is => 'ro', @@ -228,14 +228,14 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); requires 'bar'; package Foo::Baz; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); sub foo { 'Foo::Baz::FOO' } sub bar { 'Foo::Baz::BAR' } sub baz { 'Foo::Baz::BAZ' } package Foo::Thing; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'thing' => ( is => 'rw', @@ -244,7 +244,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); ); package Foo::OtherThing; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); use Moose::Util::TypeConstraints; has 'other_thing' => ( @@ -288,7 +288,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); { package Foo::Autoloaded; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); sub AUTOLOAD { my $self = shift; @@ -304,7 +304,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); } package Bar::Autoloaded; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'foo' => ( is => 'rw', @@ -313,7 +313,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); ); package Baz::Autoloaded; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); has 'foo' => ( is => 'rw', @@ -322,7 +322,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); ); package Goorch::Autoloaded; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); eval { has 'foo' => ( @@ -447,7 +447,7 @@ is($car->stop, 'Engine::stop', '... got the right value from ->stop'); { package Delegator; - use Mo qw(is required handles default builder); + use Lmo qw(is required handles default builder); sub full { 1 } sub stub; diff --git a/t/lib/Mo/init_arg.t b/t/lib/Lmo/init_arg.t similarity index 95% rename from t/lib/Mo/init_arg.t rename to t/lib/Lmo/init_arg.t index b6fab8d4..2c136a0a 100644 --- a/t/lib/Mo/init_arg.t +++ b/t/lib/Lmo/init_arg.t @@ -15,7 +15,7 @@ use Test::More; { package Foo; - use Mo qw( is init_arg ); + use Lmo qw( is init_arg ); eval { has 'foo' => ( @@ -64,7 +64,7 @@ use Test::More; { package Foo2; - use Mo qw( is init_arg clearer default ); + use Lmo qw( is init_arg clearer default ); my $counter; eval { diff --git a/t/lib/Mo/is.t b/t/lib/Lmo/is.t similarity index 97% rename from t/lib/Mo/is.t rename to t/lib/Lmo/is.t index 84ea47d1..02f1a63c 100644 --- a/t/lib/Mo/is.t +++ b/t/lib/Lmo/is.t @@ -12,7 +12,7 @@ use English qw(-no_match_vars); use Test::More; package Foo::is; -use Mo qw(is); +use Lmo qw(is); has 'stuff' => (is => 'ro'); diff --git a/t/lib/Mo/isa.t b/t/lib/Lmo/isa.t similarity index 97% rename from t/lib/Mo/isa.t rename to t/lib/Lmo/isa.t index 426c2cb9..b52d67f9 100644 --- a/t/lib/Mo/isa.t +++ b/t/lib/Lmo/isa.t @@ -29,7 +29,7 @@ sub lives_ok (&;$) { } package Foo::isa; -use Mo qw(isa); +use Lmo qw(isa); my @types = qw(Bool Num Int Str ArrayRef CodeRef HashRef RegexpRef); my @refs = ([], sub { }, {}, qr( )); @@ -191,11 +191,11 @@ my $thisperl = $^X; if ($^O ne 'VMS') {$thisperl .= $Config{_exe} unless $thisperl =~ m/$Config{_exe}$/i;} -my $pm_test = "$PerconaTest::trunk/t/lib/Mo/isa_subtest.pm"; +my $pm_test = "$PerconaTest::trunk/t/lib/Lmo/isa_subtest.pm"; ok( scalar(IPC::Cmd::run(command => [$thisperl, $pm_test])), - "Mo types work with Scalar::Util::PP", + "Lmo types work with Scalar::Util::PP", ); done_testing; diff --git a/t/lib/Mo/isa_subtest.pm b/t/lib/Lmo/isa_subtest.pm similarity index 98% rename from t/lib/Mo/isa_subtest.pm rename to t/lib/Lmo/isa_subtest.pm index 4097f10b..4cdd29e3 100644 --- a/t/lib/Mo/isa_subtest.pm +++ b/t/lib/Lmo/isa_subtest.pm @@ -16,7 +16,7 @@ use warnings; { package isa_subtest; - use Mo; + use Lmo; has attr => ( is => 'rw', diff --git a/t/lib/Lmo/meta.t b/t/lib/Lmo/meta.t new file mode 100644 index 00000000..40106511 --- /dev/null +++ b/t/lib/Lmo/meta.t @@ -0,0 +1,83 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +sub throws_ok (&;$) { + my ( $code, $pat, $msg ) = @_; + eval { $code->(); }; + like ( $EVAL_ERROR, $pat, $msg ); +} + +{ + package Metatest; + use Lmo; + + has stuff => ( is => 'rw', required => 1 ); + has init_stuff1 => ( is => 'rw', init_arg => undef ); + has init_stuff2 => ( is => 'rw', init_arg => 'fancy_name' ); +} +{ +package Metatest::child; + use Lmo; + extends 'Metatest'; + + has more_stuff => ( is => 'rw' ); +} + +my $obj = Metatest->new( stuff => 100 ); + +can_ok($obj, 'meta'); + +my $meta = $obj->meta(); + +is_deeply( + [ sort $meta->attributes ], + [ sort qw(stuff init_stuff1 init_stuff2) ], + "->attributes works" +); + +is_deeply( + [ sort $meta->attributes_for_new ], + [ sort qw(stuff fancy_name) ], + "->attributes_for_new works" +); + +# Do these BEFORE initializing ::extends +my $meta2 = Metatest::child->meta(); +is_deeply( + [ sort $meta2->attributes ], + [ sort qw(stuff init_stuff1 init_stuff2 more_stuff) ], + "->attributes works on a child class" +); + +is_deeply( + [ sort $meta2->attributes_for_new ], + [ sort qw(stuff fancy_name more_stuff) ], + "->attributes_for_new works in a child class" +); + +my $meta3 = Metatest::child->new(stuff => 10)->meta(); +is_deeply( + [ sort $meta3->attributes ], + [ sort qw(stuff init_stuff1 init_stuff2 more_stuff) ], + "->attributes works on an initialized child class" +); + +is_deeply( + [ sort $meta3->attributes_for_new ], + [ sort qw(stuff fancy_name more_stuff) ], + "->attributes_for_new works in an initialized child class" +); + +throws_ok { Metatest::child->new() } qr/\QAttribute (stuff) is required for Metatest::child/; + +done_testing; diff --git a/t/lib/Mo/object.t b/t/lib/Lmo/object.t similarity index 90% rename from t/lib/Mo/object.t rename to t/lib/Lmo/object.t index 37a71ea4..c54266c9 100644 --- a/t/lib/Mo/object.t +++ b/t/lib/Lmo/object.t @@ -10,7 +10,7 @@ use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Mo"; +use lib "$ENV{PERCONA_TOOLKIT_BRANCH}/t/lib/Lmo"; { package Clean; use Foo; } diff --git a/t/lib/Mo/required.t b/t/lib/Lmo/required.t similarity index 86% rename from t/lib/Mo/required.t rename to t/lib/Lmo/required.t index b7319317..7ef93ff1 100644 --- a/t/lib/Mo/required.t +++ b/t/lib/Lmo/required.t @@ -13,14 +13,14 @@ use Test::More; #============ package Foo::required; -use Mo qw(required); +use Lmo qw(required); has 'stuff' => (required => 1); has 'stuff2' => (required => 1); has 'foo' => (); #============ package Foo::required_is; -use Mo qw(required); +use Lmo qw(required); has 'stuff' => (required => 1, is => 'ro'); #============ @@ -28,7 +28,7 @@ has 'stuff' => (required => 1, is => 'ro'); package main; my $f0 = eval { Foo::required->new(stuff2 => 'foobar') }; -like $@, qr/^\QAttribute (stuff) is required/, 'Mo dies when a required value is not provided'; +like $@, qr/^\QAttribute (stuff) is required/, 'Lmo dies when a required value is not provided'; my $f = Foo::required->new(stuff => 'fubar', stuff2 => 'foobar'); is $f->stuff, 'fubar', 'Object is correctly initialized when required values are provided'; diff --git a/t/lib/Lmo/role.t b/t/lib/Lmo/role.t new file mode 100644 index 00000000..10345885 --- /dev/null +++ b/t/lib/Lmo/role.t @@ -0,0 +1,72 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +BEGIN { + my $have_roles = eval { require Role::Tiny }; + plan skip_all => "Can't load Role::Tiny, not testing Roles" + unless $have_roles; +} + +{ + package One::P1; use Lmo::Role; + has two => (is => 'ro', default => sub { 'two' }); + no Lmo::Role; + + package One::P2; use Lmo::Role; + has three => (is => 'ro', default => sub { 'three' }); + no Lmo::Role; + + package One::P3; use Lmo::Role; + has four => (is => 'ro', default => sub { 'four' }); + no Lmo::Role; + + package One; use Lmo; + with qw( One::P1 One::P2 ); + has one => (is => 'ro', default => sub { 'one' }); +} + +my $combined = One->new(); + +ok $combined->does($_), "Does $_" for qw(One::P1 One::P2); + +ok !$combined->does($_), "Doesn't $_" for qw(One::P3 One::P4); + +is $combined->one, "one", "attr default set from class"; +is $combined->two, "two", "attr default set from role"; +is $combined->three, "three", "attr default set from role"; + +# Testing unimport + +{ + package Two::P1; use Lmo::Role; + has two => (is => 'ro', default => sub { 'two' }); + no Lmo::Role; + + package Two; use Lmo; + with qw(Two::P1); + has three => ( is => 'ro', default => sub { 'three' } ); + no Lmo; +} + +my $two = Two->new(); + +is + $two->two(), + 'two', + "unimporting in a role doesn't remove new attributes"; + +for my $class ( qw( Two::P1 Two ) ) { + ok !$class->can($_), "...but does remove $_ from $class" for qw(has with extends requires); +} + +done_testing; diff --git a/t/lib/Mo/strict.t b/t/lib/Lmo/strict.t similarity index 87% rename from t/lib/Mo/strict.t rename to t/lib/Lmo/strict.t index eb98e4dd..9e3e0b5c 100644 --- a/t/lib/Mo/strict.t +++ b/t/lib/Lmo/strict.t @@ -11,9 +11,9 @@ use warnings FATAL => 'all'; use English qw(-no_match_vars); use Test::More; -eval 'package Foo; use Mo; $x = 1'; +eval 'package Foo; use Lmo; $x = 1'; like $@, qr/Global symbol "\$x" requires explicit package name/, - 'Mo is strict'; + 'Lmo is strict'; done_testing; diff --git a/t/lib/Mo/test.t b/t/lib/Lmo/test.t similarity index 85% rename from t/lib/Mo/test.t rename to t/lib/Lmo/test.t index 87682a31..0772900e 100644 --- a/t/lib/Mo/test.t +++ b/t/lib/Lmo/test.t @@ -13,18 +13,18 @@ use Test::More; #============ package Foo; -use Mo; +use Lmo; has 'this'; #============ package main; -ok defined(&Foo::has), 'Mo exports has'; -ok defined(&Foo::extends), 'Mo exports extends'; -ok not(defined(&Foo::new)), 'Mo does not export new'; -ok 'Foo'->isa('Mo::Object'), 'Foo isa Mo::Object'; -is "@Foo::ISA", "Mo::Object", '@Foo::ISA is Mo::Object'; +ok defined(&Foo::has), 'Lmo exports has'; +ok defined(&Foo::extends), 'Lmo exports extends'; +ok not(defined(&Foo::new)), 'Lmo does not export new'; +ok 'Foo'->isa('Lmo::Object'), 'Foo isa Lmo::Object'; +is "@Foo::ISA", "Lmo::Object", '@Foo::ISA is Lmo::Object'; ok 'Foo'->can('new'), 'Foo can new'; ok 'Foo'->can('this'), 'Foo can this'; @@ -50,7 +50,7 @@ ok not(defined($f->{this})), '{this} is not defined'; #============ package Bar; -use Mo 'builder', 'default'; +use Lmo 'builder', 'default'; extends 'Foo'; has 'that'; @@ -70,7 +70,7 @@ has guess => ( #============ package main; -ok 'Bar'->isa('Mo::Object'), 'Bar isa Mo::Object'; +ok 'Bar'->isa('Lmo::Object'), 'Bar isa Lmo::Object'; ok 'Bar'->isa('Foo'), 'Bar isa Foo'; is "@Bar::ISA", 'Foo', '@Bar::ISA is Foo'; ok 'Bar'->can('new'), 'Bar can new'; @@ -85,7 +85,7 @@ my $b = Bar->new( is ref($b), 'Bar', 'Object created'; ok $b->isa('Foo'), 'Inheritance works'; -ok $b->isa('Mo::Object'), 'Bar isa Mo::Object since Foo isa Mo::Object'; +ok $b->isa('Lmo::Object'), 'Bar isa Lmo::Object since Foo isa Lmo::Object'; is $b->this, 'thing', 'Read works in parent class'; is $b->that, 'thong', 'Read works in current class'; is ref($b->them), 'ARRAY', 'default works'; @@ -103,7 +103,7 @@ is $b->guess, 'me me me', 'default trumps builder'; #============ package Baz; -use Mo 'build'; +use Lmo 'build'; has 'foo'; @@ -114,7 +114,7 @@ sub BUILD { #============ package Maz; -use Mo; +use Lmo; extends 'Baz'; has 'bar'; diff --git a/t/lib/Lmo/unimport.t b/t/lib/Lmo/unimport.t new file mode 100644 index 00000000..f40d4485 --- /dev/null +++ b/t/lib/Lmo/unimport.t @@ -0,0 +1,28 @@ +#!/usr/bin/perl + +BEGIN { + die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n" + unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH}; + unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib"; +}; + +use strict; +use warnings FATAL => 'all'; +use English qw(-no_match_vars); +use Test::More; + +{ + package One; use Lmo; + has one => (is => 'ro', default => sub { 'one' }); + no Lmo; +} + +my $unimported = One->new(); +is + $unimported->one(), + 'one', + "after unimporting, ->one still works"; + +ok !$unimported->can($_), "after unimpoirt, can't $_" for qw(has with extends); + +done_testing;