Crafting Custom Interfaces with Sub::Exporter

Ricardo Signes
Ricardo Signescomputer programmer at Pobox.com
Sub::Exporter
 Crafting Custom Interfaces
rjbs
@cpan.org
Ricardo SIGNES
What’s an Exporter?


• Something that takes care of the
  annoying details of importing.
What is Importing?
What is Importing?


• Build it over there, then bring it here.
What is Importing?


• Build it over there, then bring it here.
• For our purposes, “it” is code.
Why Do We Import?
Why Do We Import?


• We want someone else to do the hard,
  boring work.
Why Do We Import?


• We want someone else to do the hard,
  boring work.
• And we want it done cheap.
sub strftime
{
  my($pkg,$fmt,$time);
  ($pkg,$fmt,$time,$tzname) = @_;

 my $me = ref($pkg) ? $pkg : bless [];

 if(defined $tzname)
  {
    $tzname = uc $tzname;

     $tzname = sprintf(“%+05d”,$tzname)
    unless($tzname =~ /D/);

     $epoch = timegm(@{$time}[0..5]);

     @$me = gmtime($epoch + tz_offset($tzname) - tz_offset());
  }
 else
  {
    @$me = @$time;
    undef $epoch;
  }

 _subs($me,$fmt);
}
Date::Format::strftime
strftime
How Importing Works
How Importing Works

• the client use-s a module
How Importing Works

• the client use-s a module
• the module’s import method is called
How Importing Works

• the client use-s a module
• the module’s import method is called
• something ugly happens
How Importing Works

• the client use-s a module
• the module’s import method is called
• something ugly happens
• the client has more named subs
How Importing Works

• usually that ugliness is Exporter.pm
How Importing Works

  • usually that ugliness is Exporter.pm

# the dark and twisted heart of Exporter.pm
*{“${callpkg}::$sym”} = &{“${pkg}::$sym”};
*{quot;$::$quot;} = &{quot;$::$quot;};




• the caller gets the same code
• with the same name
*{quot;$::$quot;} = &{quot;$::$quot;};




• Exporter.pm churns out identically
  named and constructed products.
The Factory Model
The Factory Model

• One size fits all
The Factory Model

• One size fits all
• If it doesn’t fit your code, adjust your
  code.
The Factory Model

• One size fits all
• If it doesn’t fit your code, adjust your
  code.
• Or abuse the Exporter
The Factory Model


• There’s Only One Way To Do It
The Tool Metaphor
The Tool Metaphor

• “You can’t write good code without
  good tools.”
The Tool Metaphor

• “You can’t write good code without
  good tools.”
• Exporters are tools for making tools.
The Tool Metaphor

• “You can’t write good code without
  good tools.”
• Exporters are tools for making tools.
• Their quality has an impact all the way
  down the line.
Craftsman Tools
Craftsman Tools

• We want adaptable tools, customized
  for our current needs.
Craftsman Tools

• We want adaptable tools, customized
  for our current needs.
• We want tools hand-crafted to our
  specifications.
Craftsman Tools

• We want adaptable tools, customized
  for our current needs.
• We want tools hand-crafted to our
  specifications.
• We want to reduce our labor by having
  someone else do the boring work.
Sub::Exporter!
Basic Exporting
The Basics

• String::Truncate
• trunc: truncate a string to length
• elide: truncate, ending in “...”
String::Truncate
String::Truncate

$string = “This string is 34 characters long.”;
String::Truncate

$string = “This string is 34 characters long.”;

trunc($string, 10); # This strin
String::Truncate

$string = “This string is 34 characters long.”;

trunc($string, 10); # This strin

elide($string, 10); # This st...
Basic Exports

To let your client write:

 use String::Truncate qw(elide trunc);
Basic Exports

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
};
Basic Groups

To let your client write:

 use String::Truncate qw(:all)
Basic Groups

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
};
Basic Groups

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
   groups => { all => [qw(elide trunc)] },
};
Basic Groups

To let your client write:

 use String::Truncate qw(:basic)
Basic Groups

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
   groups => { basic => [qw(elide trunc)] }
};
Basic Defaults

To let your client write:

 use String::Truncate; # imports “trunc”
Basic Defaults

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
   groups => {
      basic   => [ qw(elide trunc) ],
      default => [ qw(trunc) ]
   },
};
Using Exporter.pm
package String::Truncate;

use Exporter;
use vars
  qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);

@ISA = qw(Exporter);
@EXPORT = qw(trunc);
@EXPORT_OK = qw(elide trunc);
%EXPORT_TAGS = (
   all   => @EXPORT_OK,
   basic => [ qw(elide trunc) ],
);
Renaming Exports
Renaming Exports


• avoid namespace collisions
Renaming Exports


use CGI qw(:standard);
use LWP::Simple;

my $head = head(...); # what happens?
Renaming Exports


• avoid unclear or ambiguous names
Renaming Exports

use File::Basename;
use XML::Parser;

my $file = fileparse($ARGV[0]);

$parser->parsefile($file);
Renaming Exports


• “privatize” subs imported to classes
Renaming Exports


package XML::Parser::Hypothetical;
use File::Basename;
Renaming Exports
Using Exporter::Renaming
use Exporter::Renaming;
use String::Truncate
 qw(elide),
 Renaming => [ trunc => ‘trunc_str’ ];
no Exporter::Renaming;
Renaming Exports

To let your client write:

 use String::Truncate
  qw(trunc), trunc => { -as => ‘trunc_str’ };
Renaming Exports

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
   groups => {
      basic   => [ qw(elide trunc) ],
      default => [ qw(trunc) ]
   },
};
Wait a second...


use String::Truncate
 qw(trunc), trunc => { -as => ‘trunc_str’ };
Data::OptList
Quick and Dirty Data Structures
Data::OptList

@optlist = (
  qw(alfa bravo),
  charlie => [ 0, 1, 2 ],
  delta   => { a => 1 },
  ‘echo’,
  foxtrox => undef,
  ‘gulf’,
);
Data::OptList

                            $as_href = {
@optlist = (
                             alfa => undef,
  qw(alfa bravo),
                             bravo => undef,
  charlie => [ 0, 1, 2 ],
                             charlie => [ 0, 1, 2],
  delta   => { a => 1 },
                             delta   => { a => 1 },
  ‘echo’,
                             echo    => undef,
  foxtrox => undef,
                             foxtrot => undef,
  ‘gulf’,
                             gulf    => undef,
);
                            ];
Data::OptList

                            $as_aref = [
@optlist = (
                             [ alfa => undef ],
  qw(alfa bravo),
                             [ bravo => undef ],
  charlie => [ 0, 1, 2 ],
                             [ charlie => [0,1,2] ],
  delta   => { a => 1 },
                             [ delta   => {a => 1}],
  ‘echo’,
                             [ echo    => undef ],
  foxtrox => undef,
                             [ foxtrot => undef ],
  ‘gulf’,
                             [ gulf    => undef ],
);
                            ];
Data::OptList

@optlist = (
   qw(aye aye)
   love => [ qw(chex) ],
   love => [ qw(milk) ],
   aye => { sir => ‘!’ },
);
Data::OptList

                            $as_aref = [
@optlist = (
                               [ aye => undef ],
   qw(aye aye)
                               [ aye => undef ],
   love => [ qw(chex) ],
                               [ love => [qw(chex)] ],
   love => [ qw(milk) ],
                               [ love => [qw(milk)] ],
   aye => { sir => ‘!’ },
                               [ aye => {sir => ‘!’}]
);
                            ];
Data::OptList

@optlist = (
   qw(aye aye)
   love => [ qw(chex) ],
                            $as_href = die “...”;
   love => [ qw(milk) ],
   aye => { sir => ‘!’ },
);
Customizing Exports
Subclassed Exporters
Subclassed Exporters

• import is a method
Subclassed Exporters

• import is a method
• that implies that exporters are classes
Subclassed Exporters

• import is a method
• that implies that exporters are classes
• and that we can subclass them
package String::Truncate::Split;
use base qw(String::Truncate);

sub trunc {
  my ($string, $length) = @_;

    # ...

    return ($head, $tail);
}
Subclassed Exporters
Subclassed Exporters

• *{“$::$”}   = &{“$::$”};
Subclassed Exporters

• *{“$::$”} = &{“$::$”};
• @EXPORT has to be defined in the
  derived class
Subclassed Exporters

• *{“$::$”} = &{“$::$”};
• @EXPORT has to be defined in the
  derived class
• the export has to be defined in the
  exporting package
package String::Truncate::Split;
use base qw(String::Truncate);

sub trunc {
  my ($string, $length) = @_;

    # ...

    return ($head, $tail);
}

1;
package String::Truncate::Split;
use base qw(String::Truncate);

our   @EXPORT_OK = @String::Truncate::EXPORT_OK;
our   @EXPORT    = @String::Truncate::EXPORT;
our   %EXPORT_TAGS
  =   %String::Truncate::EXPORT_TAGS;

sub trunc {
  my ($string, $length) = @_;

    # ...

    return ($head, $tail);
}
package String::Truncate::Split;
use base qw(String::Truncate);

our   @EXPORT_OK = @String::Truncate::EXPORT_OK;
our   @EXPORT    = @String::Truncate::EXPORT;
our   %EXPORT_TAGS
  =   %String::Truncate::EXPORT_TAGS;

sub trunc {
  my ($string, $length) = @_;

    # ...

    return ($head, $tail);
}

*$_ = &{“String::Truncate::$_”}
  for grep { not defined &{__PACKAGE__.“::$_”} }
  @EXPORT;
package String::Truncate::Split;
use base qw(String::Truncate);

our   @EXPORT_OK = @String::Truncate::EXPORT_OK;
our   @EXPORT    = @String::Truncate::EXPORT;
our   %EXPORT_TAGS
  =   %String::Truncate::EXPORT_TAGS;

sub trunc {
  my ($string, $length) = @_;

    # ...

    return ($head, $tail);
}

do {
  no strict ‘refs’;
  *$_ = &{“String::Truncate::$_”}
    for grep { not defined &{__PACKAGE__.“::$_”} }
    @EXPORT;
}
Subclassed Exporters
Subclassed Exporters


• Sub::Exporter finds exports with “can”
Subclassed Exporters


• Sub::Exporter finds exports with “can”
• this means you can subclass exporting
  toolkits, replacing just pieces
package String::Truncate::Split;
use base qw(String::Truncate);

sub trunc {
  my ($string, $length) = @_;

    # ...

    return ($head, $tail);
}
Customizing Exports
Customizing Exports


• What if you want trunc to work
  differently when you use it?
Customizing Exports


• Some modules do this with package
  variables.
Package-Level Config


use String::Truncate qw(:all);
$String::Truncate::DEFAULT_LENGTH = 20;
$String::Truncate::DEFAULT_MARKER = “--”;
Package-Level Config

use String::Truncate qw(:all);
$String::Truncate::DEFAULT_LENGTH = 20;
$String::Truncate::DEFAULT_MARKER = “--”;

use Tools::Useful;
Package-Level Config

use String::Truncate ();
use Tools::Useful;

sub trunc {
  my ($string, $length) = @_;
  $length = 20 if not defined $length;
  String::Truncate::trunc($string, $length)
}
Package-Level Config

use String::Truncate ();
use Tools::Useful;

sub trunc {
  local $String::Truncate::DEFAULT_LENGTH
    = 20;
  String::Truncate::trunc(@_);
}
Custom Imports

use String::Truncate
 qw(trunc),
 elide => {
    -as    => ‘trail_off’,
    marker => ‘etc’,
 };
Custom Imports

use String::Truncate
 qw(trunc elide),
 elide => {
    -as    => ‘trail_off’,
    marker => ‘etc’,
 };
Custom Imports


use String::Truncate
 trunc => { -as => ‘trunc_str’, length => 10 },
 elide => { -as => ‘elide_str’, length => 10 };
Custom Imports


use String::Truncate
 -all => { -suffix => ‘_str’, length => 10 };
Exports to Order

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
   groups => {
      basic   => [ qw(elide trunc) ],
      default => [ qw(trunc) ]
   },
};
Exports to Order

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [ qw(elide trunc) ],
   groups => {
      basic   => [ qw(elide trunc) ],
      default => [ qw(trunc) ]
   },
};
Exports to Order
package String::Truncate;

use Sub::Exporter -setup => {
   exports => [
      elide => undef,
      trunc => undef,
   ],
   groups => {
      basic   => [ qw(elide trunc) ],
      default => [ qw(trunc) ]
   },
};
Exports to Order
package String::Truncate;

use Sub::Exporter -setup => {
   exports => [
      elide => ’_build_elide’,
      trunc => ’_build_trunc’,
   ],
   groups => {
      basic   => [ qw(elide trunc) ],
      default => [ qw(trunc) ]
   },
};
Generating Routines
Generating Routines
sub _build_trunc {
Generating Routines
sub _build_trunc {
 my ($class, $name, $arg) = @_;
Generating Routines
sub _build_trunc {
 my ($class, $name, $arg) = @_;
 my $_length = $arg->{length};
Generating Routines
sub _build_trunc {
 my ($class, $name, $arg) = @_;
 my $_length = $arg->{length};

return sub {
Generating Routines
sub _build_trunc {
 my ($class, $name, $arg) = @_;
 my $_length = $arg->{length};

return sub {
  my ($string, $length, @rest) = @_;
Generating Routines
sub _build_trunc {
 my ($class, $name, $arg) = @_;
 my $_length = $arg->{length};

return sub {
  my ($string, $length, @rest) = @_;
  $length = $_length if !defined $length;
Generating Routines
sub _build_trunc {
 my ($class, $name, $arg) = @_;
 my $_length = $arg->{length};

return sub {
  my ($string, $length, @rest) = @_;
  $length = $_length if !defined $length;
  trunc($string, $length, @rest);
Generating Routines
sub _build_trunc {
 my ($class, $name, $arg) = @_;
 my $_length = $arg->{length};

return sub {
  my ($string, $length, @rest) = @_;
  $length = $_length if !defined $length;
  trunc($string, $length, @rest);
}
Generating Routines
 sub _build_trunc {
  my ($class, $name, $arg) = @_;
  my $_length = $arg->{length};

    return sub {
      my ($string, $length, @rest) = @_;
      $length = $_length if !defined $length;
      trunc($string, $length, @rest);
    }
}
Routines ex nihilo
Routines ex nihilo
use Cypher::Trivial qw(cyphers);
Routines ex nihilo
use Cypher::Trivial qw(cyphers);

my ($encyph, $decyph) = cyphers(“secret”);
Routines ex nihilo
use Cypher::Trivial qw(cyphers);

my ($encyph, $decyph) = cyphers(“secret”);

$cyphertext
Routines ex nihilo
use Cypher::Trivial qw(cyphers);

my ($encyph, $decyph) = cyphers(“secret”);

$cyphertext
  = $encyph->(“Top secret message.”);
Routines ex nihilo
use Cypher::Trivial qw(cyphers);

my ($encyph, $decyph) = cyphers(“secret”);

$cyphertext
  = $encyph->(“Top secret message.”);

sub encypher {
Routines ex nihilo
use Cypher::Trivial qw(cyphers);

my ($encyph, $decyph) = cyphers(“secret”);

$cyphertext
  = $encyph->(“Top secret message.”);

sub encypher {
  my $text = shift; $encyph->($text);
Routines ex nihilo
use Cypher::Trivial qw(cyphers);

my ($encyph, $decyph) = cyphers(“secret”);

$cyphertext
  = $encyph->(“Top secret message.”);

sub encypher {
  my $text = shift; $encyph->($text);
}
Routines ex nihilo
Routines ex nihilo


use Cypher::Trivial
Routines ex nihilo


use Cypher::Trivial
  encypher => { secret => “secret” };
Routines ex nihilo


use Cypher::Trivial
  encypher => { secret => “secret” };

encypher(“Top secret message”);
Routines ex nihilo
sub _build_encypher {
 my ($class, $name, $arg) = @_;
 my ($enc, $dec) = cyphers($arg->{secret});

 return $enc;
}

sub _build_decypher {
 my ($class, $name, $arg) = @_;
 my ($enc, $dec) = cyphers($arg->{secret});

 return $dec;
}
Routines ex nihilo
sub _build_encypher {
 my ($class, $name, $arg) = @_;
 my ($enc, $dec) = cyphers($arg->{secret});

 return $enc;
}

sub _build_decypher {
 my ($class, $name, $arg) = @_;
 my ($enc, $dec) = cyphers($arg->{secret});

 return $dec;
}
Routines ex nihilo
package Cypher::Trivial;

use Sub::Exporter -setup => {
   exports => [
     encypher => ’_build_encypher’,
     decypher => ’_build_decypher’,
     cyphers => undef,
   ],
   groups => {
      cyphers => [ qw(encypher decypher) ],
   }
};
Routines ex nihilo


use Cypher::Trivial
  encypher => { secret => “secret” };

encypher(“Top secret message”);
Routines ex nihilo

use Cypher::Trivial
  -cyphers => { secret => “secret” };

encypher(“Top secret message”);

decypher(“Gbc frperg zrffntr”);
Generating Groups
package Cypher::Trivial;

use Sub::Exporter -setup => {
   exports => [
     encypher => ’_build_encypher’,
     decypher => ’_build_decypher’,
     cyphers => undef,
   ],
   groups => {
      cyphers => [ qw(encypher decypher) ],
   }
};
Generating Groups
sub _build_encypher {
 my ($class, $name, $arg) = @_;
 my ($enc, $dec) = cyphers($arg->{secret});

 return $enc;
}

sub _build_decypher {
 my ($class, $name, $arg) = @_;
 my ($enc, $dec) = cyphers($arg->{secret});

 return $dec;
}
Generating Groups
package Cypher::Trivial;

use Sub::Exporter -setup => {
   exports => [
     encypher => ’_build_encypher’,
     decypher => ’_build_decypher’,
     cyphers => undef,
   ],
   groups => {
      cyphers => ’_build_cyphers’,
   }
};
Generating Groups
Generating Groups

sub _build_cyphers {
Generating Groups

sub _build_cyphers {
  my ($class, $name, $arg) = @_;
Generating Groups

sub _build_cyphers {
  my ($class, $name, $arg) = @_;

 my %sub;
Generating Groups

sub _build_cyphers {
  my ($class, $name, $arg) = @_;

 my %sub;
 @sub{qw(encypher decypher)}
Generating Groups

sub _build_cyphers {
  my ($class, $name, $arg) = @_;

 my %sub;
 @sub{qw(encypher decypher)}
   = cyphers($arg->{secret});
Generating Groups

sub _build_cyphers {
  my ($class, $name, $arg) = @_;

 my %sub;
 @sub{qw(encypher decypher)}
   = cyphers($arg->{secret});

 return %sub;
Generating Groups

sub _build_cyphers {
  my ($class, $name, $arg) = @_;

    my %sub;
    @sub{qw(encypher decypher)}
      = cyphers($arg->{secret});

    return %sub;
}
Generating Groups


use Cypher::Trivial
  -cyphers => { secret => “secret” };
Generating Groups

use Cypher::Trivial
  -cyphers => { secret => “secret” },

    -cyphers => { secret => ‘Secret1234’,
                  -suffix => ‘_strong’ }
;
Exporting Methods
ZOMG
O NO!
Methods & Exporter.pm
Methods & Exporter.pm


 • Exporter.pm: “Do not export method
   names!”
Methods & Exporter.pm


 • Exporter.pm: “Do not export method
   names!”
 • *{“$::$”}   = &{“$::$”};
Methods & Exporter.pm
package Object::Hybrid;
use Exporter;
@Object::Exporter::ISA = qw(Exporter);

@Object::Exporter::EXPORT_OK = qw(retrieve);

sub retrieve {
  my ($class, $id) = @_;
  my $row = $class->get_row(id => $id);
  bless $row => $class;
}
Methods & Exporter.pm


use Object::Hybrid qw(retrieve);

my $object = retrieve(42);
my $object = retrieve(49);
Methods & Exporter.pm
package Object::Hybrid;
use Exporter;
@Object::Exporter::ISA = qw(Exporter);

@Object::Exporter::EXPORT_OK = qw(object);

sub retrieve {
  my ($class, $id) = @_;
  my $row = $class->get_row(id => $id);
  bless $row => $class;
}

sub object { __PACKAGE__->retrieve(@_) }
Methods & Exporter.pm


use Object::Hybrid qw(object);

my $object = object(42);
my $object = object(49);
Methods & Exporter.pm


use Object::Hybrid;

my $object = Object::Hybrid->object(42);
Methods & Exporter.pm
Methods & Exporter.pm

use Object::Hybrid;
Methods & Exporter.pm

use Object::Hybrid;
use Object::Hybrid::With::Much::Derivation;
Methods & Exporter.pm

use Object::Hybrid;
use Object::Hybrid::With::Much::Derivation;

my $object = Object::Hybrid->retrieve(42);
Methods & Exporter.pm

use Object::Hybrid;
use Object::Hybrid::With::Much::Derivation;

my $object = Object::Hybrid->retrieve(42);

my $thing   =
Methods & Exporter.pm

use Object::Hybrid;
use Object::Hybrid::With::Much::Derivation;

my $object = Object::Hybrid->retrieve(42);

my $thing =
 Object::Hybrid::With::Much::Derivation
Methods & Exporter.pm

use Object::Hybrid;
use Object::Hybrid::With::Much::Derivation;

my $object = Object::Hybrid->retrieve(42);

my $thing =
 Object::Hybrid::With::Much::Derivation
 ->retrieve(49);
Methods & Exporter.pm
package Object::Hybrid;
use Exporter;
@Object::Exporter::ISA = qw(Exporter);

@Object::Exporter::EXPORT_OK = qw(object);

sub retrieve {
  my ($class, $id) = @_;
  my $row = $class->get_row(id => $id);
  bless $row => $class;
}

sub object { __PACKAGE__->retrieve(@_) }
Currying Methods

use Object::Hybrid qw(object);

use Object::Hybrid::With::Much::Derivation
 object => { -as => ‘derived_object’ };

my $object = object(42);
my $thing = derived_object(49);
Currying Methods


use Object::Hybrid qw(object);

my $object = Object::Hybrid->object(49);
Currying Methods
Currying Methods
Currying Methods

use Sub::Exporter -setup => {
Currying Methods

use Sub::Exporter -setup => {
  exports => [ object => &_build_object ],
Currying Methods

use Sub::Exporter -setup => {
   exports => [ object => &_build_object ],
};
Currying Methods

use Sub::Exporter -setup => {
   exports => [ object => &_build_object ],
};

sub _build_object {
Currying Methods

use Sub::Exporter -setup => {
   exports => [ object => &_build_object ],
};

sub _build_object {
  my ($class, $name, $arg) = @_;
Currying Methods

use Sub::Exporter -setup => {
   exports => [ object => &_build_object ],
};

sub _build_object {
  my ($class, $name, $arg) = @_;

  return sub { $class->new(@_); }
Currying Methods

use Sub::Exporter -setup => {
   exports => [ object => &_build_object ],
};

sub _build_object {
  my ($class, $name, $arg) = @_;

    return sub { $class->new(@_); }
}
Currying Methods


use Sub::Exporter -setup => {
  exports => [ object => curry_class(‘new’) ],
}
Currying Methods


use Sub::Exporter::Util qw(curry_class);
use Sub::Exporter -setup => {
  exports => [ object => curry_class(‘new’) ],
}
Exporting Methods
Exporting Methods

• Sometimes you want to export
  methods without currying the class.
Exporting Methods

• Sometimes you want to export
  methods without currying the class.
• Exporters can serve as method
  crafters.
Exporting Methods
package Mixin::Dumper;

use Sub::Exporter -setup => {
   exports => [ qw(dump) ],
   groups => { default => [ qw(dump) ] },
};

sub dump {
  my ($self) = @_;
  require Data::Dumper;
  Data::Dumper::Dumper($self);
}
Exporting Methods
package Email::Simple::mixin:ReplyText;

use Sub::Exporter -setup => {
   exports => [ qw(reply_text) ],
   groups => { defaults => [ qw(reply_text) ] },
};

sub reply_text {
  my ($self) = @_;
  join “n”, map “>$_”, split /n/, $self->body;
}
Exporting Methods
package Email::Simple::mixin:ReplyText;

use Sub::Exporter -setup => {
   into    => ‘Email::Simple’,
   exports => [ qw(reply_text) ],
   groups => { defaults => [ qw(reply_text) ] },
};

sub reply_text {
  my ($self) = @_;
  join “n”, map “>$_”, split /n/, $self->body;
}
Exporting Methods

use Email::Simple;
use Email::Simple::mixin::ReplyText;
Exporting Methods

use Email::Simple;
use Email::Simple::mixin::ReplyText;



use Email::Simple::Mock;
use Email::Simple::mixin::ReplyText
 { into => ‘Email::Simple::Mock’ };
Emulating mixin.pm
Emulating mixin.pm

• Don’t import into my namespace...
Emulating mixin.pm

• Don’t import into my namespace...
• ...import to a new namespace...
Emulating mixin.pm

• Don’t import into my namespace...
• ...import to a new namespace...
• ...and add it to my @ISA.
Emulating mixin.pm
Emulating mixin.pm


• This makes it easy to import a chunk of
  methods and override just a few...
Emulating mixin.pm


• This makes it easy to import a chunk of
  methods and override just a few...
• ...and those few can call SUPER.
Emulating mixin.pm
package Email::Simple::mixin:ReplyText;

use Sub::Exporter -setup => {
   into    => ‘Email::Simple’,
   exports => [ qw(reply_text) ],
   groups => { defaults => [ qw(reply_text) ] },
};

sub reply_text {
  my ($self) = @_;
  join “n”, map “>$_”, split /n/, $self->body;
}
Emulating mixin.pm
package Email::Simple::mixin:ReplyText;

use Sub::Exporter -setup => {
   into    => ‘Email::Simple’,
   exporter=> mixin_exporter,
   exports => [ qw(reply_text) ],
   groups => { defaults => [ qw(reply_text) ] },
};

sub reply_text {
  my ($self) = @_;
  join “n”, map “>$_”, split /n/, $self->body;
}
Collectors
Collectors
Collectors


• Arguments that don’t export anything.
Collectors


• Arguments that don’t export anything.
• They collect data for generators to
  use.
Collectors

package String::Truncate;

use Sub::Exporter -setup => {
   exports => [
      elide => ’_build_elide’,
      trunc => ’_build_trunc’,
   ],
   collectors => [ qw(defaults) ],
};
Collectors
Collectors

use String::Truncate
Collectors

use String::Truncate
  defaults => { length => 10 },
Collectors

use String::Truncate
  defaults => { length => 10 },

 qw(-all),
Collectors

use String::Truncate
  defaults => { length => 10 },

 qw(-all),
 trunc => { length => 1,     -as => ‘onechar’ },
Collectors

use String::Truncate
  defaults => { length => 10 },

 qw(-all),
 trunc => { length => 1,    -as => ‘onechar’ },
 elide => { marker => ‘&c’, -as => ‘yul’     },
Collectors

use String::Truncate
  defaults => { length => 10 },

    qw(-all),
    trunc => { length => 1,    -as => ‘onechar’ },
    elide => { marker => ‘&c’, -as => ‘yul’     },
;
Collectors
 sub _build_trunc {
  my ($class, $name, $arg) = @_;
  my $_length = $arg->{length};

    return sub {
      my ($string, $length, @rest) = @_;
      $length = $_length if !defined $length;
      trunc($string, $length, @rest);
    }
}
Collectors
 sub _build_trunc {
  my ($class, $name, $arg, $col) = @_;
  my $_length = $arg->{length};

    return sub {
      my ($string, $length, @rest) = @_;
      $length = $_length if !defined $length;
      trunc($string, $length, @rest);
    }
}
Collectors
 sub _build_trunc {
  my ($class, $name, $arg, $col) = @_;
  my $_length = $arg->{length};

    $_length = $col->{defaults}{length}
      if !defined $_length;

    return sub {
      my ($string, $length, @rest) = @_;
      $length = $_length if !defined $length;
      trunc($string, $length, @rest);
    }
}
Collectors
Collectors

• Arguments that don’t export.
• They collect data for generators to
  use.
Collectors

• Arguments that don’t export.
• They collect data for generators to
  use.
• They can validate the collected data.
Collectors
package String::Truncate;

use Sub::Exporter -setup => {
   exports => [
      elide => ’_build_elide’,
      trunc => ’_build_trunc’,
   ],
   collectors => {
      defaults => ’_validate_defaults’,
   },
};
Collectors


sub _validate_defaults {
  my ($class, $value, $data) = @_;
  return (ref $value eq ‘HASH’);
}
Collectors
Collectors

• Arguments that don’t export.
• They collect data for generators to
  use.
• They can validate the collected data.
Collectors

• Arguments that don’t export.
• They collect data for generators to
  use.
• They can validate the collected data.
• They can do Almost Anything Else.
Collectors


sub _validate_defaults {
  my ($class, $value, $data) = @_;
  return (ref $value eq ‘HASH’);
}
Collectors
Collectors

• name - name of the collection
Collectors

• name - name of the collection
• class - invocant of import method
Collectors

• name - name of the collection
• class - invocant of import method
• config - exporter configuration
Collectors

• name - name of the collection
• class - invocant of import method
• config - exporter configuration
• into - the package that’s importing
Collectors

• name - name of the collection
• class - invocant of import method
• config - exporter configuration
• into - the package that’s importing
• import_args - args to import method
Collectors
Collectors


• name - the name of the collection
Collectors


• name - the name of the collection
• class - import’s invocant
Collectors
Collectors

• config - the Sub::Exporter config
Collectors

• config - the Sub::Exporter config
 • find out what exports exist
Collectors

• config - the Sub::Exporter config
 • find out what exports exist
 • validate collection value based on
    config
use LWP::Simple “/^is_/”;

is_success($res);
is_failure($res);
use LWP::Simpleton;

use Sub::Exporter -setup => {
   collectors => {
     like => Sub::Exporter::Util::like
   },
};
use LWP::Simple like => qr/^is_/;

is_success($res);
is_failure($res);
use LWP::Simple like => [
   qr/^is_/, undef,
   qr/^get/, { -prefix => ‘https_’, ssl => 1 }
];

is_success($res);
is_failure($res);

https_get(“https://codesimply.com”)
Collectors
Collectors

• into - the target to which exports go
Collectors

• into - the target to which exports go
 • alter the class directly
Collectors

• into - the target to which exports go
 • alter the class directly
 • particularly useful: @ISA
Crafting Custom Interfaces with Sub::Exporter
sub _make_base {
  my ($class, $value, $data) = @_;

    my $target = $data->{into};
    push @{“$target::ISA”}, $class;
}
sub _make_base {
  my ($class, $value, $data) = @_;

    my $target = $data->{into};
    push @{“$target::ISA”}, $class;
}



use Sub::Exporter -setup => {
   collectors => { base => ’_make_base’ },
};
sub _make_base {
  my ($class, $value, $data) = @_;

    my $target = $data->{into};
    push @{“$target::ISA”}, $class;
}



use Sub::Exporter -setup => {
   collectors => { base => ’_make_base’ },
};



use Magic::Superclass -base;
Crafting Custom Interfaces with Sub::Exporter
package Email::Constants;

sub _set_constants {
  my ($class, $value, $data) = @_;

    Package::Generator->assign_symbols(
      $data->{into},
      [
        EX_TEMPFAIL => 75,
        FORMATS      => [ qw(Maildir mbox mh) ],
      ],
    );
}
package Email::Constants;

sub _set_constants {
  my ($class, $value, $data) = @_;

    Package::Generator->assign_symbols(
      $data->{into},
      [
        EX_TEMPFAIL => 75,
        FORMATS      => [ qw(Maildir mbox mh) ],
      ],
    );
}



use Sub::Exporter -setup => {
   collectors => { constants => ’_set_constants’ },
};
use Email::Constants qw(constants);
Collectors
Collectors

• import_args - the arguments to import
Collectors

• import_args - the arguments to import
   • rewrite the arguments list
Collectors

• import_args - the arguments to import
   • rewrite the arguments list
   • add new imports
Crafting Custom Interfaces with Sub::Exporter
sub _setup {
sub _setup {
  my ($class, $value, $data) = @_;
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;

  } elsif (ref $value eq ‘ARRAY’) {
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;

  } elsif (ref $value eq ‘ARRAY’) {
    push @{ $data->{import_args} },
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;

  } elsif (ref $value eq ‘ARRAY’) {
    push @{ $data->{import_args} },
      [ _import => {
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;

  } elsif (ref $value eq ‘ARRAY’) {
    push @{ $data->{import_args} },
      [ _import => {
         -as => ‘import’, exports => $value } ];
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;

  } elsif (ref $value eq ‘ARRAY’) {
    push @{ $data->{import_args} },
      [ _import => {
         -as => ‘import’, exports => $value } ];
    return 1;
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;

  } elsif (ref $value eq ‘ARRAY’) {
    push @{ $data->{import_args} },
      [ _import => {
         -as => ‘import’, exports => $value } ];
    return 1;
  }
sub _setup {
  my ($class, $value, $data) = @_;

  if (ref $value eq ‘HASH’) {
    push @{ $data->{import_args} },
      [ _import => { -as => ‘import’, %$value } ];
    return 1;

  } elsif (ref $value eq ‘ARRAY’) {
    push @{ $data->{import_args} },
      [ _import => {
          -as => ‘import’, exports => $value } ];
    return 1;
  }
  return;
sub _setup {
  my ($class, $value, $data) = @_;

    if (ref $value eq ‘HASH’) {
      push @{ $data->{import_args} },
        [ _import => { -as => ‘import’, %$value } ];
      return 1;

    } elsif (ref $value eq ‘ARRAY’) {
      push @{ $data->{import_args} },
        [ _import => {
            -as => ‘import’, exports => $value } ];
      return 1;
    }
    return;
}
Crafting Custom Interfaces with Sub::Exporter
use Sub::Exporter -setup => {
use Sub::Exporter -setup => {
  collectors => { -setup => ’_setup’ },
use Sub::Exporter -setup => {
  collectors => { -setup => ’_setup’ },
  exports    => [ _import => ’_build_import’ ],
use Sub::Exporter -setup => {
  collectors => { -setup => ’_setup’ },
  exports    => [ _import => ’_build_import’ ],
});
-setup => { into_level => 2, exports => [qw(foo)] }
-setup => { into_level => 2, exports => [qw(foo)] }




_import => {
  -as        => ‘import’,
  into_level => 2,
  exports    => [qw(foo)]
}
-setup => [ qw(foo bar baz) ]
-setup => [ qw(foo bar baz) ]




_import => {
  -as     => ‘import’,
  exports => [qw(foo bar baz)]
}
use Sub::Exporter -setup => {
  collectors => { -setup => ’_setup’ },
  exports    => [ _import => ’_build_import’ ],
});
use Sub::Exporter -setup => {
  collectors => { -setup => ’_setup’ },
  exports    => [
    _import => sub {
      my ($class, $name, $arg) = @_;
      build_exporter($arg);
    },
  ],
});
package Sub::Exporter;

use Sub::Exporter -setup => {
  collectors => { -setup => &_setup },
  exports => [
    _import => sub {
      my ($class, $name, $arg) = @_;
      build_exporter($arg);
    },
  ],
});
RJBS’s Advice
RJBS’s Advice

• Write the client code first.
RJBS’s Advice

• Write the client code first.
• Make as many assumptions as possible.
RJBS’s Advice

• Write the client code first.
• Make as many assumptions as possible.
• Let most of them be refuted.
Any
Questions?
Random Tricks
Mixed-in Helpers


$object->complex_method($arg);
Mixed-in Helpers
sub _build_cplx_method {
  my ($mixin) = @_;
  sub {
    my ($self, $arg) = @_;
    $mixin->validate_arg($arg);
    $mixin->do_stuff($self, $arg);
    return $mixin->analyze($self);
  }
}

sub validate_arg {...}
Mixed-in Helpers
package Mixin::Helper;

use Sub::Exporter -setup => {
 exports => [
    complex_method => ’_build_cplx_method’,
 ],
};

sub _build_cplx_method {
  ...
Mixed-in Helpers
sub _build_cplx_method {
  my ($mixin) = @_;
  sub {
    my ($self, $arg) = @_;
    $mixin->validate_arg($arg);
    $mixin->do_stuff($self, $arg);
    return $mixin->analyze($self);
  }
}

sub validate_arg {...}
Mixed-in Helpers

package Mixin::Helper::Faster;
use base qw(Mixin::Helper);

sub analyze {
  my ($mixin, $object) = @_;
  return 1;
}

1;
A Coderef Generator
A Coderef Generator


use String::Truncate ();
A Coderef Generator


use String::Truncate ();

my $trunc;
A Coderef Generator


use String::Truncate ();

my $trunc;
String::Truncate->import(
A Coderef Generator


use String::Truncate ();

my $trunc;
String::Truncate->import(trunc =>
A Coderef Generator


use String::Truncate ();

my $trunc;
String::Truncate->import(trunc => { -as => $trunc });
Accessors sans ISA


package YAPC::Slideshow;
use Accessors::Simple -setup => {
   fields => [ qw(topic presenter timeslot room) ],
};
Accessors sans ISA
Accessors sans ISA
sub _make_accessor {
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}

sub _make_many_accessors {
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}

sub _make_many_accessors {
  my @fields = @{ $arg->{fields} };
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}

sub _make_many_accessors {
  my @fields = @{ $arg->{fields} };
  my %sub = map { $_ => _make_accessor($_) } @fields;
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}

sub _make_many_accessors {
  my @fields = @{ $arg->{fields} };
  my %sub = map { $_ => _make_accessor($_) } @fields;
  return %sub;
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}

sub _make_many_accessors {
  my @fields = @{ $arg->{fields} };
  my %sub = map { $_ => _make_accessor($_) } @fields;
  return %sub;
}
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}

sub _make_many_accessors {
  my @fields = @{ $arg->{fields} };
  my %sub = map { $_ => _make_accessor($_) } @fields;
  return %sub;
}

use Sub::Exporter -setup =>
Accessors sans ISA
sub _make_accessor {
  my ($field) = @_;
  sub {
    my ($self) = shift;
    $self->{field} = shift if @_;
    return $self->{$field};
  }
}

sub _make_many_accessors {
  my @fields = @{ $arg->{fields} };
  my %sub = map { $_ => _make_accessor($_) } @fields;
  return %sub;
}

use Sub::Exporter -setup =>
  { groups => { setup => &_make_many_accessors } };
Eat Exporter’s Brain
Eat Exporter’s Brain
sub exporter_upgrade {
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
  });
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
  });

  push @{“$new_pkg::ISA”}, $class;
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
  });

  push @{“$new_pkg::ISA”}, $class;
  return $new_pkg;
Eat Exporter’s Brain
sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

    Sub::Exporter::setup_exporter({
      as      => ‘import’,
      into    => $new_pkg,
      exports => [ @{“$pkg::EXPORT_OK”} ],
      groups => {
        %{“$pkg::EXPORT_TAGS”},
        default => [ @{“$pkg::EXPORTS”} ],
      },
    });

    push @{“$new_pkg::ISA”}, $class;
    return $new_pkg;
}
Crafting Custom Interfaces with Sub::Exporter
package UNIVERSAL;
package UNIVERSAL;

sub exporter_upgrade {
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
  });
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
  });

  push @{“$new_pkg::ISA”}, $class;
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

  return $new_pkg if $new_pkg->isa($pkg);

  Sub::Exporter::setup_exporter({
    as      => ‘import’,
    into    => $new_pkg,
    exports => [ @{“$pkg::EXPORT_OK”} ],
    groups => {
      %{“$pkg::EXPORT_TAGS”},
      default => [ @{“$pkg::EXPORTS”} ],
    },
  });

  push @{“$new_pkg::ISA”}, $class;
  return $new_pkg;
package UNIVERSAL;

sub exporter_upgrade {
  my ($pkg) = @_;
  my $new_pkg = “$pkg::SE”;

    return $new_pkg if $new_pkg->isa($pkg);

    Sub::Exporter::setup_exporter({
      as      => ‘import’,
      into    => $new_pkg,
      exports => [ @{“$pkg::EXPORT_OK”} ],
      groups => {
        %{“$pkg::EXPORT_TAGS”},
        default => [ @{“$pkg::EXPORTS”} ],
      },
    });

    push @{“$new_pkg::ISA”}, $class;
    return $new_pkg;
}
Fixing caller
Fixing caller

sub default_exporter {
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 _install(
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 _install(
   _generate($class, $generator, $name, $arg, $col),
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 _install(
   _generate($class, $generator, $name, $arg, $col),
   $into,
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 _install(
   _generate($class, $generator, $name, $arg, $col),
   $into,
   $as,
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 _install(
   _generate($class, $generator, $name, $arg, $col),
   $into,
   $as,
 );
Fixing caller

sub default_exporter {
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

    _install(
      _generate($class, $generator, $name, $arg, $col),
      $into,
      $as,
    );
}
Crafting Custom Interfaces with Sub::Exporter
sub evil_eval_exporter { # TOTALLY UNTESTED!
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
   &{“$into::_generate”};
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
   &{“$into::_generate”};
 };
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
   &{“$into::_generate”};
 };

 _install(
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
   &{“$into::_generate”};
 };

 _install(
   $col->{_g}($class, $generator, $name, $arg, $col),
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
   &{“$into::_generate”};
 };

 _install(
   $col->{_g}($class, $generator, $name, $arg, $col),
   $into,
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
   &{“$into::_generate”};
 };

 _install(
   $col->{_g}($class, $generator, $name, $arg, $col),
   $into,
   $as,
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

 $col->{_g} ||= do {
   my $g = Dump(&_generate)->Names(‘GEN’)->Out;
   $g =~ s/A$GEN = sub/sub _generate/;
   $g = “package $into;n$g”;
   eval $g;
   &{“$into::_generate”};
 };

 _install(
   $col->{_g}($class, $generator, $name, $arg, $col),
   $into,
   $as,
 );
sub evil_eval_exporter { # TOTALLY UNTESTED!
  my ($class, $gen, $name, $arg, $col, $as, $into)
    = @_;

    $col->{_g} ||= do {
      my $g = Dump(&_generate)->Names(‘GEN’)->Out;
      $g =~ s/A$GEN = sub/sub _generate/;
      $g = “package $into;n$g”;
      eval $g;
      &{“$into::_generate”};
    };

    _install(
      $col->{_g}($class, $generator, $name, $arg, $col),
      $into,
      $as,
    );
}
Thank
 You!
1 of 371

Recommended

Refactor like a boss by
Refactor like a bossRefactor like a boss
Refactor like a bossgsterndale
592 views71 slides
Perl6 grammars by
Perl6 grammarsPerl6 grammars
Perl6 grammarsAndrew Shitov
10.1K views89 slides
PHP 7 – What changed internally? (Forum PHP 2015) by
PHP 7 – What changed internally? (Forum PHP 2015)PHP 7 – What changed internally? (Forum PHP 2015)
PHP 7 – What changed internally? (Forum PHP 2015)Nikita Popov
7.5K views83 slides
Electrify your code with PHP Generators by
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP GeneratorsMark Baker
3.7K views49 slides
PHP Language Trivia by
PHP Language TriviaPHP Language Trivia
PHP Language TriviaNikita Popov
15K views77 slides
Looping the Loop with SPL Iterators by
Looping the Loop with SPL IteratorsLooping the Loop with SPL Iterators
Looping the Loop with SPL IteratorsMark Baker
240 views142 slides

More Related Content

What's hot

PHP tips and tricks by
PHP tips and tricks PHP tips and tricks
PHP tips and tricks Damien Seguy
13.4K views36 slides
PHP 5.4 by
PHP 5.4PHP 5.4
PHP 5.4Federico Damián Lozada Mosto
1.1K views42 slides
R57shell by
R57shellR57shell
R57shellady36
3.3K views154 slides
Perl Web Client by
Perl Web ClientPerl Web Client
Perl Web ClientFlavio Poletti
1.7K views183 slides
Symfony2 - extending the console component by
Symfony2 - extending the console componentSymfony2 - extending the console component
Symfony2 - extending the console componentHugo Hamon
6.8K views76 slides
An Elephant of a Different Colour: Hack by
An Elephant of a Different Colour: HackAn Elephant of a Different Colour: Hack
An Elephant of a Different Colour: HackVic Metcalfe
2.1K views30 slides

What's hot(20)

PHP tips and tricks by Damien Seguy
PHP tips and tricks PHP tips and tricks
PHP tips and tricks
Damien Seguy13.4K views
R57shell by ady36
R57shellR57shell
R57shell
ady363.3K views
Symfony2 - extending the console component by Hugo Hamon
Symfony2 - extending the console componentSymfony2 - extending the console component
Symfony2 - extending the console component
Hugo Hamon6.8K views
An Elephant of a Different Colour: Hack by Vic Metcalfe
An Elephant of a Different Colour: HackAn Elephant of a Different Colour: Hack
An Elephant of a Different Colour: Hack
Vic Metcalfe2.1K views
Doctrine MongoDB ODM (PDXPHP) by Kris Wallsmith
Doctrine MongoDB ODM (PDXPHP)Doctrine MongoDB ODM (PDXPHP)
Doctrine MongoDB ODM (PDXPHP)
Kris Wallsmith2.1K views
Introdução ao Perl 6 by garux
Introdução ao Perl 6Introdução ao Perl 6
Introdução ao Perl 6
garux1.8K views
The Joy of Smartmatch by Andrew Shitov
The Joy of SmartmatchThe Joy of Smartmatch
The Joy of Smartmatch
Andrew Shitov1.2K views
2014 database - course 2 - php by Hung-yu Lin
2014 database - course 2 - php2014 database - course 2 - php
2014 database - course 2 - php
Hung-yu Lin2.8K views
Python Ireland Nov 2010 Talk: Unit Testing by Python Ireland
Python Ireland Nov 2010 Talk: Unit TestingPython Ireland Nov 2010 Talk: Unit Testing
Python Ireland Nov 2010 Talk: Unit Testing
Python Ireland603 views
PHPUnit でよりよくテストを書くために by Yuya Takeyama
PHPUnit でよりよくテストを書くためにPHPUnit でよりよくテストを書くために
PHPUnit でよりよくテストを書くために
Yuya Takeyama9.7K views
Corephpcomponentpresentation 1211425966721657-8 by PrinceGuru MS
Corephpcomponentpresentation 1211425966721657-8Corephpcomponentpresentation 1211425966721657-8
Corephpcomponentpresentation 1211425966721657-8
PrinceGuru MS491 views
Proposal for xSpep BDD Framework for PHP by Yuya Takeyama
Proposal for xSpep BDD Framework for PHPProposal for xSpep BDD Framework for PHP
Proposal for xSpep BDD Framework for PHP
Yuya Takeyama978 views
Ae internals by mnikolenko
Ae internalsAe internals
Ae internals
mnikolenko782 views
Advanced symfony Techniques by Kris Wallsmith
Advanced symfony TechniquesAdvanced symfony Techniques
Advanced symfony Techniques
Kris Wallsmith5.4K views

Viewers also liked

Meisje van de Slijterij over Business blogs by
Meisje van de Slijterij over Business blogsMeisje van de Slijterij over Business blogs
Meisje van de Slijterij over Business blogsErno Hannink
460 views20 slides
Glogster by
GlogsterGlogster
GlogsterMarinale
547 views19 slides
Navigating the recovery gap: aid flows to the Central African Republic in 200... by
Navigating the recovery gap: aid flows to the Central African Republic in 200...Navigating the recovery gap: aid flows to the Central African Republic in 200...
Navigating the recovery gap: aid flows to the Central African Republic in 200...hdptcar
550 views25 slides
Perl 5.12 for Everyday Use by
Perl 5.12 for Everyday UsePerl 5.12 for Everyday Use
Perl 5.12 for Everyday UseRicardo Signes
1K views155 slides
Busquem imatges by
Busquem imatgesBusquem imatges
Busquem imatgesMarinale
585 views15 slides
EmployWise Presentation by
EmployWise PresentationEmployWise Presentation
EmployWise PresentationSumeet Kapur
1.3K views29 slides

Viewers also liked(6)

Meisje van de Slijterij over Business blogs by Erno Hannink
Meisje van de Slijterij over Business blogsMeisje van de Slijterij over Business blogs
Meisje van de Slijterij over Business blogs
Erno Hannink460 views
Glogster by Marinale
GlogsterGlogster
Glogster
Marinale547 views
Navigating the recovery gap: aid flows to the Central African Republic in 200... by hdptcar
Navigating the recovery gap: aid flows to the Central African Republic in 200...Navigating the recovery gap: aid flows to the Central African Republic in 200...
Navigating the recovery gap: aid flows to the Central African Republic in 200...
hdptcar550 views
Busquem imatges by Marinale
Busquem imatgesBusquem imatges
Busquem imatges
Marinale585 views
EmployWise Presentation by Sumeet Kapur
EmployWise PresentationEmployWise Presentation
EmployWise Presentation
Sumeet Kapur1.3K views

Similar to Crafting Custom Interfaces with Sub::Exporter

Good Evils In Perl (Yapc Asia) by
Good Evils In Perl (Yapc Asia)Good Evils In Perl (Yapc Asia)
Good Evils In Perl (Yapc Asia)Kang-min Liu
8.9K views122 slides
Perl Bag of Tricks - Baltimore Perl mongers by
Perl Bag of Tricks  -  Baltimore Perl mongersPerl Bag of Tricks  -  Baltimore Perl mongers
Perl Bag of Tricks - Baltimore Perl mongersbrian d foy
2.3K views26 slides
Php tips-and-tricks4128 by
Php tips-and-tricks4128Php tips-and-tricks4128
Php tips-and-tricks4128PrinceGuru MS
854 views36 slides
Bag of tricks by
Bag of tricksBag of tricks
Bag of tricksbrian d foy
4.4K views32 slides
Modern Perl by
Modern PerlModern Perl
Modern PerlMarcos Rebelo
1.8K views44 slides
Wx::Perl::Smart by
Wx::Perl::SmartWx::Perl::Smart
Wx::Perl::Smartlichtkind
766 views108 slides

Similar to Crafting Custom Interfaces with Sub::Exporter(20)

Good Evils In Perl (Yapc Asia) by Kang-min Liu
Good Evils In Perl (Yapc Asia)Good Evils In Perl (Yapc Asia)
Good Evils In Perl (Yapc Asia)
Kang-min Liu8.9K views
Perl Bag of Tricks - Baltimore Perl mongers by brian d foy
Perl Bag of Tricks  -  Baltimore Perl mongersPerl Bag of Tricks  -  Baltimore Perl mongers
Perl Bag of Tricks - Baltimore Perl mongers
brian d foy2.3K views
Bag of tricks by brian d foy
Bag of tricksBag of tricks
Bag of tricks
brian d foy4.4K views
Wx::Perl::Smart by lichtkind
Wx::Perl::SmartWx::Perl::Smart
Wx::Perl::Smart
lichtkind766 views
Metaprogramming in Haskell by Hiromi Ishii
Metaprogramming in HaskellMetaprogramming in Haskell
Metaprogramming in Haskell
Hiromi Ishii4.2K views
Good Evils In Perl by Kang-min Liu
Good Evils In PerlGood Evils In Perl
Good Evils In Perl
Kang-min Liu13.8K views
Writing Modular Command-line Apps with App::Cmd by Ricardo Signes
Writing Modular Command-line Apps with App::CmdWriting Modular Command-line Apps with App::Cmd
Writing Modular Command-line Apps with App::Cmd
Ricardo Signes6.7K views
Writing Maintainable Perl by tinypigdotcom
Writing Maintainable PerlWriting Maintainable Perl
Writing Maintainable Perl
tinypigdotcom1.3K views
SWP - A Generic Language Parser by kamaelian
SWP - A Generic Language ParserSWP - A Generic Language Parser
SWP - A Generic Language Parser
kamaelian1.4K views
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011 by Masahiro Nagano
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Designing Opeation Oriented Web Applications / YAPC::Asia Tokyo 2011
Masahiro Nagano2.6K views
Barely Legal Xxx Perl Presentation by Attila Balazs
Barely Legal Xxx Perl PresentationBarely Legal Xxx Perl Presentation
Barely Legal Xxx Perl Presentation
Attila Balazs4.3K views
Blog Hacks 2011 by Yusuke Wada
Blog Hacks 2011Blog Hacks 2011
Blog Hacks 2011
Yusuke Wada2.7K views
PHP Functions & Arrays by Henry Osborne
PHP Functions & ArraysPHP Functions & Arrays
PHP Functions & Arrays
Henry Osborne3.2K views
Functional Pe(a)rls version 2 by osfameron
Functional Pe(a)rls version 2Functional Pe(a)rls version 2
Functional Pe(a)rls version 2
osfameron799 views
C A S Sample Php by JH Lee
C A S Sample PhpC A S Sample Php
C A S Sample Php
JH Lee914 views

More from Ricardo Signes

Perl 5: Today, Tomorrow, and Christmas by
Perl 5: Today, Tomorrow, and ChristmasPerl 5: Today, Tomorrow, and Christmas
Perl 5: Today, Tomorrow, and ChristmasRicardo Signes
1.7K views261 slides
What's New in Perl? v5.10 - v5.16 by
What's New in Perl?  v5.10 - v5.16What's New in Perl?  v5.10 - v5.16
What's New in Perl? v5.10 - v5.16Ricardo Signes
2.4K views214 slides
Perl 5.14 for Pragmatists by
Perl 5.14 for PragmatistsPerl 5.14 for Pragmatists
Perl 5.14 for PragmatistsRicardo Signes
2.2K views207 slides
Dist::Zilla - Maximum Overkill for CPAN Distributions by
Dist::Zilla - Maximum Overkill for CPAN DistributionsDist::Zilla - Maximum Overkill for CPAN Distributions
Dist::Zilla - Maximum Overkill for CPAN DistributionsRicardo Signes
1.5K views226 slides
i <3 email by
i <3 emaili <3 email
i <3 emailRicardo Signes
1.1K views107 slides
Dist::Zilla by
Dist::ZillaDist::Zilla
Dist::ZillaRicardo Signes
2.5K views59 slides

More from Ricardo Signes(9)

Perl 5: Today, Tomorrow, and Christmas by Ricardo Signes
Perl 5: Today, Tomorrow, and ChristmasPerl 5: Today, Tomorrow, and Christmas
Perl 5: Today, Tomorrow, and Christmas
Ricardo Signes1.7K views
What's New in Perl? v5.10 - v5.16 by Ricardo Signes
What's New in Perl?  v5.10 - v5.16What's New in Perl?  v5.10 - v5.16
What's New in Perl? v5.10 - v5.16
Ricardo Signes2.4K views
Perl 5.14 for Pragmatists by Ricardo Signes
Perl 5.14 for PragmatistsPerl 5.14 for Pragmatists
Perl 5.14 for Pragmatists
Ricardo Signes2.2K views
Dist::Zilla - Maximum Overkill for CPAN Distributions by Ricardo Signes
Dist::Zilla - Maximum Overkill for CPAN DistributionsDist::Zilla - Maximum Overkill for CPAN Distributions
Dist::Zilla - Maximum Overkill for CPAN Distributions
Ricardo Signes1.5K views
Antediluvian Unix: A Guide to Unix Fundamentals by Ricardo Signes
Antediluvian Unix: A Guide to Unix FundamentalsAntediluvian Unix: A Guide to Unix Fundamentals
Antediluvian Unix: A Guide to Unix Fundamentals
Ricardo Signes6.2K views
Perl 5.10 for People Who Aren't Totally Insane by Ricardo Signes
Perl 5.10 for People Who Aren't Totally InsanePerl 5.10 for People Who Aren't Totally Insane
Perl 5.10 for People Who Aren't Totally Insane
Ricardo Signes89.6K views
How I Learned to Stop Worrying and Love Email::: The 2007 PEP Talk!! by Ricardo Signes
How I Learned to Stop Worrying and Love Email::: The 2007 PEP Talk!!How I Learned to Stop Worrying and Love Email::: The 2007 PEP Talk!!
How I Learned to Stop Worrying and Love Email::: The 2007 PEP Talk!!
Ricardo Signes1.7K views

Recently uploaded

NTGapps NTG LowCode Platform by
NTGapps NTG LowCode Platform NTGapps NTG LowCode Platform
NTGapps NTG LowCode Platform Mustafa Kuğu
423 views30 slides
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas... by
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...Bernd Ruecker
54 views69 slides
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f... by
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...TrustArc
170 views29 slides
The Power of Heat Decarbonisation Plans in the Built Environment by
The Power of Heat Decarbonisation Plans in the Built EnvironmentThe Power of Heat Decarbonisation Plans in the Built Environment
The Power of Heat Decarbonisation Plans in the Built EnvironmentIES VE
79 views20 slides
"Surviving highload with Node.js", Andrii Shumada by
"Surviving highload with Node.js", Andrii Shumada "Surviving highload with Node.js", Andrii Shumada
"Surviving highload with Node.js", Andrii Shumada Fwdays
56 views29 slides
CloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlue by
CloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlueCloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlue
CloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlueShapeBlue
135 views13 slides

Recently uploaded(20)

NTGapps NTG LowCode Platform by Mustafa Kuğu
NTGapps NTG LowCode Platform NTGapps NTG LowCode Platform
NTGapps NTG LowCode Platform
Mustafa Kuğu423 views
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas... by Bernd Ruecker
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...
iSAQB Software Architecture Gathering 2023: How Process Orchestration Increas...
Bernd Ruecker54 views
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f... by TrustArc
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...
TrustArc170 views
The Power of Heat Decarbonisation Plans in the Built Environment by IES VE
The Power of Heat Decarbonisation Plans in the Built EnvironmentThe Power of Heat Decarbonisation Plans in the Built Environment
The Power of Heat Decarbonisation Plans in the Built Environment
IES VE79 views
"Surviving highload with Node.js", Andrii Shumada by Fwdays
"Surviving highload with Node.js", Andrii Shumada "Surviving highload with Node.js", Andrii Shumada
"Surviving highload with Node.js", Andrii Shumada
Fwdays56 views
CloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlue by ShapeBlue
CloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlueCloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlue
CloudStack Managed User Data and Demo - Harikrishna Patnala - ShapeBlue
ShapeBlue135 views
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or... by ShapeBlue
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...
ShapeBlue198 views
Extending KVM Host HA for Non-NFS Storage - Alex Ivanov - StorPool by ShapeBlue
Extending KVM Host HA for Non-NFS Storage -  Alex Ivanov - StorPoolExtending KVM Host HA for Non-NFS Storage -  Alex Ivanov - StorPool
Extending KVM Host HA for Non-NFS Storage - Alex Ivanov - StorPool
ShapeBlue123 views
Business Analyst Series 2023 - Week 4 Session 7 by DianaGray10
Business Analyst Series 2023 -  Week 4 Session 7Business Analyst Series 2023 -  Week 4 Session 7
Business Analyst Series 2023 - Week 4 Session 7
DianaGray10139 views
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R... by ShapeBlue
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...
ShapeBlue173 views
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue by ShapeBlue
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlueVNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue
ShapeBlue203 views
What’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlue by ShapeBlue
What’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlueWhat’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlue
What’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlue
ShapeBlue263 views
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue by ShapeBlue
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue
ShapeBlue147 views
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue by ShapeBlue
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlueCloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue
ShapeBlue138 views
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT by ShapeBlue
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBITUpdates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT
ShapeBlue206 views
The Role of Patterns in the Era of Large Language Models by Yunyao Li
The Role of Patterns in the Era of Large Language ModelsThe Role of Patterns in the Era of Large Language Models
The Role of Patterns in the Era of Large Language Models
Yunyao Li85 views
State of the Union - Rohit Yadav - Apache CloudStack by ShapeBlue
State of the Union - Rohit Yadav - Apache CloudStackState of the Union - Rohit Yadav - Apache CloudStack
State of the Union - Rohit Yadav - Apache CloudStack
ShapeBlue297 views
Why and How CloudStack at weSystems - Stephan Bienek - weSystems by ShapeBlue
Why and How CloudStack at weSystems - Stephan Bienek - weSystemsWhy and How CloudStack at weSystems - Stephan Bienek - weSystems
Why and How CloudStack at weSystems - Stephan Bienek - weSystems
ShapeBlue238 views
DRBD Deep Dive - Philipp Reisner - LINBIT by ShapeBlue
DRBD Deep Dive - Philipp Reisner - LINBITDRBD Deep Dive - Philipp Reisner - LINBIT
DRBD Deep Dive - Philipp Reisner - LINBIT
ShapeBlue180 views
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ... by ShapeBlue
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
ShapeBlue184 views

Crafting Custom Interfaces with Sub::Exporter