use strict; use warnings; package IO::All::Base; use Fcntl; sub import { my $class = shift; my $flag = $_[0] || ''; my $package = caller; no strict 'refs'; if ($flag eq '-base') { push @{$package . "::ISA"}, $class; *{$package . "::$_"} = \&$_ for qw'field const option chain proxy proxy_open'; } elsif ($flag eq -mixin) { mixin_import(scalar(caller(0)), $class, @_); } else { my @flags = @_; for my $export (@{$class . '::EXPORT'}) { *{$package . "::$export"} = $export eq 'io' ? $class->_generate_constructor(@flags) : \&{$class . "::$export"}; } } } sub _generate_constructor { my $class = shift; my (@flags, %flags, $key); for (@_) { if (s/^-//) { push @flags, $_; $flags{$_} = 1; $key = $_; } else { $flags{$key} = $_ if $key; } } my $constructor; $constructor = sub { my $self = $class->new(@_); for (@flags) { $self->$_($flags{$_}); } $self->_constructor($constructor); return $self; } } sub _init { my $self = shift; $self->io_handle(undef); $self->is_open(0); return $self; } #=============================================================================== # Closure generating functions #=============================================================================== sub option { my $package = caller; my ($field, $default) = @_; $default ||= 0; field("_$field", $default); no strict 'refs'; *{"${package}::$field"} = sub { my $self = shift; *$self->{"_$field"} = @_ ? shift(@_) : 1; return $self; }; } sub chain { my $package = caller; my ($field, $default) = @_; no strict 'refs'; *{"${package}::$field"} = sub { my $self = shift; if (@_) { *$self->{$field} = shift; return $self; } return $default unless exists *$self->{$field}; return *$self->{$field}; }; } sub field { my $package = caller; my ($field, $default) = @_; no strict 'refs'; return if defined &{"${package}::$field"}; *{"${package}::$field"} = sub { my $self = shift; unless (exists *$self->{$field}) { *$self->{$field} = ref($default) eq 'ARRAY' ? [] : ref($default) eq 'HASH' ? {} : $default; } return *$self->{$field} unless @_; *$self->{$field} = shift; }; } sub const { my $package = caller; my ($field, $default) = @_; no strict 'refs'; return if defined &{"${package}::$field"}; *{"${package}::$field"} = sub { $default }; } sub proxy { my $package = caller; my ($proxy) = @_; no strict 'refs'; return if defined &{"${package}::$proxy"}; *{"${package}::$proxy"} = sub { my $self = shift; my @return = $self->io_handle->$proxy(@_); $self->_error_check; wantarray ? @return : $return[0]; }; } sub proxy_open { my $package = caller; my ($proxy, @args) = @_; no strict 'refs'; return if defined &{"${package}::$proxy"}; my $method = sub { my $self = shift; $self->_assert_open(@args); my @return = $self->io_handle->$proxy(@_); $self->_error_check; wantarray ? @return : $return[0]; }; *{"$package\::$proxy"} = (@args and $args[0] eq '>') ? sub { my $self = shift; $self->$method(@_); return $self; } : $method; } sub mixin_import { my $target_class = shift; $target_class = caller(0) if $target_class eq 'mixin'; my $mixin_class = shift or die "Nothing to mixin"; eval "require $mixin_class"; my $pseudo_class = CORE::join '-', $target_class, $mixin_class; my %methods = mixin_methods($mixin_class); no strict 'refs'; no warnings; @{"$pseudo_class\::ISA"} = @{"$target_class\::ISA"}; @{"$target_class\::ISA"} = ($pseudo_class); for (keys %methods) { *{"$pseudo_class\::$_"} = $methods{$_}; } } sub mixin_methods { my $mixin_class = shift; no strict 'refs'; my %methods = all_methods($mixin_class); map { $methods{$_} ? ($_, \ &{"$methods{$_}\::$_"}) : ($_, \ &{"$mixin_class\::$_"}) } (keys %methods); } sub all_methods { no strict 'refs'; my $class = shift; my %methods = map { ($_, $class) } grep { defined &{"$class\::$_"} and not /^_/ } keys %{"$class\::"}; return (%methods); } 1;