ACME-QuoteDB

 view release on metacpan or  search on metacpan

Build.PL  view on Meta::CPAN

#TODO give user choices about installing the quotes database
#i.e. location/permissions/owner, others?

use strict;
use warnings;
use Module::Build;

my $class = Module::Build->subclass(code => <<'EOF');
   use File::Spec;
   use File::Copy;
   use File::Basename qw/dirname/;

   sub process_db_files {
      my $self = shift;
      my $qdb = File::Spec->catfile(qw(lib ACME QuoteDB DB quotedb), 'quotes.db');
      my $_t = File::Spec->catfile(qw(blib lib ACME QuoteDB DB quotedb), 'quotes.db');
      mkdir dirname($_t);
      chmod(0777, dirname($_t));
      copy($qdb, $_t);
      chmod(0666, $_t);
   }


   sub ACTION_install {
       my $self = shift;
       $self->SUPER::ACTION_install(@_);
       my $ddir =  $self->install_destination('lib');
       #TODO give user choices about installing the quotes database
       #my $ques = 'Where do you want to install the quotes database?';
       #my $quote_dest = $self->prompt($ques, $ddir);
       my $db = q{};
       my $perms = 0666;
       my $d_perms = 0777;
       #if ($quote_dest != $ddir) {
       #    die $! unless -d dirname $quote_dest;
       #    $db = $quote_dest;
       #    $perms = $self->prompt('File Permissions for the quotes database?',
       #                   $perms);
       #}
       if (!$db) {
         $db =
           File::Spec->catfile($ddir, 'ACME', 'QuoteDB', 'DB', 'quotedb', 'quotes.db');
       }
       ##chown('THIS_UNIX_USER' $db);
       ## prompt for the chown
       # XXX we need to change/fix this
       # ideas, let installer/user decide
       # create a new system user
       # others?
       ## create quotes db as world writable,... yikes
       chmod($d_perms, dirname($db));
       chmod($perms, $db);
   }
   
EOF

my $builder = $class->new(
    module_name         => 'ACME::QuoteDB',
    license             => 'perl',
    dist_author         => 'David Wright <david_v_wright@yahoo.com>',
    dist_version_from   => 'lib/ACME/QuoteDB.pm',
    build_requires => {
        #'Module::Build'    => '0.33',
        'Module::Build'    => '0.280801',
        'Test::More'       => 0.8,
        'Readonly'         => 1.03,
        'Data::Dumper'     => 2.121,
        'Carp'             => 1.04,
        #'criticism'        => 1.02,
        'version'          => 0.70,
        'aliased'          => 0.22,
        'File::Basename'   => 2.74,
        'DBD::SQLite'      => 1.14,
        'Class::DBI'       => '3.0.17',
        'HTML::TokeParser' => 2.37,
        'Text::CSV'        => 1.06, #1.10, # 1.13
        'Cwd'              => 3.25, #3.29
        'File::Spec'       => 3.2501, #3.29
        'File::Copy'       => 2.11,
        #'Test::Pod'       => 1.22
        #'Test::Pod::Coverage'  => 1.08
    },
    add_to_cleanup      => [ 'ACME-QuoteDB-*' ],
    # can't install quotes.db with nec's perms with ExtUtils::MakeMaker
    # (well, or can, but too hard figure out at the moment)
    #create_makefile_pl => 'traditional',
    create_makefile_pl => 'passthrough',
    db_files => {'lib/ACME/QuoteDB/DB/quotedb/quotes.db' => 
                    'lib/ACME/QuoteDB/DB/quotedb/quotes.db'},

);

$builder->add_build_element('db');
$builder->create_build_script();

Changes  view on Meta::CPAN

Revision history for ACME-QuoteDB

0.1.2   Wed Sep 30 23:26:11 PDT 2009
  bug fixes:
  * Build.PL install changes - SQLite3 could not actually write to the database,
    even though, the db file was world writeable. The container dir 
    also needs to be writable, now it is.
  * ensure the database is 0666 for tests as well

0.1.1   Fri Sep 18 02:11:02 PDT 2009
  bug fixes:
  * default constructor values were not getting set on the object as they should
  * loosen untaint filepath - for the dist test failures

0.1.0   Wed Sep  9 23:43:56 PDT 2009
        Initial public pre-release (minor version)


MANIFEST  view on Meta::CPAN

Build.PL
Changes
lib/ACME/QuoteDB.pm
lib/ACME/QuoteDB/DB/Attribution.pm
lib/ACME/QuoteDB/DB/Category.pm
lib/ACME/QuoteDB/DB/DBI.pm
lib/ACME/QuoteDB/DB/Quote.pm
lib/ACME/QuoteDB/DB/QuoteCatg.pm
lib/ACME/QuoteDB/DB/quotedb/quotes.db
lib/ACME/QuoteDB/LoadDB.pm
Makefile.PL
MANIFEST
META.yml
README
t/00-load.t
t/01-load_quotes.t
t/02-get_quotes.t
t/03-load_quotes_env.t
t/04-get_quotes_more.t
t/04-load_get_quote_utf8.t
t/05-load_quotes_remote.t
t/boilerplate.t
t/data/futurama
t/data/python_quotes.txt
t/data/simpsons_quotes.csv
t/data/simpsons_quotes.tsv.csv
t/data/utf8.csv
t/data/www.amk.ca/quotations/python-quotes/index.html
t/data/www.amk.ca/quotations/python-quotes/page-10.html
t/data/www.amk.ca/quotations/python-quotes/page-2.html
t/data/www.amk.ca/quotations/python-quotes/page-3.html
t/pod-coverage.t
t/pod.t

META.yml  view on Meta::CPAN

---
name: ACME-QuoteDB
version: 0.1.2
author:
  - 'David Wright <david_v_wright@yahoo.com>'
abstract: 'API implements CRUD for a Collection of Quotes (adages/proverbs/sayings/epigrams, etc)'
license: perl
resources:
  license: http://dev.perl.org/licenses/
build_requires:
  Carp: 1.04
  Class::DBI: 3.0.17
  Cwd: 3.25
  DBD::SQLite: 1.14
  Data::Dumper: 2.121
  File::Basename: 2.74
  File::Copy: 2.11
  File::Spec: 3.2501
  HTML::TokeParser: 2.37
  Module::Build: 0.280801
  Readonly: 1.03
  Test::More: 0.8
  Text::CSV: 1.06
  aliased: 0.22
  version: 0.7
provides:
  ACME::QuoteDB:
    file: lib/ACME/QuoteDB.pm
    version: 0.1.2
  ACME::QuoteDB::DB::Attribution:
    file: lib/ACME/QuoteDB/DB/Attribution.pm
    version: 0.1.0
  ACME::QuoteDB::DB::Category:
    file: lib/ACME/QuoteDB/DB/Category.pm
    version: 0.1.0
  ACME::QuoteDB::DB::DBI:
    file: lib/ACME/QuoteDB/DB/DBI.pm
    version: 0.1.2
  ACME::QuoteDB::DB::Quote:
    file: lib/ACME/QuoteDB/DB/Quote.pm
    version: 0.1.0
  ACME::QuoteDB::DB::QuoteCatg:
    file: lib/ACME/QuoteDB/DB/QuoteCatg.pm
    version: 0.1.0
  ACME::QuoteDB::LoadDB:
    file: lib/ACME/QuoteDB/LoadDB.pm
    version: 0.1.1
generated_by: Module::Build version 0.280801
meta-spec:
  url: http://module-build.sourceforge.net/META-spec-v1.2.html
  version: 1.2

Makefile.PL  view on Meta::CPAN

# Note: this file was auto-generated by Module::Build::Compat version 0.2808_01
    
    unless (eval "use Module::Build::Compat 0.02; 1" ) {
      print "This module requires Module::Build to install itself.\n";
      
      require ExtUtils::MakeMaker;
      my $yn = ExtUtils::MakeMaker::prompt
	('  Install Module::Build now from CPAN?', 'y');
      
      unless ($yn =~ /^y/i) {
	die " *** Cannot install without Module::Build.  Exiting ...\n";
      }
      
      require Cwd;
      require File::Spec;
      require CPAN;
      
      # Save this 'cause CPAN will chdir all over the place.
      my $cwd = Cwd::cwd();
      
      CPAN::Shell->install('Module::Build::Compat');
      CPAN::Shell->expand("Module", "Module::Build::Compat")->uptodate
	or die "Couldn't install Module::Build, giving up.\n";
      
      chdir $cwd or die "Cannot chdir() back to $cwd: $!";
    }
    eval "use Module::Build::Compat 0.02; 1" or die $@;
    use lib '_build/lib';
    Module::Build::Compat->run_build_pl(args => \@ARGV);
    require MyModuleBuilder;
    Module::Build::Compat->write_makefile(build_class => 'MyModuleBuilder');

README  view on Meta::CPAN

ACME-QuoteDB

ACME::QuoteDB − API implements CRUD for a Collection of Quotes
(adages/proverbs/sayings/epigrams, etc)

This module provides an easy to use programmitic interface to a data‐
base (sqlite3 or mysql) of ’quotes’.  (any content really, that can fit
into our "defined format")

For simplicty you can think of it as a modern fancy perl version of
fortune (with a management interface, remote database connection sup‐
port, plus additional features and some not (yet) supported)

Supported actions include: (CRUD)

1 Create
    * Adding quote(s)
    * ’Batch’ Loading quotes from a file (stream, other database, etc)

1 Read
    * Displaying a single quote, random or based on some criteria
    * Displaying multiple quotes, based on some criteria
    * Displaying a specific number of quotes, based on some search criteria

1 Update
    * Update an existing quote

1 Delete
    * Remove an existing quote

Current version, 0.1.0


INSTALLATION

To install this module, run the following commands:

        perl Build.PL
        ./Build
        ./Build test
        ./Build install


SUPPORT AND DOCUMENTATION

After installing, you can find documentation for this module with the
perldoc command.

    perldoc ACME::QuoteDB

You can also look for information at:

    RT, CPAN's request tracker
        http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB

    AnnoCPAN, Annotated CPAN documentation
        http://annocpan.org/dist/ACME-QuoteDB

    CPAN Ratings
        http://cpanratings.perl.org/d/ACME-QuoteDB

    Search CPAN
        http://search.cpan.org/dist/ACME-QuoteDB/


COPYRIGHT AND LICENCE

Copyright (C) 2009 David Wright

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

lib/ACME/QuoteDB.pm  view on Meta::CPAN

#$Id: QuoteDB.pm,v 1.36 2009/09/30 07:37:09 dinosau2 Exp $
# /* vim:et: set ts=4 sw=4 sts=4 tw=78: */

package ACME::QuoteDB;

use 5.008005;        # require perl 5.8.5, re: DBD::SQLite Unicode
use warnings;
use strict;

#major-version.minor-revision.bugfix
use version; our $VERSION = qv('0.1.2');

#use criticism 'brutal'; # use critic with a ~/.perlcriticrc

use Exporter 'import';
our @EXPORT = qw/quote/; # support one liner

use Carp qw/croak/;
use Data::Dumper qw/Dumper/;
use ACME::QuoteDB::LoadDB;
use aliased 'ACME::QuoteDB::DB::Attribution' => 'Attr';
use aliased 'ACME::QuoteDB::DB::QuoteCatg'  => 'QuoteCatg';
use aliased 'ACME::QuoteDB::DB::Category'  => 'Catg';
use aliased 'ACME::QuoteDB::DB::Quote'    => 'Quote';

binmode STDOUT, ':encoding(utf8)';
binmode STDERR, ':encoding(utf8)';

sub new {
    my $class = shift;
    my $self = bless {}, $class;
    return $self;
}

# provide 1 non OO method for one liners
sub quote {
    my ($arg_ref) = @_;
    return get_quote(q{}, $arg_ref);
}

# list of quote attributions (names) (makes searching easier)
sub list_attr_names {
   return _get_field_all_from('name', Attr->retrieve_all);
}

# list of quote categories
sub list_categories {
   return _get_field_all_from('catg', Catg->retrieve_all);
}

## list of quote sources
sub list_attr_sources {
   return _get_field_all_from('source', Quote->retrieve_all);
}

sub _get_field_all_from {
   my ($field, @all_stored) = @_;

    my $arr_ref = [];
    RECORDS:
    foreach my $f_obj (@all_stored){
        my $s = $f_obj->$field;
        # if doesn't exist and not a dup
        if (! $f_obj->$field || scalar grep {/$s/sm} @{$arr_ref}){
            next RECORDS;
        }
        push @{ $arr_ref }, $f_obj->$field;
    }
    return join "\n", sort @{$arr_ref};
}

sub _get_attribution_ids_from_name {
    my ($attr_name) = @_;

    my $c_ids = [];
    # a bug: what if string starts with what we specify
    #i.e. => %Griffin% doesn' match 'Griffin' (no quotes)
    RESULTS:
    foreach my $c_obj (Attr->search_like(name => "%$attr_name%")){
       next RESULTS unless $c_obj->attr_id;
       push @{ $c_ids }, $c_obj->attr_id;
    }

    if (not scalar @{$c_ids}) {
        croak 'attribution not found';
    }

    return $c_ids;

}

sub _get_quote_id_from_quote {
    my ($quote) = @_;

    my $q_ids = [];
    # a bug: what if string starts with what we specify
    #i.e. => %Griffin% doesn' match 'Griffin' (no quotes)
    RESULTS:
    foreach my $c_obj (Quote->search(quote => $quote)){
       next RESULTS unless $c_obj->quot_id;
       push @{ $q_ids }, $c_obj->quot_id;
    }

    if (not scalar @{$q_ids}) {
        croak 'quote not found';
    }

    return $q_ids;

}

# can handle scalar or array ref
sub _rm_beg_end_space {
    my ($v) = @_;
    return unless $v;
    if (ref $v eq 'ARRAY'){
      my $arr_ref = ();
      foreach my $vl (@{$v}){
          push @{$arr_ref}, _rm_beg_end_space($vl);
      }
      return $arr_ref;
    }
    else {
      $v =~ s/\A\s+//xmsg;
      $v =~ s/\s+\z//xmsg;
      return $v;
    }
  return;
}

sub _get_one_rand_quote_from_all {
    #my $quotes_ref = [];
    #foreach my $q_obj (Quote->retrieve_all){
    #    next unless $q_obj->quote;
    #    my $record = Attr->retrieve($q_obj->attr_id);
    #    my $attr_name = $record->name || q{};
    #    push @{ $quotes_ref }, $q_obj->quote . "\n-- $attr_name";
    #}
    my $quotes_ref = _get_quote_ref_from_all(Quote->retrieve_all);
    return $quotes_ref->[rand scalar @{$quotes_ref}];
}

sub _get_rating_params {
    my ($rating) = @_;
    return unless $rating;

    my ($lower, $upper) = (q{}, q{});
    ($lower, $upper) = split /-/sm, $rating;

    if ($upper && !$lower) { croak 'negative range not permitted'};

    return (_rm_beg_end_space($lower), _rm_beg_end_space($upper));
}

sub _get_if_rating {
    my ($lower, $upper) = @_;

    if ($lower and $upper) { # a range, find within
        $lower =  qq/ AND rating >= '$lower' /;
        $upper =  qq/ AND rating <= '$upper' /;
    }
    elsif ($lower and not $upper) { # not a range, find exact rating
        $lower =  qq/ AND rating = '$lower' /
        #$upper = q{};
    }
    elsif ($upper and not $lower) {
        $upper =  qq/ AND rating = '$upper' /
        #$lower = q{};
    }

    return ($lower, $upper);
}

sub _get_ids_if_catgs_exist {
    my ($catgs) = @_;

    my $catg_ids = ();
    # get category id
    RECS:
    foreach my $c_obj (Catg->retrieve_all){
        next RECS if not $c_obj->catg;

        if (ref $catgs eq 'ARRAY'){
          foreach my $c (@{$catgs}){
            if ($c_obj->catg eq $c){
              # use cat_id if already exists
              push @{$catg_ids}, $c_obj->catg_id;
            }
          }
        }
        else {
          if ($c_obj->catg eq $catgs){
            # use cat_id if already exists
            push @{$catg_ids}, $c_obj->catg_id;
          }
        }
    }
    return $catg_ids;
}

sub _get_quote_id_from_catg_id {
    my ($catg_ids) = @_;

    my $quote_ids = ();
    RECS:
    foreach my $qc_obj (QuoteCatg->retrieve_all){
        next RECS if not $qc_obj->quot_id;

        if (ref $catg_ids eq 'ARRAY'){
          foreach my $c (@{$catg_ids}){
            if ($qc_obj->catg_id eq $c){
              # use cat_id if already exists
              push @{$quote_ids}, $qc_obj->quot_id;
            }
          }
        }
        else {
          if ($qc_obj->catg_id eq $catg_ids){
            # use cat_id if already exists
            push @{$quote_ids}, $qc_obj->quot_id;
          }
        }
    }
    return $quote_ids;
}

sub _untaint_data {
   my ($arr_ref) = @_;
   my $ut_ref = ();
   foreach my $q (@{$arr_ref}){
      if ($q =~ m{\A([0-9]+)\z}sm){
          push @{$ut_ref}, $1;
      }
   }
   return $ut_ref;
}

# TODO fixme: arg list too long
sub _get_rand_quote_for_attribution {
    my ($attr_name, $lower, $upper, $limit, $contain, $source, $catgs) = @_;

    $attr_name ||= q{};
    $lower     ||= q{};
    $upper     ||= q{};
    $limit     ||= q{};
    $contain   ||= q{};
    $source    ||= q{};
    $catgs     ||= q{};

    my $ids = _get_attribution_ids_from_name($attr_name);
    my $phs = _make_correct_num_of_sql_placeholders($ids);

    if ($attr_name) {
        $attr_name =  qq/ attr_id IN ($phs) /;
    }
    else {
        # why would we want this method without a attribution arg?
        # still, let's handle gracefully
        $attr_name =  q/ attr_id IS NOT NULL /;
        $ids = [];
    }

    if ($source) {
        $source =~ s{'}{''}gsm; # sql escape single quote
        $source =  qq/ AND source = '$source' /;
    }
    my $qids =  q{};
    if ($catgs) {
        $catgs  = _get_ids_if_catgs_exist($catgs);
        my $qid_ref = _get_quote_id_from_catg_id($catgs);
        $qids =  join ',', @{_untaint_data($qid_ref)};
        $qids  =  qq/ AND quot_id IN ($qids) /;
    }

    ($lower, $upper) = _get_if_rating($lower, $upper);

    if ($contain) { $contain =  qq/ AND quote LIKE '%$contain%' / }
    if ($limit) { $limit =  qq/ LIMIT '$limit' / };

    my @q = Quote->retrieve_from_sql(
              qq{ $attr_name $lower $upper $source $qids $contain $limit },
              @{$ids}
            );

    # XXX code duplication but smaller footprint
    # choosing not less code duplication, we'll see,...
    #my $quotes_ref = [];
    #foreach my $q_obj ( @q ){
    #    next unless $q_obj->quote;
    #    my $record = Attr->retrieve($q_obj->attr_id);
    #    my $attr_name = $record->name || q{};
    #    push @{ $quotes_ref }, $q_obj->quote . "\n-- $attr_name";
    #}
    #return _get_quote_ref_from_all(\@q);
    # XXX array_ref does not work here!
    return _get_quote_ref_from_all(@q);

    #return $quotes_ref;
}

sub _get_quote_ref_from_all {
    my (@results) = @_;
    #my ($results) = @_;

    my $quotes_ref = [];
    #foreach my $q_obj ( @{$results} ){
    foreach my $q_obj ( @results ){
        next unless $q_obj->quote;
        my $rec = Attr->retrieve($q_obj->attr_id);
        my $attr_name = $rec->name || q{};
        push @{ $quotes_ref }, $q_obj->quote . "\n-- $attr_name";
    }

    return $quotes_ref;
}

sub _args_are_valid {
    my ( $arg_ref, $accepted ) = @_;

    my $arg_ok = 0;
    foreach my $arg ( %{$arg_ref} ) {
        if ( scalar grep { $arg =~ $_ } @{$accepted} ) {
            $arg_ok = 1;
        }
    }

   if (!$arg_ok) {croak 'unsupported argument option passed'}
}

sub add_quote {
    my ( $self, $arg_ref ) = @_;

    _args_are_valid($arg_ref, [qw/Quote AttrName Source Rating Category/]);

    my $load_db = ACME::QuoteDB::LoadDB->new({
                                #verbose => 1,
                  });

    $load_db->set_record(quote  => $arg_ref->{Quote});
    $load_db->set_record(name   => $arg_ref->{AttrName});
    $load_db->set_record(source => $arg_ref->{Source});
    $load_db->set_record(catg   => $arg_ref->{Category});
    $load_db->set_record(rating => $arg_ref->{Rating});

    if ($load_db->get_record('quote') and $load_db->get_record('name')) {
        return $load_db->write_record;
    }
    else {
        croak 'quote and attribution name are mandatory parameters';
    }

    return;
}

# XXX lame, can only get an id from exact quote
sub get_quote_id {
    my ( $self, $arg_ref ) = @_;

    if (not $arg_ref) {croak 'Quote required'}

    _args_are_valid($arg_ref, [qw/Quote/]);

    my $ids = _get_quote_id_from_quote($arg_ref->{'Quote'});

    return join "\n", sort @{$ids};
}

sub update_quote {
    my ( $self, $arg_ref ) = @_;

    if (not $arg_ref) {croak 'QuoteId and Quote required'}

    _args_are_valid($arg_ref, [qw/Quote QuoteId Source 
                                  Category Rating AttrName/]);

    my $q = Quote->retrieve($arg_ref->{'QuoteId'});

    my $atr = Attr->retrieve($q->attr_id);

    # XXX need to support multi categories
    #my $ctg = Catg->retrieve($q->catg_id);
    my $qc = QuoteCatg->retrieve($q->quot_id);

    my $ctg = Catg->retrieve($qc->catg_id);

    $q->quote($arg_ref->{'Quote'});

    if ($arg_ref->{'Source'}){$q->source($arg_ref->{'Source'})}

    if ($arg_ref->{'Rating'}){$q->rating($arg_ref->{'Rating'})};

    if ($arg_ref->{'AttrName'}){$atr->name($arg_ref->{'AttrName'})};

    # XXX need to support multi categories
    if ($arg_ref->{'Category'}){
       $ctg->catg($arg_ref->{'Category'})
    }

    return ($q->update && $atr->update && $ctg->update);
}

sub delete_quote {
    my ( $self, $arg_ref ) = @_;

    if (not $arg_ref) {croak 'QuoteId required'}

    _args_are_valid($arg_ref, [qw/QuoteId/]);

    my $q = Quote->retrieve($arg_ref->{'QuoteId'});

    #$q->quote($arg_ref->{'QuoteId'});

    return $q->delete;

}

sub get_quote {
    my ( $self, $arg_ref ) = @_;

    # default use case, return random quote from all
    if (not $arg_ref) {
        return _get_one_rand_quote_from_all;
    }

    _args_are_valid($arg_ref, [qw/Rating AttrName Source Category/]);

    my ($lower, $upper) = (q{}, q{});
    if ($arg_ref->{'Rating'}) {
        ($lower, $upper) = _get_rating_params($arg_ref->{'Rating'});
    }

    my $attr_name = q{};
    if ( $arg_ref->{'AttrName'} ) {
        $attr_name = _rm_beg_end_space($arg_ref->{'AttrName'});
    }

    my $source = q{};
    if ( $arg_ref->{'Source'} ) {
        $source = _rm_beg_end_space($arg_ref->{'Source'});
    }

    my $catg; # will become scalar or array ref
    if ( $arg_ref->{'Category'} ) {
       $catg = _rm_beg_end_space($arg_ref->{'Category'});
    }

    # use case for attribution, return random quote
    my $quotes_ref =
          _get_rand_quote_for_attribution($attr_name, $lower,
                     $upper, q{}, q{}, $source, $catg);

    # one random from specified pool
    return $quotes_ref->[rand scalar @{$quotes_ref}];

}

# XXX isn't there a method in DBI for this, bind something,...
# TODO follow up 
sub _make_correct_num_of_sql_placeholders {
    my ($ids) = @_;
    # XXX a hack to make a list of '?' placeholders
    my @qms = ();
    for (1..scalar @{$ids}) {
       push @qms, '?';
    }
    return join ',', @qms;
}

sub get_quotes {
    my ( $self, $arg_ref ) = @_;

    # default use case, return random quote from all
    if (not $arg_ref) {
        return _get_one_rand_quote_from_all;
    }

    _args_are_valid($arg_ref, [qw/Rating AttrName Limit Category Source/]);

    my ($lower, $upper) = (q{}, q{});
    if ($arg_ref->{'Rating'}) {
        ($lower, $upper) = _get_rating_params($arg_ref->{'Rating'});
    }

    my $limit = q{};
    if ($arg_ref->{'Limit'}) {
        # specify 'n' amount of quotes to limit by
        $limit = _rm_beg_end_space($arg_ref->{'Limit'});
    }

    my $attribution = q{};
    if ( $arg_ref->{'AttrName'} ) {
        $attribution = _rm_beg_end_space($arg_ref->{'AttrName'});
    }

    my $source = q{};
    if ( $arg_ref->{'Source'} ) {
        $source = _rm_beg_end_space($arg_ref->{'Source'});
    }

    my $catg = q{};
    if ( $arg_ref->{'Category'} ) {
        $catg = _rm_beg_end_space($arg_ref->{'Category'});
    }
    # use case for attribution, return random quote
    return _get_rand_quote_for_attribution($attribution, $lower,
                     $upper, $limit, q{}, $source, $catg);

}


sub get_quotes_contain {
    my ( $self, $arg_ref ) = @_;


    my $contain = q{};
    if ($arg_ref->{'Contain'}) {
        $contain = _rm_beg_end_space($arg_ref->{'Contain'});
    }
    else {
        croak 'Contain is a mandatory parameter';
    }

    _args_are_valid($arg_ref, [qw/Contain Rating AttrName Limit/]);

    my ($lower, $upper) = (q{}, q{});
    if ($arg_ref->{'Rating'}) {
        ($lower, $upper) = _get_rating_params($arg_ref->{'Rating'});
    }

    my $limit = q{};
    if ($arg_ref->{'Limit'}) {
        $limit = _rm_beg_end_space($arg_ref->{'Limit'});
    }

    # default use case for attribution, return random quote
    my $attr_name = q{};
    if ( $arg_ref->{'AttrName'} ) {
        # return 'n' from random from specified pool
        $attr_name = _rm_beg_end_space($arg_ref->{'AttrName'});
    }

    return _get_rand_quote_for_attribution($attr_name, $lower, $upper, $limit, $contain);
}

1 and 'Chief Wiggum: Uh, no, you got the wrong number. This is 9-1... 2.';


__END__

=head1 NAME

ACME::QuoteDB - API implements CRUD for a Collection of Quotes (adages/proverbs/sayings/epigrams, etc)


=head1 VERSION

Version 0.1.2


=head1 SYNOPSIS

Easy access to a collection of quotes (the 'Read' part)

As quick one liner:

    # randomly display one quote from all available. (like motd, 'fortune')
    perl -MACME::QuoteDB -le 'print quote()'

    # Say you have populated your quotes database with some quotes from 
    # 'The Simpsons'
    # randomly display one quote from all available for person 'Ralph'
    perl -MACME::QuoteDB -le 'print quote({AttrName => "ralph"})'

    # example of output
    Prinskipper Skippel... Primdable Skimpsker... I found something!
    -- Ralph Wiggum

    # get 1 quote, only using these categories (you have defined)
    perl -MACME::QuoteDB -le 'print quote({Category => [qw(Humor Cartoon ROTFLMAO)]})'


In a script/module, OO usage:

    use ACME::QuoteDB;

    my $sq = ACME::QuoteDB->new;

    # get random quote from any attribution
    print $sq->get_quote; 

    # get random quote from specified attribution
    print $sq->get_quote({AttrName => 'chief wiggum'}); 

    # example of output
    I hope this has taught you kids a lesson: kids never learn.
    -- Chief Wiggum

    # get all quotes from one source
    print @{$sq->get_quotes({Source => 'THE SimPSoNs'})}; # is case insensitive

    # get 2 quotes, with a low rating that contain a specific string
    print @{$sq->get_quotes_contain({
                  Contain =>  'til the cow',
                  Rating  => '1-5',
                  Limit   => 2
            })};

    # get 5 quotes from given source
    print @{$sq->get_quotes({Source => 'The Simpsons',
                             Limit  => 5
           })};

    # list all sources
    print $sq->list_attr_sources;

    # list all categories
    print $sq->list_categories;


=head1 DESCRIPTION

This module provides an easy to use programmitic interface 
to a database (sqlite3 or mysql) of 'quotes'.  (any content really, 
that can fit into our L<"defined format"|/"record format">)

For simplicty you can think of it as a modern fancy perl version 
of L<fortune|/fortune> 
(with a management interface, remote database
connection support, 
plus additional features and some not (yet) supported)

Originally, this module was designed for a collection of quotes from a well 
known TV show, once I became aware that distributing it as such would be 
L<copyright infringement|/'copyright infringement'>, I generalized the module, so it can be loaded 
with 'any' content. (in the quote-ish L<format|/"record format">)

=head4 Supported actions include: (CRUD)

=over 4

=item 1 Create

       * Adding quote(s)
       * 'Batch' Loading quotes from a file (stream, other database, etc)

=item 1 Read

       * Displaying a single quote, random or based on some criteria
       * Displaying multiple quotes, based on some criteria
       * Displaying a specific number of quotes, based on some search criteria

=item 1 Update

       * Update an existing quote

=item 1 Delete

       * Remove an existing quote

=back


=head4 Examples of L<Read|/Read>

    my $sq = ACME::QuoteDB->new;

    # on Oct 31st, one could get an appropriate (humorous) quote:
    # (providing, of course that you have defined/populated these categories)
    print $sq->get_quote({Category => [qw(Haloween Humor)]}); 

    # get everthing from certain attributor:
    print @{$sq->get_quotes({AttrName => 'comic book guy'})};

    # get all quotes with a certain rating
    $sq->get_quotes({Rating => '7.0'});

    # get all quotes containing some specific text:
    $sq->get_quotes_contain({Contain => 'til the cow'});


=head4 Examples of L<Create|/Create>

(See L<ACME::QuoteDB::LoadDB> for batch loading)
 
    # add a quote to the database
    my $id_of_added = $sq->add_quote({
                          Quote     => 'Hi, I'm Peter,...",
                          AttrName  => 'Peter Griffin',
                          Source    => 'Family American Dad Guy',
                          Rating    => '1.6',
                          Category  => 'TV Humor',
                      });

=head4 Example of L<Update|/Update>

    # update a quote in the database
    my $quote_id = $sq->get_quote_id({Quote => 'Hi, I'm Peter,..."});

    $sq->update_quote({
        QuoteId   => $quote_id,
        Quote     => 'Hi, I'm Peter, and your not!',
        AttrName  => 'Peter Griffin',
        Source    => 'Family Guy',
        Rating    => '5.7',
        Category  => [qw(TV Humor Crude Adolescent)]
    });

    # category/quote is a many to many relationship: 
    # 1 quote can be in many categories. (and of course 1 category can have many quotes)


=head4 Example of L<Delete|/Delete>

    # delete a quote from the database
    $sq->delete_quote({QuoteId => $quote_id});
    

=over 2

=item record format

One full quote database record currently consits of 5 fields:

Quote, AttrName, Source, Rating, Category

    Quote     => 'the quote desired' # mandatory
    AttrName  => 'who said it'       # mandatory
    Source    => 'where was it said'
    Rating    => 'how you rate the quote/if at all',
    Category  => 'what category is the quote in',

For example:

    Quote     => 'Hi, I'm Peter,...",
    AttrName  => 'Peter Griffin',
    Source    => 'Family Guy',
    Rating    => '8.6',
    Category  => 'TV Humor',

=item * NOTE: In order for this module to be useful one has to load some quotes
 to the database.  Hey, just once though :) (see below - L<Loading Quotes|/"LOADING QUOTES">)

=back

=head1 OVERVIEW

Easy, quick auto-CRUD access to a collection of quotes. (which you provide)

Some ideal uses for this module could be:

=over 4

=item 1 

Quotes Website (quotes/movie/lyrics/limerick/proverbs/jokes/etc)

=item 2 

perl replacement for 'fortune'

=item 3 

Dynamic signature generation

=item 4 

international languages (has utf8 support)

=item 5 

convenient storing/sharing collections of quotes

=item 6 

for me to finally have a place to store (and manage) quotes (that can
be easily backed up or even to a remote db if desired)

=item 7 

anywhere perl is supported and 'quotes' are desired.

=item 8

others? (let me know what you do, if you want, if you do)

=back

See L</DESCRIPTION> above

Also see L<ACME::QuoteDB::LoadDB>


=head1 USAGE

    use ACME::QuoteDB;

    my $sq = ACME::QuoteDB->new;

    print $sq->get_quote;

    # examples are based on quotes data in the test database. 
    # (see tests t/data/)

    # get specific quote based on basic text search.
    # search all 'ralph' quotes for string 'wookie'
    print $sq->get_quotes_contain({
                  Contain   => 'wookie', 
                  AttrName => 'ralph',
                  Limit     => 1          # only return 1 quote (if any)
           });
    # output:
    I bent my wookie.
    -- Ralph Wiggums

    # returns all quotes attributed to 'ralph', with a rating between 
    # (and including) 7 to 9
    print join "\n",  @{$sq->get_quotes({
                                          AttrName => 'ralph', 
                                          Rating    => '7-9'
                                        })
                       };
    
    # same thing but limit to 2 results returned
    # (and including) 7 to 9
    print join "\n",  @{$sq->get_quotes({
                                          AttrName => 'ralph', 
                                          Rating    => '7-9',
                                          Limit     => 2
                                         })
                       };

    # get 6 random quotes (any attribution)
    foreach my $q ( @{$sq->get_quotes({Limit => 6})} ) {
        print "$q\n";
    }


    # get list of available attributions (that have quotes provided by this module)
    print $sq->list_attr_names;

    # any unique part of name will work
    # i.e these will all return the same results (because of our limited
    # quotes db data set)
    print $sq->get_quotes({AttrName => 'comic book guy'});
    print $sq->get_quotes({AttrName => 'comic book'});
    print $sq->get_quotes({AttrName => 'comic'});
    print $sq->get_quotes({AttrName => 'book'});
    print $sq->get_quotes({AttrName => 'book guy'});
    print $sq->get_quotes({AttrName => 'guy'});

   # get all quotes, only using these categories (you have defined)
   print @{$sq->get_quotes({ Category => [qw(Humor ROTFLMAO)] })};

   # get all quotes from Futurama
   print @{$sq->get_quotes({Source => Futurama})};


Also see t/02* included with this distribution.
(available from the CPAN if not included on your system)


=head1 SUBROUTINES/METHODS 

For the most part this is an OO module. There is one function (quote) provided
for command line 'one liner' convenience. 

=head2 quote
    
    returns one quote. (is exported).
    this takes identical arguments to 'get_quote'. (see below)
     
    example:

    perl -MACME::QuoteDB -le 'print quote()'

=head2 new

    instantiate a ACME::QuoteDB object.

    takes no arguments

    # example
    my $sq = ACME::QuoteDB->new;

=head2 get_quote
     
    returns one quote

    # get random quote from any attribution
    print $sq->get_quote;

    # get random quote from specified attribution
    print $sq->get_quote({AttrName => 'chief wiggum'});

    Optional arguments, a hash ref.

    available keys: AttrName, Rating

    my $args_ref = {
                     AttrName => 'chief wiggum'
                     Rating    => 7,
                    };

    print $sq->get_quote($args_ref);

    Note: The 'Rating' option is very subjective. 
    It's a 0-10 scale of 'quality' (or whatever you decide it is)

    To get a list of the available AttrNames use the list_attr_names method
    listed below.  
    
    Any unique part of name will work

    Example, for attribution 'comic book guy'

    # these will all return the same results
    print $sq->get_quotes({AttrName => 'comic book guy'});

    print $sq->get_quotes({AttrName => 'comic book'});

    print $sq->get_quotes({AttrName => 'comic'});

    print $sq->get_quotes({AttrName => 'book'});

    print $sq->get_quotes({AttrName => 'book guy'});

    print $sq->get_quotes({AttrName => 'guy'});
 
    # However, keep in mind the less specific the request is the more results
    # are returned, for example the last one would match, 'Comic Book Guy', 
    # 'Buddy Guy' and 'Guy Smiley',...

=begin comment
    
    # XXX this is a bug with sub _get_attribution_ids_from_name 
    #print $sq->get_quotes({AttrName => 'guy'}); would not match 'Guy Smiley'

=end comment

=head2 add_quote
     
    Adds the supplied record to the database

    possible Key arguments consist of:
        Quote, AttrName, Source, Rating, Category  

    with only Quote and AttrName being mandatory (all are useful though):

    For Example: 

      my $q = 'Lois: Peter, what did you promise me?' .
      "\nPeter: That I wouldn't drink at the stag party." .
      "\nLois: And what did you do?" .
      "\nPeter: Drank at the stag pa-- ... Whoa. I almost walked into that one.";
      
      $sq->add_quote({
          Quote     => $q,
          AttrName  => 'Peter Griffin',
          Source    => 'Family Guy',
          Rating    => '8.6',
          Category  => 'TV Humor',
      });


=head2 get_quote_id (very beta)
 
   given a (verbatim) quote, will retrieve that quotes id
   (only useful for then doing an L</update> or L</delete>

   possible Key arguments consist of: Quote

   my $q = 'Lois: Peter, what did you promise me?' .
  "\nPeter: That I wouldn't drink at the stag party." .
  "\nLois: And what did you do?" .
  "\nPeter: Drank at the stag pa-- ... Whoa. I almost walked into that one.";
  
  my $qid = $sq->get_quote_id({Quote => $q});
  print $qid; # 30

=head2 delete_quote (very beta)

    deletes an existing quote in the database
    takes an valid quote id (see L</get_quote_id>)

    possible Key arguments consist of: QuoteId

      $sq->delete_quote({QuoteId => $qid});


=head2 update_quote (very beta)
     
    updates an existing quote in the database

    possible Key arguments consist of: QuoteId, Quote

      my $q = 'Lois: Peter, what did you promise me?' .
      "\nPeter: That I wouldn't drink at the stag party." .
      "\nLois: And what did you do?" .
      "\nPeter: Drank at the stag pa-- ... Whoa. I almost walked into that one.";

      $q =~ s/Lois/Marge/xmsg;
      $q =~ s/Peter/Homer/xmsg;
 
      $sq->update_quote({
          QuoteId   => $qid, # as returned from L</get_quote_id>
          Quote     => $q,
          AttrName  => 'Lois Simpson',
          Source    => 'The Simpsons Guys',
          Rating    => '9.6',
          Category  => 'Sometimes Offensive Humor',
      });


=head2 get_quotes

    returns zero or more quote(s)

    Optional arguments, a hash ref.

    available keys: AttrName, Rating, Limit

    # returns 2 ralph wiggum quotes with a rating between 
    # (and including) 7 to 9
    print join "\n",  @{$sq->get_quotes({
                                          AttrName => 'ralph', 
                                          Rating    => '7-9',
                                          Limit     => 2
                                         })
                       };

    AttrName and Rating work exactely the same as for get_quote (docs above)
    
    Limit specifies the amout of results you would like returned. (just like
    with SQL)


=head2 get_quotes_contain

    returns zero or more quote(s), based on a basic text search.

    # get specific quote based on basic text search.
    # search all ralph wiggum quotes for string 'wookie'
    print $sq->get_quotes_contain({
                  Contain   => 'wookie', 
                  AttrName => 'ralph',
                  Limit     => 1          # only return 1 quote (if any)
           })->[0]; # q{Ralph: I bent my wookie.};


    Optional arguments, a hash ref.

    available keys: AttrName, Contain, Limit

    AttrName and Limit work exactly the same as for get_quotes (docs above)
    
    Contain specifies a text string to search quotes for. If a AttrName
    option is included, search is limited to that attribution.

    Contain is a simple text string only. Regex not supported
    Contain literally becomes: AND quote LIKE '%$contain%'


=head2 list_attr_names

    returns a list of attributions (name) for which we have quotes.

    # get list of available attributions (that have quotes provided by this module)
    print $sq->list_attr_names;


=head2 list_categories

    returns a list of categories defined in the database

    # get list of available categories (that have quotes provided by this module)
    print $sq->list_categories;


=head2 list_attr_sources

    returns a list of attribution sources defined in the database

    # get list of attribution sources (that have quotes provided by this module)
    print $sq->list_attr_sources;


=head1 LOADING QUOTES

In order to actually use this module, one has to load quotes content,
hopefully this is relativly easy,... (see t/01-load_quotes.t in tests)

=over 4

=item 1 add_quote, one record at a time, probably within an iteration loop

see L</add_quote>

=item 1 (Batch Load) load quotes from a csv file. (tested with comma and tab delimiters)

  format of file must be as follows: (headers)
  "Quote", "Attribution Name", "Attribution Source", "Category", "Rating"
 
  for example:
  "Quote", "Attribution Name", "Attribution Source", "Category", "Rating"
  "I hope this has taught you kids a lesson: kids never learn.","Chief Wiggum","The Simpsons","Humor",9
  "Sideshow Bob has no decency. He called me Chief Piggum. (laughs) Oh wait, I get it, he's all right.","Chief Wiggum","The Simpsons","Humor",8

=item 1 if these dont suit your needs, ACME::QuoteDB::LoadDB is sub-classable, 

  so one can extract data anyway they like and populate the db themselves. 
  (there is a test that illustrates overriding the stub method, 'dbload')

   you need to populate a record data structure:

    $self->set_record(quote  => q{}); # mandatory
    $self->set_record(name   => q{}); # mandatory
    $self->set_record(source => q{}); # optional but useful
    $self->set_record(catg   => q{}); # optional but useful
    $self->set_record(rating => q{}); # optional but useful

   # then to write the record you call
   $self->write_record;

   NOTE: this is a record-by-record operation, so one would perform this within a
   loop. there is no bulk (memory dump) write operation currently.


=back


For more see L<ACME::QuoteDB::LoadDB>


=begin comment
 
    keep pod coverage happy.

    # Coverage for ACME::QuoteDB is 71.4%, with 3 naked subroutines:
    # Attr
    # Quote
    # Catg
    # QuoteCatg

    pod tests incorrectly state, Attr, Quote and Catg are subroutines, well they
    are,... (as aliases) but act on a different object. 
    
    TODO: explore the above (is this a bug, if so, who's?, version effected, 
    create use case, etc) 
    
=head2 Attr

=head2 Quote

=head2 Catg

=head2 QuoteCatg

=end comment

=head1 DIAGNOSTICS

An error such as:

C<DBD::SQLite::db prepare_cached failed: no such table: ,...>

probably means that you do not have a database created in the correct format.

basically, you need to create the database, usually, on a first run

you need to add the flag (to the loader):

create_db => 1, # first run, create the db

appending to an existing database is the default behaviour

see L<ACME::QuoteDB::LoadDB/create_db_tables>

=head1 CONFIGURATION AND ENVIRONMENT

if you are running perl > 5.8.5 and have access to
install cpan modules, you should have no problem installing this module
(utf-8 support in DBD::SQLite not avaible until 5.8 - we don't support 'non
utf-8 mode)

=over 1

=item * By default, the quotes database used by this module installs in the 
system path, 'lib', (See L<Module::Build/"INSTALL PATHS">)
as world writable - i.e. 0666 (and probably owned by root)
If you don't like this, you can modify Build.PL to not chmod the file and it
will install as 444/readonly, you can also set a chown in there for whoever
you want to have RW access to the quotes db.

Alternativly, one can specify a location to a quotes database (file) to use.
(Since the local mode is sqlite3, the file doesn't even need to exist, just
needs read/write access to the path on the filesystem)

Set the environmental variable:

$ENV{ACME_QUOTEDB_PATH} (untested on windows)

(this has to be set before trying a database load and also (everytime before 
using this module, obviouly)

Something such as:

BEGIN { 
    # give alternate path to the DB
    # doesn't need to exist, will create
    $ENV{ACME_QUOTEDB_PATH} = '/home/me/my_stuff/my_quote_db'
}

* (NOTE: be sure this (BEGIN) exists *before* the 'use ACME::QuoteDB' lines)

The default is to use sqlite3.

In order to connect to a mysql database, several environmental variables
are required.

BEGIN {
    # have to set this to use remote database
    $ENV{ACME_QUOTEDB_REMOTE} =  'mysql';
    $ENV{ACME_QUOTEDB_DB}     =  'acme_quotedb';
    $ENV{ACME_QUOTEDB_HOST}   =  'localhost';
    $ENV{ACME_QUOTEDB_USER}   =  'acme_user';
    $ENV{ACME_QUOTEDB_PASS}   =  'acme';
}

Set the above in a begin block.

The database connection is transparent. 

Module usage wise, all operations are the same but now
you will be writing to the remote mysql database specified.

(The user will need read/write permissions to the db/tables)
(mysql admin duties are beyond the scope of this module)

The only supported databases at this time are sqlite and mysql.

It is trivial to add support for others

=back

=head1 DEPENDENCIES

L<Carp>

L<Data::Dumper>

L<criticism> (pragma - enforce Perl::Critic if installed)

L<version>(pragma - version numbers)

L<aliased>

L<Test::More>

L<DBD::SQLite>

L<DBI>

L<Class::DBI>

L<File::Basename>

L<Readonly>

L<Cwd>

L<Module::Build>


=head1 INCOMPATIBILITIES

none known of

=head1 SEE ALSO

man fortune (unix/linux)

L<Fortune>

L<fortune>

L<Acme::RandomQuote::Base>

L<WWW::LimerickDB>

=begin comment

    C<Fortune> http://search.cpan.org/~gward/Fortune-0.2/Fortune.pm
    C<fortune> http://search.cpan.org/~cwest/ppt-0.14/bin/fortune
    C<Acme::RandomQuote::Base> http://search.cpan.org/~mangaru/Acme-RandomQuote-Base-0.01/lib/Acme/RandomQuote/Base.pm
    C<WWW::LimerickDB> http://search.cpan.org/~zoffix/WWW-LimerickDB-0.0305/lib/WWW/LimerickDB.pm

=end comment


=head1 AUTHOR

David Wright, C<< <david_v_wright at yahoo.com> >>

=head1 TODO

=over 2

=item 1 if the database cannot be found, no error is printed!!!

or if you have no write access to it!
"you'll just get 'no attribute can be found,,...", which is cryptic to say
the least!

=item 1 add a dump backup to csv

a backup mechanism for your db to a regular text csv file.

=item 1 clean up tests 'skip if module X' not installed

(one of sqlite3 or mysql is required). currently dies if DBD::SQLite not
installed

=item 1 support multiple categories from LoadDB

how to load multipul categories from a csv file? 
(try to avoid somthing ugly in our csv file format). or maybe don't support
this.

=item 1 (possibly) support long/short quotes output (see 'man fortune')

=back


=head1 BUGS AND LIMITATIONS

The CRUD stuff is weak for sure.
(i.e. add_quote, update_quote, delete_quote, get_quote_id)

For example, currently you can only get the quote id from the exact quote

In the future, I may just expose the DBI::Class object directly
to those that need/want it.

=begin comment

get_quotes_contain  uses %search% to do it's pattern mattching, so that will
miss some obvious searches, which it should find.

i.e.
'Bill' will not find 'Bill' , beginning and endings of words will be off.

XXX - look at search_like, instead of what you are doing now

=end comment

currently, I am not encapsulating the record data structure used 
by LoadDB->write. (i.e. it's a typical perl5 ojbect, the blessed hash)

I will for sure be encapsulating all data in a future version.
(so, don't have code that does $self->{record}->{name} = 'value', or you won't
be happy down the road). Instead use $self->get_record('name') (getter) or
$self->set_record(name => 'my attrib') (setter)


When we are using a SQLite database backend ('regular' local usage), we 
should probably be using, ORLite instead of Class::DBI 
(although we have not seen any issues yet).

Please report any bugs or feature requests to C<bug-acme-quotedb at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-QuoteDB>.  
I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ACME::QuoteDB


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ACME-QuoteDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ACME-QuoteDB>

=item * Search CPAN

L<http://search.cpan.org/dist/ACME-QuoteDB/>

=back

=head1 ACKNOWLEDGEMENTS

The construction of this module was guided by:

Perl Best Practices - Conway

Test Driven Development

Object Oriented Programming

Gnu

vim 

Debian Linux

Mac OSX

The collective wisdom and code of The CPAN

this module was created with module-starter

module-starter --module=ACME::QuoteDB \
        --author="David Wright" --mb --email=david_v_wright@yahoo.com

=head1 ERRATA

    Q: Why did you put it in the ACME namespace?
    A: Seemed appropriate. I emailed modules@cpan.org and didn't get a
       different reaction.

    Q: Why did you write this?
    A: At a past company, a team I worked on a project with had a test suite, 
    in which at the completion of successful tests (100%), a 'wisenheimer' 
    success message would be printed. (Like a quote or joke or the like)
    (Interestingly, it added a 'fun' factor to testing, not that one is needed 
    of course ;). It was hard to justify spending company time to find and 
    add decent content to the hand rolled process, this would have helped.

    Q: Don't you have anything better to do, like some non-trivial work?
    A: Yup

    Q: Hey Dood! why are u uzing Class::DBI as your ORM!?  Haven't your heard 
       of L<DBIx::Class>?
    A: Yup, and I'm aware of 'the new hotness' L<Rose::DB>. If you use this 
       module and are unhappy with the ORM, feel free to change it. 
       So far L<Class::DBI> is working for my needs.


=head1 FOOTNOTES

=over 4

=item fortune 

unix application in 'games' (FreeBSD) type 'man fortune' from the command line

=item copyright infringement 

L<http://www.avvo.com/legal-answers/is-it-copyright-trademark-infringement-to-operate--72508.html>

=item wikiquote

interesting reading, wikiquote fair use doc: L<http://en.wikiquote.org/wiki/Wikiquote:Copyrights>

=back

=head1 LICENSE AND COPYRIGHT

Copyright 2009 David Wright, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.


=cut

1; # End of ACME::QuoteDB

lib/ACME/QuoteDB/DB/Attribution.pm  view on Meta::CPAN

#$Id: Attribution.pm,v 1.12 2009/09/30 07:37:09 dinosau2 Exp $
# /* vim:et: set ts=4 sw=4 sts=4 tw=78: */

package ACME::QuoteDB::DB::Attribution;
use base 'ACME::QuoteDB::DB::DBI';

use 5.008005;        # require perl 5.8.5, re: DBD::SQLite Unicode
use warnings;
use strict;

#use criticism 'brutal'; # use critic with a ~/.perlcriticrc

use version; our $VERSION = qv('0.1.0');

ACME::QuoteDB::DB::Attribution->table('attribution');
ACME::QuoteDB::DB::Attribution->columns(All    => qw/attr_id name/);
ACME::QuoteDB::DB::Attribution->has_many(quote => 'ACME::QuoteDB::DB::Quote');

1;
__END__

=head1 NAME

ACME::QuoteDB::DB::Attribution - Class::DBI For ACME::QuoteDB

=head1 VERSION

Version 0.1.0


=head1 SYNOPSIS

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>


=head1 DESCRIPTION

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>

see L<Class::DBI>

=head1 OVERVIEW

see L<ACME::QuoteDB>

See L<Description|/Description> above

=head1 USAGE

See Synopsis

Also see t/01* included with the distribution.
(available from the CPAN if not included on your system)

=head1 SUBROUTINES/METHODS

see L<ACME::QuoteDB>


=head1 DIAGNOSTICS

None currently known


=head1 CONFIGURATION AND ENVIRONMENT

if you are running perl > 5.8.5 and have access to
install cpan modules, you should have no problem installing this module

=head1 DEPENDENCIES

L<version>(pragma - version numbers)

L<Class::DBI>

L<DBD::SQLite>

=head1 INCOMPATIBILITIES

none known of

=head1 SEE ALSO

L<ACME::QuoteDB>

L<Class::DBI>

=head1 AUTHOR

David Wright, C<< <david_v_wright at yahoo.com> >>

=head1 BUGS AND LIMITATIONS

Please report any bugs or feature requests to C<bug-acme-thesimpsonsquotes at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-QuoteDB>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ACME::QuoteDB


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ACME-QuoteDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ACME-QuoteDB>

=item * Search CPAN

L<http://search.cpan.org/dist/ACME-QuoteDB/>

=back


=head1 LICENSE AND COPYRIGHT


Copyright 2009 David Wright, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.



lib/ACME/QuoteDB/DB/Category.pm  view on Meta::CPAN

#$Id: Category.pm,v 1.7 2009/09/30 07:37:09 dinosau2 Exp $
# /* vim:et: set ts=4 sw=4 sts=4 tw=78: */

package ACME::QuoteDB::DB::Category;
use base 'ACME::QuoteDB::DB::DBI';

use 5.008005;        # require perl 5.8.5, re: DBD::SQLite Unicode
use warnings;
use strict;

#use criticism 'brutal'; # use critic with a ~/.perlcriticrc

use version; our $VERSION = qv('0.1.0');
ACME::QuoteDB::DB::Category->table('category');
ACME::QuoteDB::DB::Category->columns(All    => qw/catg_id catg/);
ACME::QuoteDB::DB::Category->has_many(quote => 'ACME::QuoteDB::DB::Quote');

1;

__END__

=head1 NAME

ACME::QuoteDB::DB::Attribution - Class::DBI For ACME::QuoteDB

=head1 VERSION

Version 0.1.0


=head1 SYNOPSIS

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>


=head1 DESCRIPTION

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>

see L<Class::DBI>

=head1 OVERVIEW

see L<ACME::QuoteDB>

See L<Description|/Description> above

=head1 USAGE

See Synopsis

Also see t/01* included with the distribution.
(available from the CPAN if not included on your system)

=head1 SUBROUTINES/METHODS

see L<ACME::QuoteDB>


=head1 DIAGNOSTICS

None currently known


=head1 CONFIGURATION AND ENVIRONMENT

if you are running perl > 5.8.5 and have access to
install cpan modules, you should have no problem installing this module

=head1 DEPENDENCIES

L<version>(pragma - version numbers)

L<Class::DBI>

L<DBD::SQLite>

=head1 INCOMPATIBILITIES

none known of

=head1 SEE ALSO

L<ACME::QuoteDB>

L<Class::DBI>

=head1 AUTHOR

David Wright, C<< <david_v_wright at yahoo.com> >>

=head1 BUGS AND LIMITATIONS

Please report any bugs or feature requests to C<bug-acme-thesimpsonsquotes at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-QuoteDB>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ACME::QuoteDB


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ACME-QuoteDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ACME-QuoteDB>

=item * Search CPAN

L<http://search.cpan.org/dist/ACME-QuoteDB/>

=back


=head1 LICENSE AND COPYRIGHT


Copyright 2009 David Wright, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.




lib/ACME/QuoteDB/DB/DBI.pm  view on Meta::CPAN

#$Id: DBI.pm,v 1.19 2009/09/30 07:37:09 dinosau2 Exp $
# /* vim:et: set ts=4 sw=4 sts=4 tw=78: */

package ACME::QuoteDB::DB::DBI;
use base 'Class::DBI';

use 5.008005;        # require perl 5.8.5
                     # DBD::SQLite Unicode is not supported before 5.8.5
use warnings;
use strict;

#use criticism 'brutal'; # use critic with a ~/.perlcriticrc

use version; our $VERSION = qv('0.1.2');

use Readonly;
use File::Basename qw/dirname/;
use Carp qw/croak/;
use Cwd 'abs_path';
use File::Spec;

Readonly my $QUOTES_DATABASE => $ENV{ACME_QUOTEDB_PATH}
                                  || File::Spec->catfile(_untaint_db_path(),
                                                   q(quotedb), q(quotes.db)
                                     );

# set this to use a remote database
# i.e. mysql
Readonly my $REMOTE => $ENV{ACME_QUOTEDB_REMOTE};

# be more specific (or more general) this is mysql
# and 'remote' can be localhost
if ($REMOTE && $REMOTE ne 'mysql') {
      croak "mysql is the only remote database supported"
               ." set ENV{ACME_QUOTEDB_REMOTE} = 'mysql'";
}
elsif ($REMOTE && $REMOTE eq 'mysql') {

    my $database = $ENV{ACME_QUOTEDB_DB};
    my $host     = $ENV{ACME_QUOTEDB_HOST};
    my $user     = $ENV{ACME_QUOTEDB_USER};
    my $pass     = $ENV{ACME_QUOTEDB_PASS};

    ACME::QuoteDB::DB::DBI->connection(
           "DBI:mysql:database=$database;host=$host",$user,$pass,
               {
                   RaiseError        => 1,
                   mysql_enable_utf8 => 1,
               }
               
           )
      || croak "can not connect to: $database $!";
}
else {

  ACME::QuoteDB::DB::DBI->connection(
           'dbi:SQLite:dbname='.$QUOTES_DATABASE, '', '',
               {
                   RaiseError => 1,
                   unicode    => 1,
                   # func/pragma's may not work here,..(probably isnt' smart anyway)
                   #count_changes  => 0,
                   #temp_store     => 2,
                   #synchronous    => 'OFF',
                   #busy_timeout => 3600000
               }
           )
      || croak "$QUOTES_DATABASE does not exist, or cant be created $!";

      # how to enable this function?
      #ACME::QuoteDB::DB::DBI->set_sql(func( 3600000, 'busy_timeout' ); 
}


sub get_current_db_path {
    return $QUOTES_DATABASE;
}

sub _untaint_db_path {
    my $sane_path = abs_path(dirname(__FILE__));
    # appease taint mode, what a dir path looks like,... (probably not)
    $sane_path =~ m{([a-zA-Z0-9-_\.:\/\\\s]+)}; #add '.', ':' for win32
    return $1 || croak 'cannot untaint db path';
}


1;

__END__

=head1 NAME

ACME::QuoteDB::DB::DBI - DBI For ACME::QuoteDB

=head1 VERSION

Version 0.1.2


=head1 SYNOPSIS

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>

=head1 DESCRIPTION

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>

see L<Class::DBI>

=head1 OVERVIEW

see L<ACME::QuoteDB>

See L<Description|/Description> above

=head1 USAGE

See Synopsis

Also see t/01* included with the distribution.
(available from the CPAN if not included on your system)

=head1 SUBROUTINES/METHODS

see L<ACME::QuoteDB>


=head2 get_current_db_path

returns the path to our current database.
determined first by $ENV{ACME_QUOTEDB_PATH}
and next by the default system path to 'quotes.db'


=head1 DIAGNOSTICS

None currently known


=head1 CONFIGURATION AND ENVIRONMENT

By default, the quotes database used by this module installs in a system path,
which means you'll need to be root (sudo :) to load and modify it.

Alternativly, one can specify a location to a quotes database (file) to use.

Set the environmental variable:

$ENV{ACME_QUOTEDB_PATH} (untested on windows)

(this has to be set before trying a database load and also (everytime) before 
using this module, obviouly)

see L<ACME::QuoteDB>

and

see L<ACME::QuoteDB::LoadDB>


=head1 DEPENDENCIES

L<Readonly>

L<version>(pragma - version numbers)

L<File::Basename>

L<Class::DBI>

=head1 INCOMPATIBILITIES


none known of

=head1 SEE ALSO

L<ACME::QuoteDB>;

L<Class::DBI>;

=head1 AUTHOR

David Wright, C<< <david_v_wright at yahoo.com> >>

=head1 BUGS AND LIMITATIONS

Please report any bugs or feature requests to C<bug-acme-thesimpsonsquotes at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-QuoteDB>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ACME::QuoteDB


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ACME-QuoteDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ACME-QuoteDB>

=item * Search CPAN

L<http://search.cpan.org/dist/ACME-QuoteDB/>

=back

=head1 ACKNOWLEDGEMENTS

=head1 LICENSE AND COPYRIGHT


Copyright 2009 David Wright, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.


lib/ACME/QuoteDB/DB/Quote.pm  view on Meta::CPAN

#$Id: Quote.pm,v 1.12 2009/09/30 07:37:09 dinosau2 Exp $
# /* vim:et: set ts=4 sw=4 sts=4 tw=78: */

package ACME::QuoteDB::DB::Quote;
use base 'ACME::QuoteDB::DB::DBI';

use 5.008005;        # require perl 5.8.5, re: DBD::SQLite Unicode
use warnings;
use strict;

#use criticism 'brutal'; # use critic with a ~/.perlcriticrc

use version; our $VERSION = qv('0.1.0');
ACME::QuoteDB::DB::Quote->table('quote');

ACME::QuoteDB::DB::Quote->columns(
                    All => qw/quot_id attr_id quote source rating/
                );

1;

__END__

=head1 NAME

ACME::QuoteDB::DB::Quote - Class::DBI For ACME::QuoteDB

=head1 VERSION

Version 0.1.0


=head1 SYNOPSIS

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>


=head1 DESCRIPTION

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>

see L<Class::DBI>

=head1 OVERVIEW

see L<ACME::QuoteDB>

See L<Description|/Description> above

=head1 USAGE

See Synopsis

Also see t/01* included with the distribution.
(available from the CPAN if not included on your system)

=head1 SUBROUTINES/METHODS

see L<ACME::QuoteDB>


=head1 DIAGNOSTICS

None currently known


=head1 CONFIGURATION AND ENVIRONMENT

if you are running perl > 5.8.5 and have access to
install cpan modules, you should have no problem installing this module

=head1 DEPENDENCIES

L<version>(pragma - version numbers)

L<Class::DBI>

L<DBD::SQLite>

=head1 INCOMPATIBILITIES

none known of

=head1 SEE ALSO

L<ACME::QuoteDB>

L<Class::DBI>

=head1 AUTHOR

David Wright, C<< <david_v_wright at yahoo.com> >>

=head1 BUGS AND LIMITATIONS

Please report any bugs or feature requests to C<bug-acme-thesimpsonsquotes at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-QuoteDB>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ACME::QuoteDB


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ACME-QuoteDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ACME-QuoteDB>

=item * Search CPAN

L<http://search.cpan.org/dist/ACME-QuoteDB/>

=back


=head1 LICENSE AND COPYRIGHT


Copyright 2009 David Wright, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.





lib/ACME/QuoteDB/DB/QuoteCatg.pm  view on Meta::CPAN

#$Id: QuoteCatg.pm,v 1.4 2009/09/30 07:37:09 dinosau2 Exp $
# /* vim:et: set ts=4 sw=4 sts=4 tw=78: */

package ACME::QuoteDB::DB::QuoteCatg;
use base 'ACME::QuoteDB::DB::DBI';

use 5.008005;        # require perl 5.8.5, re: DBD::SQLite Unicode
use warnings;
use strict;

#use criticism 'brutal'; # use critic with a ~/.perlcriticrc

use version; our $VERSION = qv('0.1.0');
ACME::QuoteDB::DB::QuoteCatg->table('quote_catg');
ACME::QuoteDB::DB::QuoteCatg->columns(All    => qw/id catg_id quot_id/);
#ACME::QuoteDB::DB::QuoteCatg->has_many(quote => 'ACME::QuoteDB::DB::Quote');

1;

__END__

=head1 NAME

ACME::QuoteDB::DB::QuoteCatg - DBI For ACME::QuoteDB


=head1 VERSION

Version 0.1.0


=head1 SYNOPSIS

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>


=head1 DESCRIPTION

This module is not meant to be used standalone it is used by C<ACME::QuoteDB>;

see L<ACME::QuoteDB>

see L<Class::DBI>

=head1 OVERVIEW

see L<ACME::QuoteDB>

See L<Description|/Description> above

=head1 USAGE

See Synopsis

Also see t/01* included with the distribution.
(available from the CPAN if not included on your system)

=head1 SUBROUTINES/METHODS

see L<ACME::QuoteDB>


=head1 DIAGNOSTICS

None currently known


=head1 CONFIGURATION AND ENVIRONMENT

if you are running perl > 5.8.5 and have access to
install cpan modules, you should have no problem installing this module

=head1 DEPENDENCIES

L<version>(pragma - version numbers)

L<Class::DBI>

L<DBD::SQLite>

=head1 INCOMPATIBILITIES

none known of

=head1 SEE ALSO

L<ACME::QuoteDB>

L<Class::DBI>

=head1 AUTHOR

David Wright, C<< <david_v_wright at yahoo.com> >>

=head1 BUGS AND LIMITATIONS

Please report any bugs or feature requests to C<bug-acme-thesimpsonsquotes at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-QuoteDB>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ACME::QuoteDB


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ACME-QuoteDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ACME-QuoteDB>

=item * Search CPAN

L<http://search.cpan.org/dist/ACME-QuoteDB/>

=back


=head1 LICENSE AND COPYRIGHT


Copyright 2009 David Wright, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.






lib/ACME/QuoteDB/LoadDB.pm  view on Meta::CPAN

#$Id: LoadDB.pm,v 1.30 2009/09/30 07:37:09 dinosau2 Exp $
# /* vim:et: set ts=4 sw=4 sts=4 tw=78: */

package ACME::QuoteDB::LoadDB;

use 5.008005;        # require perl 5.8.5, re: DBD::SQLite Unicode
use warnings;
use strict;

#use criticism 'brutal'; # use critic with a ~/.perlcriticrc

use version; our $VERSION = qv('0.1.1');

# with Text::CSV only use 'perl csv loader'
# 'one time' db load performance not a concern
BEGIN {local $ENV{PERL_TEXT_CSV} = 0}

use aliased 'ACME::QuoteDB::DB::Attribution' => 'Attr';
use aliased 'ACME::QuoteDB::DB::QuoteCatg'  => 'QuoteCatg';
use aliased 'ACME::QuoteDB::DB::Category'  => 'Catg';
use aliased 'ACME::QuoteDB::DB::Quote'    => 'Quote';
use aliased 'ACME::QuoteDB::DB::DBI'     => 'QDBI';
use File::Basename qw/dirname basename/;
use File::Glob qw(:globally :nocase);
use Encode qw/is_utf8 decode/;
use Data::Dumper qw/Dumper/;
use Carp qw/carp croak/;
use Text::CSV;
use Readonly;
use DBI;

# if not in utf8 latin1 is assumed
my $FILE_ENCODING = 'iso-8859-1';

Readonly my @QUOTE_FIELDS => qw/quote name source catg rating/;

# XXX refactor
sub new {
    my ($class, $args) = @_;

    # TODO encapsulation
    my $self = bless {}, $class;

    # store each record we extract - keys map to database fields
    # TODO proper encapsulation
    $self->{record} = {};
    $self->{record}->{quote}  = q{};
    $self->{record}->{rating} = q{};
    $self->{record}->{name}   = q{};
    $self->{record}->{source} = q{};
    $self->{record}->{catg}   = q{};

    $self->{file}        = $args->{file};
    $self->{dir}         = $args->{dir};
    $self->{data}        = $args->{data};
    $self->{file_format} = $args->{file_format};
    $FILE_ENCODING       = $args->{file_encoding} || $FILE_ENCODING;
    $self->{delim}       = $args->{delimiter};
    $self->{verbose}     = $args->{verbose};
    $self->{category}    = $args->{category};
    $self->{rating}      = $args->{rating};
    $self->{attr_source} = $args->{attr_source};
    $self->{orig_args}   = $args;
    $self->{success}     = undef;

    # start with if set
    $self->{record}->{rating} = $self->{rating};
    $self->{record}->{name}   = $self->{attr_source};
    $self->{record}->{source} = $self->{attr_source};
    if (ref $self->{category} eq 'ARRAY') {
       $self->{record}->{catg} = ();
       foreach my $c (@{$self->{category}}){
         push @{$self->{record}->{catg}}, $c;
       }
    }
    else {
       $self->{record}->{catg} = $self->{category};
    }

    # db connection info
    if ($ENV{ACME_QUOTEDB_DB}) {
        $self->{db}   = $ENV{ACME_QUOTEDB_DB};
        $self->{host} = $ENV{ACME_QUOTEDB_HOST};
        $self->{user} = $ENV{ACME_QUOTEDB_USER};
        $self->{pass} = $ENV{ACME_QUOTEDB_PASS};
    }

    if (!$args->{dry_run}){$self->{write_db} = 1};
    #if ($args->{create_db}) {$self->create_db};
    if ($args->{create_db}) {$self->create_db_tables};

    return $self;
}

sub set_record {
  my ($self, $field, $value) = @_;

  # TODO support mult-field simultanous loading

  if ($value) {
      $self->{record}->{$field} = $value;
  }

  return $self;
}

sub debug_record {
  my ($self) = @_;

  print Dumper $self->{record};

  return;
}

sub get_record {
  my ($self, $field) = @_;

  if (not $field){return $self}

  return $self->{record}->{$field};
}

sub data_to_db {
    my ($self) = @_;

    if ($self->{file} and $self->{data} and $self->{dir}){
        croak 'only file, data or dir as arg but not both'
    }
    elsif (! ($self->{file} or $self->{data} or $self->{dir})) {
        croak 'file, data or dir needed as arg'
    }

    if ($self->{file}) {
        $self->_parse_file($self->{file});
    }
    elsif ($self->{data}) {
        $self->_parse_data($self->{data});
    }
    elsif ($self->{dir}) {
        my $dir = $self->{dir};
        my $e = q{};
        foreach my $f (<$dir*>) {
           #if (! (-e $f) || -z $f) # no worky - need path info
           $self->_parse_file($f);
           $e++;
        }
        if (! $e){croak 'no files to parse in: ', Dumper $dir;};
    }
    else {
      croak 'no file source in args!', Dumper $self;
    }

    return;
}

sub _parse_file {
  my ($self, $file) = @_;

  if (!-f $file) { croak "file not found: $file" }

  if ($self->{verbose}){warn "processing file: $file\n"};

  if (($self->{file_format} eq 'csv') || ($self->{file_format} eq 'tsv')){
      $self->dbload_from_csv($file);
  }
  elsif (($self->{file_format} eq 'html') || ($self->{file_format} eq 'custom')){
      # not supported, too many possibilities
      # supply your own
      $self->dbload($file);
  }
  else {
      croak 'unsupported file format requested, format must be csv or tsv';
  }

  return;
}

sub _parse_data {
  my ($self, $data) = @_;

  if (!$data) {croak "data empty $data"}

  if ($self->{verbose}){carp 'processing data:'};

  if ($self->{file_format} =~ /(?:csv|tsv)/sm) {
      croak 'TODO: not yet supported';
      #$self->dbload_from_csv($data);
  }
  elsif (($self->{file_format} eq 'html') || ($self->{file_format} eq 'custom')){
      # not supported, too many possibilities
      # supply your own
      $self->dbload($data);
  }
  else {
      croak 'unsupported file format requested, '
             .'format must be csv, tsv. html, custom also possible';
  }

  return $self;
}

sub _confirm_header_order {
  my ($hr) = @_;

  return ($hr->{quote}  eq 'Quote'
      and $hr->{name}   eq 'Attribution Name',
      and $hr->{source} eq 'Attribution Source',
      and $hr->{catg}   eq 'Category',
      and $hr->{rating} eq 'Rating')
      or croak 'incorrect headers or header order';
}

sub dbload_from_csv {
  my ($self, $file) = @_;

  my $delim = $self->{delim} || ',';
  my $csv = Text::CSV->new({
     sep_char => $delim,
     binary => 1
  });
  $csv->column_names (@QUOTE_FIELDS);

  open my $source, '<:encoding(utf8)', $file || croak $!;

  _confirm_header_order($csv->getline_hr($source));

  while (my $hr = $csv->getline_hr($source)) {
      next unless $hr->{quote} and $hr->{name};

      if ($self->{verbose}){
          print "\n",
                'Quote:   ', $hr->{quote},"\n",
                'Name:    ', $hr->{name},"\n",
                'Source:  ', $hr->{source},"\n",
                'Category:', $hr->{catg},"\n",
                'Rating:  ', $hr->{rating},"\n\n";
      };

      $self->set_record(quote  => $hr->{quote});
      $self->set_record(name   => $hr->{name});
      $self->set_record(source => ($self->{attr_source} || $hr->{source}));
      # take user defined first
      # TODO support multi categories
      $self->set_record(catg   => ($self->{category} || $hr->{catg}));
      $self->set_record(rating => ($self->{rating} || $hr->{rating}));
      $self->write_record;
  }
  close $source or carp $!;

  return $self;
}

# sub class this - i.e. provide this method in your code (see test
# 01-load_quotes.t)
sub dbload {
  croak 'Override this. Provide this method in a sub class (child) of this object';
  # see tests: t/01-load_quotes.t for examples
}

sub _to_utf8 {
    my ($self) = @_;

    RECORD:
    foreach my $r (@QUOTE_FIELDS){
        my $val = $self->{record}->{$r};
        if (ref $val eq 'ARRAY'){
         foreach my $v (@{$val}){
           if (!is_utf8($v)){
             push @{$self->{record}->{$r}}, decode($FILE_ENCODING, $v);
           }
         }
        }
        else {
          if (!is_utf8($val)){
            $self->{record}->{$r} = decode($FILE_ENCODING, $val);
          }
        }
    }

    return $self;
}

# XXX refactor (the following 3 methods)

# one person can have many quotes, is this person in our attribution table
# already?
sub _get_id_if_attr_name_exist {
    my ($self) = @_;

    my $attr_id = q{};

    RECS:
    foreach my $c_obj (Attr->retrieve_all){
        next RECS if not $c_obj->name;
        if ($c_obj->name eq $self->get_record('name')){
          # use attribution id if already exists
          $attr_id = $c_obj->attr_id;
        }
    }
    return $attr_id;
}

sub _get_id_if_catg_exist {
    my ($self, $ctg) = @_;

    my $catg_id = q{};
    # get category id
    RECS:
    foreach my $c_obj (Catg->retrieve_all){
        next RECS if not $c_obj->catg;
        if ($c_obj->catg eq $ctg){
          # use cat_id if already exists
          $catg_id = $c_obj->catg_id;
        }
    }
    return $catg_id;
}

#TODO : refactor
sub write_record {
    my ($self) = @_;

    $self->_to_utf8;

    if ($self->{verbose} and $self->get_record('name')){
        print 'Attribution Name: ',$self->get_record('name'),"\n";
    };

    my $attr_id = $self->_get_id_if_attr_name_exist;
    # nope, ok, add them
    if (not $attr_id) { # attribution record does not already exist, 
                        # create new entry
        if ($self->{write_db}) {
            $attr_id = Attr->insert({
                          name   => $self->get_record('name'),
                       });
        }
    }

    my $catg_ids = ();
    if ($self->{write_db}) {
      my ($catg) = $self->get_record('catg');
      if (! ref $catg){ # 'single' value
        my $catg_id = $self->_get_id_if_catg_exist($catg);
        if (!$catg_id) {
          # category does not already exist, 
          # create new entry
          $catg_id = Catg->insert({catg => $catg});
        }
        push @{$catg_ids}, $catg_id;
      } # support multi catg
      elsif (ref $catg eq 'ARRAY'){
          foreach my $c (@{$catg}){
            my $catg_id = $self->_get_id_if_catg_exist($c);
            if (!$catg_id) { # category does not already exist, 
               # create new entry
               $catg_id = Catg->insert({catg => $c});
            }
            push @{$catg_ids}, $catg_id;
          }
      }
    }

    $self->_display_vals_if_verbose;

    if ($self->{write_db}) {
       my $qid = Quote->insert({
               attr_id  => $attr_id,
               quote    => $self->get_record('quote'),
               source   => $self->get_record('source'),
               rating   => $self->get_record('rating')
       }) or croak $!;

       if ($qid) {
         my $id;
         foreach my $cid (@{$catg_ids}){
           $id =   QuoteCatg->insert({
                 quot_id  => $qid,
                 catg_id  => $cid,
            }) or croak $!;
         }
       }
    }
    # confirmation?
    # TODO add a test for failure
    if ($self->{write_db} and not $attr_id) {croak 'db write not successful'}

    #$self->set_record(undef);
    $self->{record} = {};
    $self->_reset_orig_args;

    if ($self->{write_db}) {
        $self->success(1);
    }

    return $self->success;
}

sub _reset_orig_args {
  my ($self) = @_;

  $self->{record}->{rating} = $self->{orig_args}->{rating};
  $self->{record}->{name}   = $self->{orig_args}->{attr_source};
  $self->{record}->{source} = $self->{orig_args}->{attr_source};
  if (ref $self->{orig_args}->{category} eq 'ARRAY') {
     foreach my $c (@{$self->{orig_args}->{category}}){
       push @{$self->{record}->{catg}}, $c;
     }
  } 
  else {
    $self->{record}->{catg} = $self->{orig_args}->{category};
  }

}

sub success {
  my ($self, $flag) = @_;

  $self->{success} ||= $flag;

  return $self->{success};
};

sub _display_vals_if_verbose {
    my ($self) = @_;

    if ($self->{verbose}){
        #print 'Quote: ',   $self->get_record('quote'),"\n";
        #print 'Source: ',  $self->get_record('source'),"\n";
        #print 'Category: ',$self->get_record('catg'),"\n";
        #print 'Rating: ',  $self->get_record('rating'),"\n";
        print Dumper $self->{record};
    }

    return $self;
}

#sub create_db {
#    my ($self) = @_;
#
#    if ($self->{db} and $self->{host}) {
#      $self->create_db_mysql();
#    }
#}

sub create_db_tables {
    my ($self) = @_;

    if ($self->{db} and $self->{host}) {
      #$self->create_db_mysql();
      $self->create_db_tables_mysql();
    }
    else {
      create_db_tables_sqlite();
    }

    return $self;

}

# XXX  we want the user to supply a pre created database.
# created as such 'CREATE DATABASE $dbn CHARACTER SET utf8 COLLATE utf8_general_ci'
# this get's into too many isseuwith privs and database creation
#Sat Aug 22 13:42:37 PDT 2009
# did this:
#mysql> CREATE DATABASE acme_quotedb CHARACTER SET utf8 COLLATE utf8_general_ci;
#mysql> grant usage on *.* to acme_user@localhost identified by 'acme';
#mysql> grant all privileges on acme_quotedb.* to acme_user@localhost ;

#sub create_db_mysql {
#    my ($self) = @_;
#
#     # hmmmm, what about priv's access, etc
#     # maybe user need to supply a db, they have 
#     # access to, already created (just the db though)
#     ## create our db
#     #my $dbhc = DBI->connect('DBI:mysql:database=mysql;host='
#     #                           .$self->{host}, $self->{user}, $self->{pass})
#     #      || croak "db cannot be accessed $! $DBI::errstr";
#
#     #my $dbn = $self->{db};
#     #my $db = qq(CREATE DATABASE $dbn CHARACTER SET utf8 COLLATE utf8_general_ci);
#     # eval {
#     #     $dbhc->do($db) or croak $dbhc->errstr;
#     # };
#     # $@ and croak 'Cannot create database!';
#     # $dbhc->disconnect; $dbhc = undef;
#
#     my $drh = DBI->install_driver('mysql');
#     my $rc = $drh->func("dropdb", $self->{db}, 
#                    [$self->{host}, $self->{user}, $self->{password}],
#                    'admin'
#                );
#
#        $rc = $drh->func("createdb", $self->{db}, 
#                    [$self->{host}, $self->{user}, $self->{password}],
#                    'admin'
#                );
#}

# XXX refactor with sqlite
sub create_db_tables_mysql {
    my ($self) = @_;

     # connect to our db
     my $c = $self->{db}.';host='.$self->{host};
     my $dbh = DBI->connect(
             "DBI:mysql:database=$c", $self->{user}, $self->{pass})
               || croak "db cannot be accessed $! $DBI::errstr";

    eval {
        $dbh->do('DROP TABLE IF EXISTS quote;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS quote (
            quot_id        INTEGER NOT NULL AUTO_INCREMENT, 
            attr_id        INTEGER,
            quote          TEXT,
            source         TEXT,
            rating         REAL,
            PRIMARY KEY(quot_id)
            );')
            #)CHARACTER SET utf8 COLLATE utf8_general_ci;
            #) ENGINE = MYISAM CHARACTER SET utf8 COLLATE utf8_general_ci; 
            or croak $dbh->errstr;

        $dbh->do('DROP TABLE IF EXISTS attribution;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS attribution (
            attr_id  INTEGER NOT NULL AUTO_INCREMENT,
            name     TEXT,
            PRIMARY KEY(attr_id)
            );') or croak $dbh->errstr;

        $dbh->do('DROP TABLE IF EXISTS category;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS category (
            catg_id    INTEGER NOT NULL AUTO_INCREMENT, 
            catg       TEXT,
            PRIMARY KEY(catg_id)
            );') or croak $dbh->errstr;

        $dbh->do('DROP TABLE IF EXISTS quote_catg;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS quote_catg (
            id    INTEGER NOT NULL AUTO_INCREMENT, 
            catg_id    INTEGER, 
            quot_id    INTEGER, 
            PRIMARY KEY(id)
            );') or croak $dbh->errstr;


       $dbh->disconnect or warn $dbh->errstr;

       $dbh = undef;
    };

    return $@ and croak 'Cannot create database tables!';

}

sub create_db_tables_sqlite {

     my $db = QDBI->get_current_db_path;

     #XXX is there really no way to do this with the existing 
     # connection?!(class dbi)
     my $dbh = DBI->connect('dbi:SQLite:dbname='.$db, '', '')
       || croak "$db cannot be accessed $! $DBI::errstr";

    #-- sqlite does not have a varchar datatype: VARCHAR(255)
    #-- A column declared INTEGER PRIMARY KEY will autoincrement.
    eval {
        $dbh->do('DROP TABLE IF EXISTS quote;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS quote (
            quot_id        INTEGER PRIMARY KEY, 
            attr_id        INTEGER,
            quote          TEXT,
            source         TEXT,
            rating         REAL
            );')
            or croak $dbh->errstr;

        $dbh->do('DROP TABLE IF EXISTS attribution;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS attribution (
            attr_id  INTEGER PRIMARY KEY,
            name     TEXT
            );') or croak $dbh->errstr;

        $dbh->do('DROP TABLE IF EXISTS category;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS category (
            catg_id    INTEGER PRIMARY KEY, 
            catg       TEXT
            );') or croak $dbh->errstr;

        $dbh->do('DROP TABLE IF EXISTS quote_catg;') or croak $dbh->errstr;

        $dbh->do('CREATE TABLE IF NOT EXISTS quote_catg (
            id         INTEGER PRIMARY KEY,
            catg_id    INTEGER,
            quot_id    INTEGER
            );') or croak $dbh->errstr;

        $dbh->disconnect or carp $dbh->errstr;

        $dbh = undef;
    };

    return $@ and croak 'Cannot create database tables!';

}

q(My cat's breath smells like cat food. --Ralph Wiggum);


__END__

=head1 NAME

ACME::QuoteDB::LoadDB - Database loader for ACME::QuoteDB

=head1 VERSION

Version 0.1.1

=head1 SYNOPSIS

load a csv file to quotes database 

  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file => '/home/me/data/simpsons_quotes.csv',
                              file_format => 'csv',
                          });
  
  $load_db->data_to_db;

  print $load_db->success; # bool

header columns of the csv file as follows:

"Quote", "Attribution Name", "Attribution Source", "Category", "Rating"


=head1 DESCRIPTION

This module is part of L<ACME::QuoteDB>. This is a Database loader, it
takes (quotes) data and loads into a database 
(currently L<sqlite3 or mysql|/'CONFIGURATION AND ENVIRONMENT'>),
which is then accessed by L<ACME::QuoteDB>.


There are several ways to get quote data into the db via this loader:
(There are more aimed towards 'batch' operations, i.e load a bunch of 
records quickly)

=over 4

=item 1

* csv file (pre determined format)

     pros: quick and easy to load.
     cons: getting the quotes data into the correct format need by this module

=item 2

* any source.

    One can take quote data from any source, override
    L<ACME::QuoteDB::LoadDB/dbload> loader methods to populate a record
    and write it to the db.
     pros: can get any quote data into the db.
     cons: you supply the method. depending on the complexity of the data
           source and munging required this will take longer then the other 
           methods.

=back

=head3 load from csv file

The pre defined csv file format is:

format of file is as follows: (headers)
"Quote", "Attribution Name", "Attribution Source", "Category", "Rating"
  
   for example:
   "Quote", "Attribution Name", "Attribution Source", "Category", "Rating"
   "I hope this has taught you kids a lesson: kids never learn.","Chief Wiggum","The Simpsons","Humor",9
   "Sideshow Bob has no decency. He called me Chief Piggum. (laughs) Oh wait, I get it, he's all right.","Chief Wiggum","The Simpsons","Humor",8


   my $load_db = ACME::QuoteDB::LoadDB->new({
                               file => dirname(__FILE__).'/data/simpsons_quotes.csv',
                               file_format => 'csv',
                           });
   
   $load_db->data_to_db;

   if (!$load_db->success){print 'failed'}


=head3 load from any source

If those dont catch your interest, ACME::QuoteDB::LoadDB is sub-classable, 
so one can extract data anyway they like and populate the db themselves. 
(there is a test that illustrates overriding the stub method, 'dbload')

you need to populate a record data structure:

    $self->set_record(quote  => q{}); # mandatory
    $self->set_record(name   => q{}); # mandatory
    $self->set_record(source => q{}); # optional but useful
    $self->set_record(catg   => q{}); # optional but useful
    $self->set_record(rating => q{}); # optional but useful

    # then to write the record you call
    $self->write_record;

NOTE: this is a one record at a time operation, so one would perform 
this within a loop. there is no bulk write operation currently.


=head1 OVERVIEW

You have a collection of quotes (adages/sayings/quips/epigrams, etc) for
whatever reason, you use these quotes for whatever reason, you want to 
access these quotes in a variety of ways,...

This module is part of L<ACME::QuoteDB>. 

This is a Database loader, it takes data (quotes) and loads into a database, 
which is then accessed by L<ACME::QuoteDB>.

See L<ACME::QuoteDB>.


=head1 USAGE

General usage, csv/tsv file in the expected format loaded to the database

  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file => '/home/me/data/sorta_funny_quotes.tsv',
                              file_format => 'tsv',
                              delimiter => "\t",
                              # provide a attr_source for all (if not in data)
                              # data is used first, if not defined use below
                              attr_source => 'Things Randomly Overheard',
                              # provide a category for all (if not in data)
                              category => 'Humor',
                              # provide a rating for all
                              rating   => 5, # scale 1-10
                          });
  $load_db->data_to_db;

  if (!$load_db->success){print 'failed'}

Also see t/01-load_quotes.t included with the distribution.

(available from the CPAN if not included on your system)


=head1 SUBROUTINES/METHODS 

This is an Object Oriented module. There is no proceedural interface.

=head2 new

  Instantiate a ACME::QuoteDB::LoadDB object.

  Argument is a hash ref. Params below 


=head4 Data Related Parameters

=over 4

=item  file or directory - one or the other required (not both)

if file, must be in our defined format, full path is needed.

if directory, full path is needed, can supply a basic glob type filter.

example:

{ file  => '/home/me/data/simpsons_quotes.csv' }

{ dir  => '/home/me/data/*.csv' }
 

=item  file_format - required

can be one of: 'csv', 'tsv', 'custom', or 'html'

if 'html' or 'custom' you must supply the method for parsing. 
(see tests for examples)

example:

{ file_format => 'csv' }


=item  delimiter - optional, default is a comma for csv

csv/tsv options tested: comma(,) and tab(\t)

'html' - not applicable

example:

{ delimiter => "\t" }

=item  category - optional, extracted from data if exists, otherwise will use what you
specify

TODO one quote to multiple categories

=item  attr_source - extracted from data if exists, otherwise will use what you
specify

example:

{attr_source => 'The Simpsons'}

=item  file_encoding - optional

Files being loaded are assumed to be utf8 encoded. if utf8 flag is not detected,
falls back to latin1 (iso-8859-1). If neither of these is correct, set this
option to the encoding your file is in.

=back

=head4 Operation Related Parameters

=over 4

=item  dry_run - optional

do not write to the database. Use with verbose flag to see what would have beed
written.

This can be helpful for testing the outcome of Loading results. 

i.e. like to confirm that the parsing of your data is correct

example:

{
 dry_run => 1,
 verbose => 1
}

=item  verbose  - optional

display to STDOUT what is being done

This can be helpful for testing quotes extraction from file parsing

example:

{verbose => 1}

=item  create_db  - optional (boolean)

L<ACME::QuoteDB::LoadDB> default behaviour is to always assume there is a
database and append new data to that. (It is usually only needed the first 
time one load's data)

setting this parameter to a true value will create a new database.
(so while this is an optional param, it is required at least once ;)

B<NOTE: it is not intelligent, if you hand it a populated database,
it will happily overwrite all data>

B<AGAIN: setting this param will destroy the current database, creating a new
empty one>

example:

{create_db => 1}


=back 

=head2 data_to_db

takes the data input provided to new, process' it and writes to the database.
should appropriatly blow up if not successful

=head2 dbload_from_csv

takes a csv file (in our defined format) as an argument, parses it and writes
the data to the database. (uses L<Text::CSV> with pure perl parser)
utf-8 safe. (opens file as utf8)

will croak with message if not successful


=head2 dbload

if your file format is set to 'html' or 'custom' you must 
define this method to do your parsing in a sub class.

Load from html is not supported because there are too many 
ways to represt the data. (same with 'custom')
(see tests for examples - there is a test for loading a 'fortune' file format)

One can subclass ACME::QuoteDB::LoadDB and override dbload,
to do our html parsing

=head2 debug_record

dump record (show what is set on the internal data structure) 

e.g. Data::Dumper

=head2 set_record

only needed it one plans to sub-class this module.
otherwise, is transparent in usage.

if you are sub-classing this module, you would have to populate 
this record. (L</write_record> knows about/uses this data structure)

possible fields consist of:

$self->set_record(quote  => q{});
$self->set_record(rating => q{});
$self->set_record(name   => q{});
$self->set_record(source => q{});
$self->set_record(catg   => q{});

currently can only set one attribute at a time.

ie. you cant do this:

 $self->set_record(
            name   => $name,
            source => $source
 );

 # or this even
 $self->set_record({
            name   => $name,
            source => $source
 });

=head2 get_record

only useful it one plans to sub-class this module.
otherwise, is transparent in usage.

if you are sub-classing this module, you would have to populate 
this record. [see L</set_record>] 

(L</write_record> knows about/uses this data structure)

possible fields consist of:

$self->get_record('quote');
$self->get_record('rating');
$self->get_record('name');
$self->get_record('source');
$self->get_record('catg');
 
=head2 success

indicates that the database load was successfull 

is undef on failure or if trying a L</dry_run>

 
=head2 write_record

takes the data structure 'record' '$self->get_record'
(which must exist). checks if attribution name ($self->get_record('name')) exists, 
if so, uses existing attribution name, otherwsie creates a new one

Load from html is not supported because there are too many 
ways to represt the data. (see tests for examples)

One can subclass ACME::QuoteDB::LoadDB and override dbload,
to do our html parsing

=head2 create_db_tables
 
create an empty quotes database (with correct tables). 

(usually only performed the first time you load data)

B<NOTE: will overwrite ALL existing data>

Set 'create_db' parameter (boolean) to a true value upon instantiation 
to enable.

The default action is to assume the database (and tables) exist and just
append new L<ACME::QuoteDB::LoadDB> loads to that.

=begin comment
 
    keep pod coverage happy.

    # Coverage for ACME::QuoteDB::LoadDB is 71.4%, with 3 naked subroutines:
    # Catg
    # Quote
    # Attr
    # QuoteCatg

    pod tests incorrectly state, Catg, Quote and Attr are subroutines, well they
    are,... (as aliases) but are imported into here, not defined within
    
    TODO: explore the above (is this a bug, if so, who's?, version effected, 
    create use case, etc) 
   
 
=head2 Attr

=head2 Catg

=head2 Quote

=head2 QuoteCatg

=head2 QDBI

=end comment

=begin comment

    These methods are more or less private.
    I may use them in another modules but You don't need to use or
    know about them, so I will obfuscate them here

=head2 create_db_tables_sqlite

=head2 create_db_tables_mysql

=end comment

=head1 DIAGNOSTICS

An error such as:

C<DBD::SQLite::db prepare_cached failed: no such table: ,...>

probably means that you do not have a database created in the correct format.

basically, you need to create the database, usually, on a first run

you need to add the flag:

create_db => 1, # first run, create the db

appending to an existing database is the default behaviour

see L</create_db_tables>


=head1 CONFIGURATION AND ENVIRONMENT

if you are running perl > 5.8.5 and have access to
install cpan modules, you should have no problem installing this module
(utf-8 support in Text::CSV not avaible until 5.8 - we don't support 'non
utf-8 mode)

=over 1

=item * By default, the quotes database used by this module installs in the 
system path, 'lib', (See L<Module::Build/"INSTALL PATHS">)
as world writable - i.e. 0666 (and probably owned by root)
If you don't like this, you can modify Build.PL to not chmod the file and it
will install as 444/readonly, you can also set a chown in there for whoever
you want to have RW access to the quotes db.

Alternativly, one can specify a location to a quotes database (file) to use.
(Since the local mode is sqlite3, the file doesn't even need to exist, just
needs read/write access to the path)

Set the environmental variable:

$ENV{ACME_QUOTEDB_PATH} (untested on windows)

(this has to be set before trying a database load and also (everytime) before 
using this module, obviouly)

Something such as:

BEGIN {
    # give alternate path to the DB
    # doesn't need to exist, will create
    $ENV{ACME_QUOTEDB_PATH} = '/home/me/my_stuff/my_quote_db'
}

* (NOTE: be sure this (BEGIN) exists *before* the 'use ACME::QuoteDB' lines)

The default is to use sqlite3.

In order to connect to a mysql database, several environmental variables
are required.

BEGIN {
    # have to set this to use remote database
    $ENV{ACME_QUOTEDB_REMOTE} =  'mysql';
    $ENV{ACME_QUOTEDB_DB}     =  'acme_quotedb';
    $ENV{ACME_QUOTEDB_HOST}   =  'localhost';
    $ENV{ACME_QUOTEDB_USER}   =  'acme_user';
    $ENV{ACME_QUOTEDB_PASS}   =  'acme';
}

Set the above in a begin block and all operations are the same but now
you will be writing to the remote mysql database specified.

(The user will need read/write permissions to the db/tables)
(mysql admin duties are beyond the scope of this module)

The only supported databases at this time are sqlite and mysql.

It is trivial to add support for others


see: L<LOADING QUOTES|ACME::QuoteDB/LOADING QUOTES>


=back 


=head1 DEPENDENCIES

L<Carp>

L<Data::Dumper>

L<criticism> (pragma - enforce Perl::Critic if installed)

L<version>(pragma - version numbers)

L<aliased>

L<Test::More>

L<DBD::SQLite>

L<DBI>

L<Class::DBI>

L<File::Basename>

L<Readonly>

L<Module::Build>


=head1 INCOMPATIBILITIES

none known of

=head1 SEE ALSO

man fortune (unix/linux)

L<Fortune>

L<fortune>

L<ACME::QuoteDB>


=head1 AUTHOR

David Wright, C<< <david_v_wright at yahoo.com> >>

=head1 BUGS AND LIMITATIONS

Please report any bugs or feature requests to C<bug-acme-quotedb-loaddb at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ACME-QuoteDB::LoadDB>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.


=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ACME::QuoteDB::LoadDB


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=ACME-QuoteDB::LoadDB>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/ACME-QuoteDB::LoadDB>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/ACME-QuoteDB::LoadDB>

=item * Search CPAN

L<http://search.cpan.org/dist/ACME-QuoteDB::LoadDB/>

=back

=head1 ACKNOWLEDGEMENTS

The construction of this module was guided by:

Perl Best Practices - Conway

Test Driven Development

Object Oriented Programming

Gnu is Not Unix

vim 

Debian Linux

Mac OSX

The collective wisdom and code of The CPAN

=head1 LICENSE AND COPYRIGHT

Copyright 2009 David Wright, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.


=cut

1; # End of ACME::QuoteDB::LoadDB

t/00-load.t  view on Meta::CPAN

#!perl -T

use Test::More tests => 1;

BEGIN {
        use_ok( 'ACME::QuoteDB' );
}

diag( "Testing ACME::QuoteDB $ACME::QuoteDB::VERSION, Perl $], $^X" );

t/01-load_quotes.t  view on Meta::CPAN

#!perl -T

use strict;
use warnings;

use Carp qw/croak/;

BEGIN {
    eval "use DBD::SQLite";
    $@ and croak 'DBD::SQLite is a required dependancy';
}

use ACME::QuoteDB;
use ACME::QuoteDB::LoadDB;

#use Test::More 'no_plan';
use Test::More tests => 29;
use File::Basename qw/dirname/;
use Data::Dumper qw/Dumper/;
use File::Spec;

# A. test dry run, show if parsing is succesful but don't load the database
{
  my $q = File::Spec->catfile((dirname(__FILE__),'data'), 
                               'simpsons_quotes.tsv.csv'
          );

  # only 2 supported formats: 'simple' text (which is the default) and 'tsv' 
  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file        => $q,
                              file_format => 'tsv', # the only supported format
                              delimiter   => "\t",
                              # provide a category for all (if not in data)
                              category    => 'Humor',
                              # provide a attr_source for all (if not in data)
                              attr_source => 'The Simpsons',
                              dry_run     => 1, # don't write to the database
                              #verbose    => 1, # show what is being done
                              create_db   => 1, # need to create the database
                          });
  isa_ok $load_db, 'ACME::QuoteDB::LoadDB';

  $load_db->data_to_db;

  #flag not set on dry_run
  is $load_db->success, undef; # success only after a database write, 
  
  my $sq = ACME::QuoteDB->new;
  isa_ok $sq, 'ACME::QuoteDB';
  ok ! $sq->list_attr_names;
}

{
  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file =>
                              #dirname(__FILE__).'/data/simpsons_quotes.tsv.csv',
                              File::Spec->catfile(
                                  (dirname(__FILE__),'data'), 
                                     'simpsons_quotes.tsv.csv'
                              ),
                              file_format => 'tsv',
                              delimiter => "\t",
                              #verbose => 1,
                              create_db   => 1, # first run, create the db
                              # provide a attr_source for all (if not in data)
                              attr_source => 'The Simpsons',
                              # provide a category for all (if not in data)
                              category => 'Humor',
                              # provide a rating for all
                              rating   => 6,
                          });
  
  isa_ok $load_db, 'ACME::QuoteDB::LoadDB';

  $load_db->data_to_db;

  ok $load_db->success;
  is $load_db->success, 1;
   
  my $sq = ACME::QuoteDB->new;
  isa_ok $sq, 'ACME::QuoteDB';
  
  # expected attribution list from our data
  my @expected_attribution_list = (
           'Apu Nahasapemapetilon',
           'Chief Wiggum',
           'Comic Book Guy',
           'Grandpa Simpson',
           'Ralph Wiggum',
          );
  
  is( $sq->list_attr_names, join "\n", sort @expected_attribution_list);
}

{
  #my $sqf = dirname(__FILE__) .  '/data/simpsons_quotes.csv';

  my $sqf = File::Spec->catfile((dirname(__FILE__),'data'), 
                               'simpsons_quotes.csv'
          );
  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file        => $sqf,
                              file_format => 'csv',
                              #delimiter  => ",", # comma is default
                              #verbose    => 1,
                              create_db   => 1, # first run, create the db
                          });
  
  isa_ok $load_db, 'ACME::QuoteDB::LoadDB';
  $load_db->data_to_db;
  ok $load_db->success;
  is $load_db->success, 1;
   
  my $sq = ACME::QuoteDB->new;
  isa_ok $sq, 'ACME::QuoteDB';
  
  # expected attribution list from our data
  my @expected_attribution_list = (
           'Apu Nahasapemapetilon',
           'Chief Wiggum',
           'Comic Book Guy',
           'Grandpa Simpson',
           'Ralph Wiggum',
          );
  
  is( $sq->list_attr_names, join("\n", sort(@expected_attribution_list)));
}

{ # load from html is not supported because there are too many 
  # ways to represt the data.
  # this is an example of extracting quotes from html:
  # subclass ACME::QuoteDB::LoadDB and override dbload,
  # to do our html parsing
  package LoadQuoteDBFromHtml;
  use base 'ACME::QuoteDB::LoadDB';
  use Carp qw/croak/;
  use Data::Dumper qw/Dumper/;
  use HTML::TokeParser;

    sub dbload {
      my ($self, $file) = @_;

      my $p = HTML::TokeParser->new($file) || croak $!;
    
      while (my $token = $p->get_tag("p")) {
          my $idn = $token->[1]{class} || q{};
          my $id = $token->[1]{id} || q{}; # if a quotation is continued (id
          #is not set)
          next unless $idn and ( $idn eq 'quotation' || $idn eq 'source');
          #my $data = $p->get_trimmed_text("/p");
          my $data = $p->get_text('p', 'cite');
          #warn Dumper $data;
          # XXX see $self->set_record in ACME::QuoteDB::LoadDB for fields
          # to populate
          if ($idn eq 'quotation' and $id) {
              $self->set_record(quote => $data);
          }
          elsif ($idn eq 'quotation' and not $id) {
              my $d = $self->get_record('quote') || q{};
              $self->set_record(quote => qq{$d $data});
          }
          elsif ($idn eq 'source'){
              my ($name, $source) = split /,/, $data;
              if ($name) {
                chomp $name;
                $name =~ s/\A\s+//xms;
                $name =~ s/\s+\z//xms;
              }
              $self->set_record(name   => $name);
              $self->set_record(source => $source);

              # TODO
              #$self->set_record({
              #           name   => $name,
              #           source => $source
              #});
          }
    
          if ($self->get_record('quote') and $self->get_record('name')) {
              # we provided a category and rating, otherwise would have to
              # parse from data too
              $self->set_record(catg => $self->{category});
              $self->set_record(rating => $self->{rating});

              # TODO
              #$self->set_record({
              #           catg => $self->{category},
              #           rating => $self->{rating}
              #});

              #$self->debug_record;
              $self->write_record;
          }
      }
    }

  package main;
  use File::Basename qw/dirname/;
  use File::Spec;

  # simple glob pattern accepted
  my $py_quot = File::Spec->catfile(
                         dirname(__FILE__), 'data', 'www.amk.ca', 'quotations',
                                'python-quotes', '*.html'
                );

  my $load_db = LoadQuoteDBFromHtml->new({
                              dir => $py_quot,
                              file_format => 'html',
                              create_db   => 1, # first run, create the db
                              # provide a category for all (if not in data)
                              category => 'Python',
                              # provide a rating for all (if not in data)
                              # and desired
                              rating => 5,
                          });
  
  isa_ok $load_db, 'ACME::QuoteDB::LoadDB';
  $load_db->data_to_db;
  ok $load_db->success;
  is $load_db->success, 1;
   
  my $sq = ACME::QuoteDB->new;
  isa_ok $sq, 'ACME::QuoteDB';
   
  # expected attribution list from our data (ok, so the data has some
  # 'inconsistancies',...
  #grep "'source'" *.html|sed -e 's/,.*$//g' -e 's/<\/p>//g' -e s'/^.*>//g'| sort -u    
  #seems more accurate: grep "'source'" *.html|sed -e "s/^.*source'>//g" -e 's/,.*$//g' | sort -u
  my @expected_attribution_list = (
            'Aaron Watters',
            'Alex Martelli',
            'Allan Bailey',
            'A.M. Kuchling',
            'Andrew Mullhaupt',
            'Anthony Baxter',
            'An unknown poster and Fredrik Lundh',
            'Brett Cannon',
            'Christian Tismer',
            'Donald E. Knuth',
            'Donn Cave uses sarcasm with devastating effect',
            'Fred Drake on the Documentation SIG',
            'Fredrik Lundh',
            'From Kim "Howard" Johnson\'s',
            'Gareth McCaughan',
            'Gordon McMillan',
            'Guido van Rossum',
            'GvR',
            'Jack Jansen',
            'Jeremy Hylton',
            'Jim Ahlstrom',
            'Jim Fulton and Paul Everitt on the Bobo list',
            'Jim Fulton and Tim Peters',
            'John Eikenberry on the Bobo list',
            'John Holmgren',
            'John J. Lehmann',
            'John Redford',
            'Joseph Strout',
            "Kristj\x{E1}n J\x{F3}nsson",
            'Larry Wall',
            'Mark Jackson',
            'Matthew Lewis Carroll Smith',
            'Michael Palin',
            'Mike Meyer',
            'Nick Seidenman and Guido van Rossum',
            'Paul Boddie',
            'Paul Prescod',
            'Paul Winkler',
            'Sriram Srinivasan',
            'Steve Majewski',
            'Steven D. Majewski',
            'Tim Berners-Lee',
            'Tim Chase',
            'Timothy J. Grant and Tim Peters',
            'Tim Peters',
            'Told by Nick Leaton',
            'Tom Christiansen',
            'Vladimir Marangozov and Tim Peters',
        );
  
  is( $sq->list_attr_names, join "\n", sort @expected_attribution_list);
}

{ # prove load a fortune format file
  # this is an example of importing a file in the 'fortune' format
  # subclass ACME::QuoteDB::LoadDB and override dbload, to do our parsing
  package Fortune2QuoteDB;
  use base 'ACME::QuoteDB::LoadDB';
  use Carp qw/croak/;
  use Data::Dumper qw/Dumper/;

  sub dbload {
    my ($self, $file) = @_;

    open my $source, '<:encoding(utf8)', $file || croak $!;

    local $/ = $self->{delim};

    my $q = q{};

    while (my $line = <$source>){

      #$self->debug_record;

      $q .= $line;
      $q =~ s{\A\s+}{}xsmg;
      $q =~ s{\s+\z}{}xsmg;
      $q =~ s/\s+$self->{delim}//g;

      $self->set_record(quote => $q);

      my $name = $self->get_record('quote');
      $name =~ s{\A(.*?):.*}{$1}xmsg; # not accurate,

      $self->set_record(name   => $name);
      $self->set_record(source => $self->{attr_source});
      $self->set_record(catg   => $self->{category} || q{});
      $self->set_record(rating => $self->{rating} || q{});

      $self->write_record;
      $q = q{};
    }
    close $source || croak $!;
  }

  package main;
  use File::Basename qw/dirname/;
  use File::Spec;

  my $fd = File::Spec->catfile(dirname(__FILE__), 'data', 'futurama');

  my $load_db = Fortune2QuoteDB->new({
                              file        => $fd,
                              file_format => 'custom',
                              delimiter  => "%",
                              #verbose     => 1,
                              create_db   => 1, # first run, create the db
                              # provide a attr_source for all (if not in data)
                              # use fortune filename for 'source'
                              attr_source => 'Futurama',
                              # provide a category for all (if not in data)
                              category    => 'Humor',
                              # provide a rating for all
                              rating      => 6.2,
                          });
  
  isa_ok $load_db, 'ACME::QuoteDB::LoadDB';

  $load_db->data_to_db;

  is $load_db->success, 1;
   
  my $sq = ACME::QuoteDB->new;
  isa_ok $sq, 'ACME::QuoteDB';
  is( $sq->list_attr_sources, 'Futurama');
  is( $sq->list_categories, 'Humor');

  is scalar @{$sq->get_quotes({AttrName => 'Leela'})}, 2;
  is scalar @{$sq->get_quotes({AttrName => 'Professor'})}, 2;
  is scalar @{$sq->get_quotes({AttrName => 'Fry'})}, 4;
  is scalar @{$sq->get_quotes({AttrName => 'Bender'})}, 1;
  is scalar @{$sq->get_quotes({AttrName => 'Zapp'})}, 1;
  # set_quote? update  futurama to be in humor and cartoon
}

t/02-get_quotes.t  view on Meta::CPAN

#!perl -T

use strict;
use warnings;

use ACME::QuoteDB;
use ACME::QuoteDB::LoadDB;

#use Test::More 'no_plan';
use Test::More tests => 33;
use File::Basename qw/dirname/;
use Data::Dumper qw/Dumper/;
use Carp qw/croak/;
use File::Spec;


BEGIN {
    eval "use DBD::SQLite";
    $@ and croak 'DBD::SQLite is a required dependancy';
}

#make test db writeable
sub make_test_db_rw {
     use ACME::QuoteDB::DB::DBI;
     # yeah, this is supposed to be covered by the build process
     # but is failing sometimes,...
     chmod 0666, ACME::QuoteDB::DB::DBI->get_current_db_path;
}

{
    make_test_db_rw;

    my $q = File::Spec->catfile((dirname(__FILE__),'data'), 'simpsons_quotes.csv');
    my $load_db = ACME::QuoteDB::LoadDB->new({
                                file        => $q,
                                file_format => 'csv',
                                create_db   => 1, # first run, create the db
                            });

    isa_ok $load_db, 'ACME::QuoteDB::LoadDB';
    $load_db->data_to_db;
    is $load_db->success, 1;
}

my $sq = ACME::QuoteDB->new;

# some 'the simpsons' characters
my @expected_attr_name_list = (
         'Apu Nahasapemapetilon',
         'Chief Wiggum',
         'Comic Book Guy',
         'Grandpa Simpson',
         'Ralph Wiggum',
        );
is( $sq->list_attr_names, join "\n", sort @expected_attr_name_list);

#warn $sq->get_quote, "\n";
ok $sq->get_quote; # default get random quote
ok $sq->get_quote =~ m{\w+};

{
    my $res = $sq->get_quote({AttrName => 'apu'});
    if ($res =~ m/apu/xmsgi) {
        pass 'ok';
    } 
    else {
        fail 'a supposed apu quote, should contain "Apu" within,...';
    }
}

{
    my $res = $sq->get_quote({AttrName => 'chief wiggum'});
    if ($res =~ m/(chief|clancy|wiggum|police|gun|donut)/xmsgi) {
        pass 'ok';
    } 
    else {
        fail 'a supposed chief wiggum quote, 
               should contain "chief wiggum" within,...';
    }
}

{
    my $res = $sq->get_quote({AttrName => 'wiggum'});
    if ($res =~ m/(ralph|chief|wiggum)/xmsgi) {
        pass 'ok';
    } 
    else {
        fail 'a supposed wiggum quote, should 
              contain "ralph or chief" within,...';
    }
}

{
    my $q= 'I hope this has taught you kids a lesson: ' .
                     qq{kids never learn.\n-- Chief Wiggum};
    my $res = $sq->get_quote({AttrName => 'Chief Wiggum', Rating => '9.0'});
    if ($res && $res eq $q) {
        pass 'ok';
    } 
    else {
        fail 'quote should be found';
    }
}

eval { # param mispelled
    $sq->get_quote({Charcter => 'bart'});
};
if ($@) {
    pass if $@ =~ m/unsupported argument option passed/;
} else {fail 'should alert user on non existant params' };


eval {
    $sq->get_quote({Limit => '4'}); # only avail for 'get_quotes'
};
if ($@) {
    pass if $@ =~ m/unsupported argument option passed/;
} else {fail 'should alert user on non existant params' };


eval {
    $sq->get_quote({Contain => '4'}); # only avail for 'get_quotes_contain'
};
if ($@) {
    pass if $@ =~ m/unsupported argument option passed/;
} else {fail 'should alert user on non existant params' };


# any unique part of name should work
# i.e these should all return the same results
is scalar @{$sq->get_quotes({AttrName => 'comic book guy'})}, 8;
is scalar @{$sq->get_quotes({AttrName => 'comic book'})}, 8;
is scalar @{$sq->get_quotes({AttrName => 'comic'})}, 8;
is scalar @{$sq->get_quotes({AttrName => 'book'})}, 8;
is scalar @{$sq->get_quotes({AttrName => 'book guy'})}, 8;
is scalar @{$sq->get_quotes({AttrName => 'guy'})}, 8;

eval {
    $sq->get_quotes({AttrName => 'your momma'});
};
if ($@) {
    pass 'ok' if $@ =~ m/attribution not found/;
    pass 'ok'; #'dont talk about my momma on the simpsons';
} else {fail 'attribution should not be found' };

eval {
    $sq->get_quotes({AttrName => 'chewbaccas momma'});
};
if ($@) {
    pass 'ok' if $@ =~ m/attribution not found/;
    pass 'ok'; #'now your really asking for trouble'; 
} else {fail 'attribution should not be found' };


eval { # param mispelled
    $sq->get_quotes({Charcter => 'bart'});
};
if ($@) {
    pass if $@ =~ m/unsupported argument option passed/;
} else {fail 'should alert user on non existant params' };


#sqlite> select COUNT(*) from quote where attribution_id IN (29,5);
#61 # get all family name wiggum quotes (ralph and clancy)
is scalar @{$sq->get_quotes({AttrName => 'wiggum', Rating => '2-10'})}, 15;

# get 6 random quotes
is scalar @{$sq->get_quotes({Limit => 6})}, 6;

is scalar @{$sq->get_quotes({
                           Limit  => 2,
                           Rating => '9-10'
                           })}, 2; # get 2 very funny random quotes

is scalar @{$sq->get_quotes({
                           AttrName => '  wiggum   ',
                           Rating    => ' 4 - 7 ', # opps, 'spacing' out,...
                           Limit     => 2
                           })}, 2; # get 2 not so funny wiggum quotes

ok $sq->get_quotes({
                     AttrName => 'comic',
                     Rating => '7'
                   })->[0] =~ m/Highlander/;

ok $sq->get_quotes({
                     AttrName => 'comic',
                     Rating => '7.0'
                   })->[0] =~ m/Highlander/;


my $gs = "Big deal! When I was a pup, we got spanked by presidents " .
         "'til the cows came home! Grover Cleveland spanked me on " .
         "two non-consecutive occasions!\n-- Grandpa Simpson";
is $sq->get_quotes_contain({
                  Contain =>  'til the cow'
})->[0], $gs;


is $sq->get_quotes_contain({
                  Contain =>  'til the cow',
                  Rating  => '1-5',
                  Limit   => 2
})->[0], undef;


eval {
    $sq->get_quotes_contain({
                      Contain =>  'til the cow',
                      Rating  => '-7',
                      Limit   => 2
    })};
if ($@) {
    pass if $@ =~ m/negative range not permitted/;
} else {fail 'should alert user on incorrect rating input' };


is $sq->get_quotes_contain({
                  Contain   => 'wookie',
                  AttrName => 'ralph',
                  Limit     => 1
})->[0], qq{I bent my wookie.\n-- Ralph Wiggum};



t/03-load_quotes_env.t  view on Meta::CPAN

#!perl -T

use strict;
use warnings;

use Carp qw/croak/;
use File::Temp;

BEGIN {
    eval "use DBD::SQLite";
    $@ and croak 'DBD::SQLite is a required dependancy';

    # give alternate path to the DB
    $ENV{ACME_QUOTEDB_PATH} = 
          File::Temp->new( UNLINK => 0,
                           EXLOCK => 0,
                           SUFFIX => '.dat',
                     );
}

use Test::More tests => 9;
use File::Basename qw/dirname/;
use File::Spec;
use DBI;
use ACME::QuoteDB;
use ACME::QuoteDB::LoadDB;

{ # prove it's not using the provided db path
  my $def_db = File::Spec->catfile( (dirname(__FILE__), '..', 'lib', 'ACME',
                            'QuoteDB', 'DB'), 'quotes.db'
               );
  if ( -e $def_db ) { 
    ok unlink $def_db;
  } 
  else {
    ok 'already gone';
  }
}

ok -z $ENV{ACME_QUOTEDB_PATH};
 
{
  my $q = File::Spec->catfile((dirname(__FILE__),'data'), 
      'simpsons_quotes.tsv.csv'
  );

  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file        => $q,
                              file_format => 'tsv',
                              create_db   => 1,
                              delimiter   => "\t",
                              attribution => 'The Simpsons',
                              category    => 'Humor',
                              rating      => 6,
                              #verbose     =>1
                          });
  
  isa_ok $load_db, 'ACME::QuoteDB::LoadDB';
  $load_db->data_to_db;
  ok $load_db->success;
  is $load_db->success, 1;

   
  my $sq = ACME::QuoteDB->new;
  isa_ok $sq, 'ACME::QuoteDB';
  
  # expected attribution list from our data
  my @expected_attribution_list = (
           'Apu Nahasapemapetilon',
           'Chief Wiggum',
           'Comic Book Guy',
           'Grandpa Simpson',
           'Ralph Wiggum',
          );
  
  is( $sq->list_attr_names, join "\n", sort @expected_attribution_list);

  $load_db = undef;
}
ok ! -z $ENV{ACME_QUOTEDB_PATH};

my $dbh = DBI->connect('dbi:SQLite:dbname='.$ENV{ACME_QUOTEDB_PATH},'','');
my $count = $dbh->selectrow_hashref('SELECT COUNT(*) AS COUNT FROM quote');
is $count->{COUNT}, 29 ;




t/04-get_quotes_more.t  view on Meta::CPAN

#!perl -T

# TODO more tests, make add_quote break!
# TODO see bottom of file for more todo's

use strict;
use warnings;

use ACME::QuoteDB;
use ACME::QuoteDB::LoadDB;

#use Test::More 'no_plan';
use Test::More tests => 24;
use File::Basename qw/dirname/;
use Data::Dumper qw/Dumper/;
use Carp qw/croak/;
use File::Spec;
use Readonly;

BEGIN {
    eval "use DBD::SQLite";
    $@ and croak 'DBD::SQLite is a required dependancy';
}


Readonly my $FG_QUOTE => 'Lois: Peter, what did you promise me?' .
"\nPeter: That I wouldn't drink at the stag party." .
"\nLois: And what did you do?" .
"\nPeter: Drank at the stag pa-- ... Whoa. I almost walked into that one.";
  

{
    #make test db writeable
    use ACME::QuoteDB::DB::DBI;
    # yeah, this is supposed to be covered by the build process
    # but is failing sometimes,...
    chmod 0666, ACME::QuoteDB::DB::DBI->get_current_db_path;

    my $q = File::Spec->catfile((dirname(__FILE__),'data'), 
        'simpsons_quotes.csv'
    );
    my $load_db = ACME::QuoteDB::LoadDB->new({
                                file        => $q,
                                file_format => 'csv',
                                create_db   => 1,
                            });

    isa_ok $load_db, 'ACME::QuoteDB::LoadDB';
    $load_db->data_to_db;
    is $load_db->success, 1;
}

my $sq = ACME::QuoteDB->new;

is $sq->get_quote({Rating => '8.7'}),
    "Me fail English? That's unpossible.\n-- Ralph Wiggum";

is( $sq->list_attr_sources, 'The Simpsons');
is( $sq->list_categories, 'Humor');


{
  eval { # quote is mandatory
     $sq->add_quote({
         Quote     => q{},
         AttrName  => 'Peter Griffin',
         Source    => 'Family Guy',
         Rating    => '8.6',
         Category  => 'TV Humor',
     });
  };
  if ($@) {
      if ($@ =~ m/ are mandatory parameters/){ 
         pass 'correct, exception expected'
      }
      else {fail 'unexpected exception occured'};
  } 
  else {fail 'quote and name are required'};
}


# quote does not yet exist in db
{ 
  eval { # see, not exist yet
      $sq->get_quote({AttrName => 'Griffin'});
  };
  if ($@) {
     pass 'ok' if $@ =~ m/attribution not found/;
  } else {
     fail 'attribution does not yet exist, so should not be found'
  };
}

{ # now, add new quote to the db

  $sq->add_quote({
      Quote     => $FG_QUOTE,
      AttrName  => 'Peter Griffin',
      Source    => 'Family Guy',
      Rating    => '8.6',
      Category  => 'TV Humor',
  });

  # exist now
  ok scalar $sq->get_quote({AttrName => 'GRIFFIN'}); # case insensitve
  my $fgc = $FG_QUOTE;
  $fgc .= "\n-- Peter Griffin";
  is $fgc, $sq->get_quote({AttrName => 'Peter G'});

  # get newly updated source and category
  is( $sq->list_attr_sources, "Family Guy\nThe Simpsons" );
  is( $sq->list_categories, "Humor\nTV Humor");
}

{
  # crud
  # get_quote id, update quote content, delete quote

  my $qid = $sq->get_quote_id({Quote => $FG_QUOTE});

  my $qu = $FG_QUOTE;
  $qu =~ s/Lois/Marge/xmsg;
  $qu =~ s/Peter/Homer/xmsg;
 
  is $sq->get_quote({Rating => '9.6'}), undef;

  $sq->update_quote({
      QuoteId   => $qid,
      Quote     => $qu,
      AttrName  => 'Lois Simpson',
      Source    => 'The Simpsons Guys',
      Rating    => '9.6',
      Category  => 'Cartoon Noir',
  });

  $qu .= "\n-- Lois Simpson";
  eval { # see, updated, should now be 'Lois Simpson'
     $sq->get_quote({AttrName => 'Peter G'});
  };
  if ($@) {
      pass 'ok' if $@ =~ m/attribution not found/;
  } else {fail 'attribution does not yet exist, so should not be found'};

  is $sq->get_quote({AttrName => 'Lois Simpson'}), $qu;
  is $sq->get_quote({AttrName => 'Lois S'}), $qu;
  is $sq->get_quote({Rating => '9.6'}), $qu;

  is $sq->get_quote({Source => 'The Simpsons Guys'}), $qu;
  is $sq->get_quote({Category => 'Cartoon Noir'}), $qu;

  $sq->delete_quote({QuoteId => $qid});
  # see, bye, bye
  is $sq->get_quote({AttrName => 'Lois S'}), undef;
  is $sq->get_quote({Rating => '9.6'}), undef;

}

# TODO
{ # add new quote to the db

  $sq->add_quote({
      Quote     => $FG_QUOTE,
      AttrName  => 'Peter Griffin',
      Source    => 'Family Guy',
      Rating    => '8.6',
      Category => [qw(Humor TV PG13 Crude Cartoon ROTFLMAO)]
  });

  my $qid = $sq->get_quote_id({Quote => $FG_QUOTE});

  # one quote can belong to many categories
  my $q = $FG_QUOTE;
  $q .= "\n-- Peter Griffin";
  is $sq->get_quote({
           Source => 'Family Guy',
           Category => [qw(Humor TV PG13 Crude Cartoon ROTFLMAO)]
  }), $q;

  # get all quotes from these categories
  is $sq->get_quote({
           Category => [qw(Crude Cartoon ROTFLMAO)]
  }), $q;

  is scalar @{$sq->get_quotes({
           Category => [qw(Humor ROTFLMAO)]
  })}, 30;

  ok $sq->delete_quote({QuoteId => $qid}); 
}

t/04-load_get_quote_utf8.t  view on Meta::CPAN

#!perl -T
# /* vim:et: set ts=4 sw=4 sts=4 tw=78 encoding=utf-8: */

use 5.008005;        # require perl 5.8.5
                     # DBD::SQLite Unicode is not supported before 5.8.5
use strict;
use warnings;
use utf8; # yes this source code does contain utf8 characters

use ACME::QuoteDB;
use ACME::QuoteDB::LoadDB;

#use Test::More 'no_plan';
use Test::More tests => 8;
use File::Basename qw/dirname/;
use Data::Dumper qw/Dumper/;
use Carp qw/croak/;
use File::Temp;
use File::Spec;


BEGIN {
    eval "use DBD::SQLite";
    $@ and croak 'DBD::SQLite is a required dependancy';

    # give alternate path to the DB
    $ENV{ACME_QUOTEDB_PATH} = 
          File::Temp->new( UNLINK => 0,
                           EXLOCK => 0,
                           SUFFIX => '.dat',
                     );
}

# matches the data in our utf8.csv file, soon to be in our quote db
my $utf8_quotes = [
    '¥ · £ · € · $ · ¢ · ₡ · ₢ · ₣ · ₤ · ₥ · ₦ · ₧ · ₨ · ₩ · ₪ · ₫ · ₭ · ₮ · ₯',
    '我能吞下玻璃而不伤身体。',
    '私はガラスを食べられます。それは私を傷つけません。',
    '나는 유리를 먹을 수 있어요. 그래도 아프지 않아요',
    'Tsésǫʼ yishą́ągo bííníshghah dóó doo shił neezgai da. ',
    'Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα.',
    'मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती.',
    'אני יכול לאכול זכוכית וזה לא מזיק לי',
];# any takers for specifying each multibyte code sequence for the above,.. ;)

{
    #make test db writeable
    use ACME::QuoteDB::DB::DBI;
    # yeah, this is supposed to be covered by the build process
    # but is failing sometimes,...
    chmod 0666, ACME::QuoteDB::DB::DBI->get_current_db_path;

    my $q = File::Spec->catfile((dirname(__FILE__),'data'), 
        'utf8.csv'
    );
    my $load_db = ACME::QuoteDB::LoadDB->new({
                                file        => $q,
                                file_format => 'csv',
                                delimiter   => "\t",
                                create_db   => 1
                            });

    isa_ok $load_db, 'ACME::QuoteDB::LoadDB';
    $load_db->data_to_db;
    is $load_db->success, 1;
}

my $sq = ACME::QuoteDB->new;

# matches the data in our utf8.csv file, attribution's to the 'quotes' above
my @expected_attribution_list = (
    'UTF-8 Sampler Currency',
    'I can eat grass (Chinese)',
    'I can eat grass (Japanese)',
    'I can eat grass (Korean)',
    'I can eat grass (Navajo)',
    'I can eat grass (Greek)',
    'I can eat grass (Hindi)',
    'I can eat grass (Hebrew)',
);
is( $sq->list_attr_names, join "\n", sort @expected_attribution_list);

ok $sq->get_quote; # default get random quote
ok $sq->get_quote =~ m{\w+};

is $sq->get_quote({AttrName => $expected_attribution_list[1]}),
      $utf8_quotes->[1] . "\n-- " . $expected_attribution_list[1];

is $sq->get_quote({AttrName => $expected_attribution_list[6]}),
      $utf8_quotes->[6] . "\n-- " . $expected_attribution_list[6];

is @{ $sq->get_quotes({ Rating => '10' })}, @{$utf8_quotes};

t/05-load_quotes_remote.t  view on Meta::CPAN

#!perl -T

use strict;
use warnings;

#BEGIN {
#       eval "use DBD::SQLite";
#       plan $@ ? (skip_all => 'needs DBD::SQLite for testing') : (tests => 6);
#}

use Carp qw/croak/; 
use Test::More;
#use Test::More tests => 9;
#use Test::More qw/no_plan/;
use File::Basename qw/dirname/;
use DBI;
use File::Temp;
use File::Spec;

BEGIN {
    eval "use DBI";
    $@ and plan skip_all => 'DBI/mysql is required for this test';

    # have to set this to use remote database
    $ENV{ACME_QUOTEDB_REMOTE} =  'mysql';
    $ENV{ACME_QUOTEDB_DB}     =  'acme_quotedb';
    $ENV{ACME_QUOTEDB_HOST}   =  'localhost';
    $ENV{ACME_QUOTEDB_USER}   =  'acme_user';
    $ENV{ACME_QUOTEDB_PASS}   =  'acme';
}
my $database = $ENV{ACME_QUOTEDB_DB};
my $host     = $ENV{ACME_QUOTEDB_HOST};
my $user     = $ENV{ACME_QUOTEDB_USER};
my $pass     = $ENV{ACME_QUOTEDB_PASS};

# XXX these use's must happen after the BEGIN,...
use ACME::QuoteDB::LoadDB;
use ACME::QuoteDB;

eval {
  my $q = File::Spec->catfile((dirname(__FILE__),'data'), 
      'simpsons_quotes.csv'
  );

  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file        => $q,
                              file_format => 'csv',
                              create_db   => 1,
                          });
};
$@ and plan skip_all => 'mysql not installed or not configured for test user';

# ok, still here? let's run some tests 
plan tests => 7;

{ # create it
  my $q = File::Spec->catfile((dirname(__FILE__),'data'), 
      'simpsons_quotes.csv'
  );

  my $load_db = ACME::QuoteDB::LoadDB->new({
                              file        => $q,
                              file_format => 'csv',
                              create_db   => 1,
                          });
  
  isa_ok $load_db, 'ACME::QuoteDB::LoadDB';
  $load_db->data_to_db;
  ok $load_db->success;
  is $load_db->success, 1;
   
  my $sq = ACME::QuoteDB->new;
  isa_ok $sq, 'ACME::QuoteDB';
  
  # expected attribution list from our data
  my @expected_attribution_list = (
           'Apu Nahasapemapetilon',
           'Chief Wiggum',
           'Comic Book Guy',
           'Grandpa Simpson',
           'Ralph Wiggum',
          );
  
  is( $sq->list_attr_names, join "\n", sort @expected_attribution_list);

  $load_db = undef;
}

my $dbh = DBI->connect("DBI:mysql:database=$database;host=$host",$user,$pass)
          || croak "can not connect to: $database $!";
my $count = $dbh->selectrow_hashref('SELECT COUNT(*) AS COUNT FROM quote');
is $count->{COUNT}, 29 ;

my $qc = $dbh->selectrow_hashref('SELECT COUNT(*) AS COUNT FROM quote_catg');
is $qc->{COUNT}, 29 ;




t/boilerplate.t  view on Meta::CPAN

#!perl -T

use strict;
use warnings;
use Test::More tests => 3;
use File::Basename qw/dirname/;
use File::Spec;

sub not_in_file_ok {
    my ($filename, %regex) = @_;
    my $file = File::Spec->catfile((dirname(__FILE__), '..'), $filename);
    open( my $fh, '<', $file )
        or die "couldn't open $file for reading: $!";

    my %violated;

    while (my $line = <$fh>) {
        while (my ($desc, $regex) = each %regex) {
            if ($line =~ $regex) {
                push @{$violated{$desc}||=[]}, $.;
            }
        }
    }

    if (%violated) {
        fail("$file contains boilerplate text");
        diag "$_ appears on lines @{$violated{$_}}" for keys %violated;
    } else {
        pass("$file contains no boilerplate text");
    }
}

sub module_boilerplate_ok {
    my ($module) = @_;
    not_in_file_ok($module =>
        'the great new $MODULENAME'   => qr/ - The great new /,
        'boilerplate description'     => qr/Quick summary of what the module/,
        'stub function definition'    => qr/function[12]/,
    );
}

not_in_file_ok(README =>
  "The README is used..."       => qr/The README is used/,
  "'version information here'"  => qr/to provide version information/,
);

not_in_file_ok(Changes =>
  "placeholder date/time"       => qr(Date/time)
);

module_boilerplate_ok('lib/ACME/QuoteDB.pm');


t/data/futurama  view on Meta::CPAN

 Leela: Hey, you know what might be a hoot?
 Professor: No. Why would I know that? 
%
 Fry: I want to see the edge of the universe.
 Amy: Ooh, that sounds cool. 
 Zoidberg: It's funny. You live in the universe by you 
  never do these things 'til someone comes to visit.
%
 Fry: So, there's an infinite number of parallel universes?
 Professor: No, just the two.
 Fry: Oh, well, I'm sure that's enough.
%
 Handcrafters: New hands in about an hour
 Fry: These new hands are great. I'm gonna break them in tonight.
%
 Fry: I've only got two fantasies left: to be invisible in a 
chocolate factory, and to be romantically linked to a  celebrity.
 Bender: I could pound your head 'til you think that's what happened.
 Fry: Okay.
%
 nappster.com: Download any celebrity from A.A. Milne to Z.Z. Top
%
 If food is not reasonably clean, return uneaten portion for partial refund
%
 Brooklyn Aquarium, special exhibit: boids of da wattah
%
 Professor: Oh, dear. She's stuck in an infinite loop and he's an idiot. 
   Well, that's love for you.
%
 Bender: Stay away from our women. You got metal fever, baby, metal fever! 
%
 Professor: I knew I should have shown him "Electro-Gonnorhea, the Noisy Killer."
%
 Lucy Liu: That was incredible, Bender. You're like Jackie Chan 
   before he got all doughy.
%
 Zapp: Now that's a wave of destruction that's easy on the eyes. 
%
 Leela: And nappster says illegal copies never hurt anybody. 
%
 Fry: Lucy Liu-bot, if I don't survive the corn, I want you to know that I 
 love you as much as a man can love a computerized image of a gorgeous 
 celebrity, which it turns out is a lot.

t/data/python_quotes.txt  view on Meta::CPAN

We will perhaps eventually be writing only small modules which are identified
by name as they are used to build larger ones, so that devices like
indentation, rather than delimiters, might become feasible for expressing local
structure in the source language.
      -- Donald E. Knuth, "Structured Programming with goto Statements",
         Computing Surveys, Vol 6 No 4, Dec. 1974

Python's syntax succeeds in combining the mistakes of Lisp and Fortran. I do
not construe that as progress.
      -- Larry Wall, May 12 2004

    Some rejected alternate names for "Monty Python's Flying Circus":
    1 2 3 / It's Them! / Arthur Megapode's Flying Circus / The Horrible Earnest
Megapode / The Panic Show / The Plastic Mac Show / Ow! It's Colin Plint! /
Vaseline Review / Vaseline Parade / The Keen Show / Brian's Flying Circus / The
Year of the Stoat / Cynthia Fellatio's Flying Circus / Owl Stretching Time /
The Whizzo Easishow! (Guaranteed to last 1/2 hour! Money back if not!)
      -- From Kim "Howard" Johnson's _Life Before and After Monty Python_.
         It's interesting to contemplate what Python would have been called
         if one of these names had been chosen.

Anybody else on the list got an opinion? Should I change the language or not?
      -- Guido van Rossum, 28 Dec 1991

in-any-case-the-best-christmas-present-i-got-today!-ly y'rs - tim
      -- Tim Peters, 29 Dec 1991 [First occurrence of Tim Peters's long-
         phrase-ly idiom.]

but-i'm-not-even-motivated-enough-to-finish-this-sig-
      -- Tim Peters, 20 Dec 2000

Ha -- you have done me the favor of underestimating my ignorance <smile>.
      -- Tim Peters, 30 Dec 1991

I prefer (all things being equal) regularity/orthogonality and logical
syntax/semantics in a language because there is less to have to remember. (Of
course I *know* all things are NEVER really equal!)
      -- Guido van Rossum, 6 Dec 1991

The details of that silly code are irrelevant.
      -- Tim Peters, 4 Mar 1992

Frankly, I'd rather not try to compete with Perl in the areas where Perl is
best -- it's a battle that's impossible to win, and I don't think it is a good
idea to strive for the number of obscure options and shortcuts that Perl has
acquired through the years.
      -- Guido van Rossum, 7 Jul 1992

Python is a truly wonderful language. When somebody comes up with a good idea
it takes about 1 minute and five lines to program something that almost does
what you want. Then it takes only an hour to extend the script to 300 lines,
after which it still does almost what you want.
      -- Jack Jansen, 8 Jul 1992

If you have a browser from CERN's WWW project (World-Wide Web, a distributed
hypertext system) you can browse a WWW hypertext version of the manual...
      -- Guido van Rossum, 19 Nov 1992 [First mention of the Web on python-
         list.]

Just a success note for Guido and the list: Python 0.9.9, stdwin, readline,
gmp, and md5 all go up on linux 0.99 pl11 without much problems.
      -- Allan Bailey, 2 Aug 1993 [First mention of Linux on python-list.]

Rule: "You shouldn't have to open up a black box and take it apart to find out
you've been pushing the wrong buttons!" Corollary: "Every black box should have
at least TWO blinking lights: "Paper Jam" and "Service Required" (or
equivalent)."
      -- Steven D. Majewski, 9 Sep 1993

We've been through a couple of syntax changes, but I have sort of assumed that
by the time we get to version 1.0 release, the language, (if not the
implementation) will essentially be stable.
      -- Steven D. Majewski, 14 Sep 1993

"Python tricks" is a tough one, cuz the language is so clean. E.g., C makes an
art of confusing pointers with arrays and strings, which leads to lotsa neat
pointer tricks; APL mistakes everything for an array, leading to neat
one-liners; and Perl confuses everything period, making each line a joyous
adventure <wink>.
      -- Tim Peters, 16 Sep 1993

I've seen Python criticized as "ugly" precisely because it *doesn't* have a
trick-based view of the world. In many ways, it's a dull language, borrowing
solid old concepts from many other languages & styles: boring syntax,
unsurprising semantics, few automatic coercions, etc etc. But that's one of the
things I like about it.
      -- Tim Peters, 16 Sep 1993

One of the things that makes it interesting, is exactly how much Guido has
managed to exploit that *one* implementation trick of 'namespaces'.
      -- Steven D. Majewski, 17 Sep 1993

Anyone familiar with Modula-3 should appreciate the difference between a
layered approach, with generic Rd/Wr types, and the Python 'C with foam
padding' approach.
      -- John Redford, 24 Nov 1993

People simply will not agree on what should and shouldn't be "an error", and
once exception-handling mechanisms are introduced to give people a choice, they
will far less agree on what to do with them.
      -- Tim Peters, 17 Dec 1993

Note that because of its semantics, 'del' *can't* be a function: "del a"
deletes 'a' from the current namespace. A function can't delete something from
the calling namespace (except when written by Steve Majewski :-).
      -- Guido van Rossum, 1 Aug 1994

    I don't know a lot about this artificial life stuff -- but I'm suspicious
of anything Newsweek gets goofy about -- and I suspect its primary use is as
another money extraction tool to be applied by ai labs to the department of
defense (and more power to 'em).
    Nevertheless in wondering why free software is so good these days it
occurred to me that the propagation of free software is one gigantic artificial
life evolution experiment, but the metaphor isn't perfect.
    Programs are thrown out into the harsh environment, and the bad ones die.
The good ones adapt rapidly and become very robust in short order.
    The only problem with the metaphor is that the process isn't random at all.
Python *chooses* to include Tk's genes; Linux decides to make itself more
suitable for symbiosis with X, etcetera.
    Free software is artificial life, but better.
      -- Aaron Watters, 29 Sep 1994

I claim complete innocence and ignorance! It must have been Tim. I wouldn't
know a Trondheim Hammer if it fell on my foot!
      -- Steve Majewski, 10 Jan 1995

(Aieee! Yet another thing on my TODO pile!)
      -- A.M. Kuchling, 10 Jan 1995

[After someone wrote "...assignment capability, a la djikstra"] Ehh, the poor
old man's name is Dijkstra. I should know, "ij" is a well known digraph in the
Dutch language. And before someone asks the obvious: his famous "P and V" names
for semaphores are derived for the Dutch words "Passeer" and "Verlaat", or
"Pass" and "Leave". And no, I haven't met him (although he did work at CWI back
in the fifties when it was called, as it should still be today, Mathematical
Centre). he currently lives in Austin, Texas I believe. (While we're at it...
does anybody remember the Dijkstra font for Macintoshes? It was a scanned
version of his handwriting. I believe Luca Cardelli scanned it -- the author of
Obliq, a somewhat Python-like distributed language built on Modula-3. I could
go on forever... :-)
      -- Guido van Rossum, 19 Jan 1995

As always, I'll leave it to a volunteer to experiment with this.
      -- Guido van Rossum, 20 Jan 1995

Non-masochists, please delete this article NOW.
      -- Aaron Watters, 20 Jan 1995

If Perl weren't around, I'd probably be using Python right now.
      -- Tom Christiansen in comp.lang.perl, 2 Jun 1995

GUI stuff is *supposed* to be hard. It builds character.
      -- Jim Ahlstrom, at one of the early Python workshops

    >VERY cool mod, Peter. I'll be curious to see GvR's reaction to your
syntax.
    Hm.
      -- Nick Seidenman and Guido van Rossum, 1 Aug 1996

Python is an experiment in how much freedom programmers need. Too much freedom
and nobody can read another's code; too little and expressiveness is
endangered.
      -- Guido van Rossum, 13 Aug 1996

[On regression testing] Another approach is to renounce all worldly goods and
retreat to a primitive cabin in Montana, where you can live a life of purity,
unpolluted by technological change. But now and then you can send out little
packages....
      -- Aaron Watters

Ah, you're a recent victim of forceful evangelization. Write your own assert
module, use it, and come back in a few months to tell me whether it really
caught 90% of your bugs.
      -- Guido van Rossum, 7 Feb 1997

The larger scientific computing centers generally have a "theory" division and
a "actually uses the computer" <wink> division. The theory division generally
boasts some excellent theoreticians and designers, while the other division
generally boasts some excellent physical scientists who simply want to get
their work done. In most labs I've seen, the two divisions hate each others'
guts (or, rarely, blissfully ignore each other), & the politics is so thick you
float on it even after they embed your feet in cement blocks (hence even the
simple relief of death is denied you <wink>).
      -- Tim Peters, 25 Mar 1997

In one particular way the conflict is fundamental & eternal: the "working
scientists" generally understand the hardware du jour perfectly, and
passionately resent any attempt to prevent them from fiddling with it directly
-- while the theory folks are forever inventing new ways to hide the hardware
du jour. That two groups can both be so right and so wrong at the same time is
my seventh proof for the existence of God ...
      -- Tim Peters, 25 Mar 1997

You're going to be in a minority - you're coming to Python programming from a
language which offers you a lot more in the way of comfortable operations than
Python, instead of coming from medieval torture chambers like C or Fortran,
which offer so much less.
      -- Andrew Mullhaupt, 26 Jun 1997

...although Python uses an obsolete approach to memory management, it is a
*good* implementation of that approach, as opposed to S, which uses a
combination of bad implementation and demented design decisions to arrive at
what may very well be the worst memory behavior of any actually useful program.
      -- Andrew Mullhaupt, 26 Jun 1997

I suggested holding a "Python Object Oriented Programming Seminar", but the
acronym was unpopular.
      -- Joseph Strout, 28 Feb 1997

Strangely enough I saw just such a beast at the grocery store last night.
Starbucks sells Javachip. (It's ice cream, but that shouldn't be an obstacle
for the Java marketing people.)
      -- Jeremy Hylton, 29 Apr 1997

    A little girl goes into a pet show and asks for a wabbit. The shop keeper
looks down at her, smiles and says:
    "Would you like a lovely fluffy little white rabbit, or a cutesy wootesly
little brown rabbit?"
    "Actually", says the little girl, "I don't think my python would notice."
      -- Told by Nick Leaton, 4 Dec 1996

When I originally designed Perl 5's OO, I thought about a lot of this stuff,
and chose the explicit object model of Python as being the least confusing. So
far I haven't seen a good reason to change my mind on that.
      -- Larry Wall, 27 Feb 1997 on perl5-porters

PSA 1996 Budget
---------------
Income:
$1,093,276.54  'Guido for President' 
                 Campaign Contributions(1)
$        3.12  Milk Money Extortion Program
$    2,934.07  PSA Memberships
-------------
$1,096,213.73  Total Income

Expenses:
$  652,362.55  Monty Python Licencing Fees (2)
$   10,876.45  Pre-Release 2 Week Vacations (3)
$  369,841.59  Post-Release 2 Week Vacations (3)
$       15.01  Alien Abduction Insurance
$   62,541.72  Python Web Site Maintenance
$      554.65  Great Comfort Cream
-------------
$1,096,191.97  Total Expenses
$      (21.76) Total Profit (Loss)
    Notes:
    (1) Many of you many not be aware of the fabulously successful 'Guido for
President' Campaign. While Guido has no interest in being the president, the
PSA thought it would be a cool way to collect money. The centerpiece of the
campaign featured an attractive offer to spend the night in Guido's spare
bedroom in exchange for a $50,000.00 contribution. (Mark Lutz stayed TWICE!)
    (2) Since the proliferation of Monty Python related names (Python, Monty,
Grail, Eric-the-Half-a-Compiler, et al.) has increased over the past year, the
PSA felt it would be wise to licencing the Python name to forestall any
lawsuits. An added benefit is that John Cleese is teaching Guido how to walk
funny.
    (3) Pre-Release vacations are spent in the Catskills. Post-Release
vacations are spent in the Bahamas. Guido is currently working on a system
which will allow him to make more releases of Python; thus octupling the number
of vacations he takes in a year.
      -- Matthew Lewis Carroll Smith, 4 Apr 1997

I mean, just take a look at Joe Strout's brilliant little "python for
beginners" page. Replace all print-statements with sys.stdout.write(
string.join(map(str, args)) + "\n") and you surely won't get any new beginners.
And That Would Be A Very Bad Thing.
      -- Fredrik Lundh, 27 Aug 1996

Ya, ya, ya, except ... if I were built out of KSR chips, I'd be running at 25
or 50 MHz, and would be wrong about ALMOST EVERYTHING almost ALL THE TIME just
due to being a computer! Think about it -- when's the last time you spent 20
hours straight debugging your son/wife/friend/neighbor/dog/ferret/snake? And
they *still* fell over anyway? Except in a direction you've never seen before
each time you try it? The easiest way to tell you're dealing with a computer is
when the other side keeps making the same moronic misteakes over and misteakes
over and misteakes over and misteakes over and misteakes over and misteakes
CTRL-C again.
      -- Tim Peters, 30 Apr 1997

BTW, a member of the ANSI C committee once told me that the only thing rand is
used for in C code is to decide whether to pick up the axe or throw the dwarf,
and if that's true I guess "the typical libc rand" is adequate for all but the
most fanatic of gamers <wink>.
      -- Tim Peters, 21 June 1997.

Things in Python are very clear, but are harder to find than the secrets of
wizards. Things in Perl are easy to find, but look like arcane spells to invoke
magic.
      -- Mike Meyer, 6 Nov 1997

    Indeed, as Palin has come to understand, being part of Python means never
really knowing what may lurk around the corner.
    "We've never really followed any rules at all with Python," he said. "We're
a spontaneous lot. It's more fun that way."
      -- Michael Palin, quoted from a Reuters/Variety news item titled
         "Rare Python Reunion", Jan 15 1998.

Python is an excellent language for learning object orientation. (It also
happens to be my favorite OO scripting language.)
      -- Sriram Srinivasan, _Advanced Perl Programming_

The point is that newbies almost always read more into the semantics of release
than are specified, so it's worthwile to be explicit about how little is being
said <wink>.
      -- Tim Peters, 12 Feb 1998

Ah! "Never mind" to a bunch of what I said before (this editor can't move
backwards <wink>).
      -- Tim Peters, 12 Feb 1998

After 1.5 years of Python, I'm still discovering richness (and still unable to
understand what the hell Jim Fulton is talking about).
      -- Gordon McMillan, 13 Mar 1998

Tabs are good, spaces are bad and mixing the two just means that your motives
are confused and that you don't use enough functions.
      -- John J. Lehmann, 19 Mar 1998

... but whenever optimization comes up, people get sucked into debates about
exciting but elaborate schemes not a one of which ever gets implemented; better
to get an easy 2% today than dream about 100% forever.
      -- Tim Peters, 22 Mar 1998

I've been playing spoilsport in an attempt to get tabnanny.py working, but now
that there's absolutely no reason to continue with this, the amount of my life
I'm willing to devote to it is unbounded <0.9 wink>.
      -- Tim Peters, 30 Mar 1998

Python is a little weak in forcing encapsulation. It isn't made for bondage and
domination environments.
      -- Paul Prescod, 30 Mar 1998

One of my first big programming assignments as a student of computer science
was a source formatter for Pascal. The assignment was designed to show us the
real-life difficulties of group programming projects. It succeeded perhaps too
well. For a long time, I was convinced that source code formatters were a total
waste of time, and decided to write beautiful code that no automatic formatter
could improve upon. In fact, I would intentionally write code that formatters
could only make worse.
      -- Guido van Rossum, 31 Mar 1998

You need to build a system that is futureproof; it's no good just making a
modular system. You need to realize that your system is just going to be a
module in some bigger system to come, and so you have to be part of something
else, and it's a bit of a way of life.
      -- Tim Berners-Lee, at the WWW7 conference

From gotos to the evolution of life in 10 posts; that's comp.lang.python for
you!
      -- A.M. Kuchling, 4 Apr 1998

This is *Python*! If we didn't care what code looked like, most of us would
probably be hacking in some version of Lisp -- which already covered most of
Python's abstract *semantics* way back when Guido was just a wee snakelet
frolicking in the lush Amsterdam jungle.
      -- Tim Peters, 24 Apr 1998

The infinities aren't contagious except in that they often appear that way due
to their large size.
      -- Tim Peters on the IEEE 754 floating point standard, 27 Apr 1998

The "of course, while *I* have no problem with this at all, it's surely too
much for a lesser being" flavor of argument always rings hollow to me. Are you
personally confused by the meanings for "+" that exist today? *Objecting* to
the variations is a different story; I'm wondering whether you personally
stumble over them in practice. I don't; Steven doesn't; I doubt that you do
either. I'm betting that almost *nobody* ever does, in which case those "less
nimble colleagues and students" must be supernaturally feeble to merit such
concern.
      -- Tim Peters, 29 Apr 1998

    "Ideally, IMO, two messages with the same name should have the same meaning
but possibly different implementations. Of course, "meaning" is somewhat
relative, but the notion that two messages with the same name should have the
same 'meaning' is very useful."
    "Like clothes.launder() vs money.launder(), or shape.draw() vs
blood.draw(), or matrix.norm() vs hi.norm() <wink>? I'm afraid English thrives
on puns, and the same word routinely means radically different things across
application areas. Therefore, to insist that a word have "one true meaning" in
a programming language is insisting that the language cater to one true
application domain."
      -- Jim Fulton and Tim Peters, in a discussion of rich comparisons, 29
         Apr 1998

Indeed, when I design *my* killer language, the identifiers "foo" and "bar"
will be reserved words, never used, and not even mentioned in the reference
manual. Any program using one will simply dump core without comment. Multitudes
will rejoice.
      -- Tim Peters, 29 Apr 1998

Too little freedom makes life confusingly clumsy; too much, clumsily confusing.
Luckily, the tension between freedom and restraint eventually gets severed by
Guido's Razor.
      -- Tim Peters, 29 Apr 1998

In other words, I'm willing to see dark corners added to the language, as long
as I don't have to go into them myself.
      -- A.M. Kuchling, 29 Apr 1998

This argument is specious. What on earth would it mean to compare an object you
created with another object from someone else's code unless you knew exactly
what each object's semantics were? Do you really want to ask if my abstract
syntax tree is less then your HTTP connection object?
      -- Jeremy Hylton, in a discussion of rich comparisons, 29 Apr 1998

Two things I learned for sure during a particularly intense acid trip in my own
lost youth: (1) everything is a trivial special case of something else; and,
(2) death is a bunch of blue spheres.
      -- Tim Peters, 1 May 1998

Well, they will be: "<" will mean what everyone thinks it means when applied to
builtin types, and will mean whatever __lt__ makes it mean otherwise, except
when __lt__ isn't defined but __cmp__ is in which case it will mean whatever
__cmp__ makes it mean, except when neither __lt__ or __cmp__ are defined in
which case it's still unsettled. I think. Or isn't that what you meant by
"clearly defined"?
      -- Tim Peters, 6 May 1998

You write a great program, regardless of language, by redoing it over & over &
over & over, until your fingers bleed and your soul is drained. But if you tell
newbies *that*, they might decide to go off and do something sensible, like
bomb defusing<wink>.
      -- Tim Peters, 5 Jun 1998

OO styles help in part because they make it easier to redo large parts over,
or, when the moon is shining just right, to steal large parts from someone
else. Python helps in many additional ways regardless of style, not least of
which in that it hurts less to throw away 50 lines of code than 5,000 <0.5
wink>. The pains, and joys, of programming are *qualitatively* the same under
Python. There's less pain less often, and joy comes quicker. And that's worth a
whole lot.
      -- Tim Peters, 5 Jun 1998

I've had a DBA tell me that what I wanted to do "could not" be done because his
silly $5000 tool couldn't model it. Proving him wrong simply increased his
conviction that what I was doing was immoral and perverse. Which, come to think
of it, it probably was. Hee hee.
      -- Gordon McMillan, 8 Jun 1998

The majority of programmers aren't really looking for flexibility. Most
languages that enjoy huge success seem to do so not because they're flexible,
but because they do one particular thing *extremely* well. Like Fortran for
fast number-crunching in its day, or Perl for regexps, or C++ for compatibility
with C, or C for ... well, C's the exception that proves the rule.
      -- Tim Peters, 11 Jun 1998

It has also been referred to as the "Don Beaudry *hack*," but that's a
misnomer. There's nothing hackish about it -- in fact, it is rather elegant and
deep, even though there's something dark to it.
      -- Guido van Rossum, _Metaclass Programming in Python 1.5_

Just point your web browser at http://www.python.org/search/ and look for
"program", "doesn't", "work", or "my". Whenever you find someone else whose
program didn't work, don't do what they did. Repeat as needed.
      -- Tim Peters, on python-help, 16 Jun 1998

Now some people see unchecked raw power and flee from perceived danger, while
others rush toward perceived opportunity. That's up to them. But I think it's
enormously *clarifying* in either case to see just *how* raw this particular
gimmick can get.
      -- Tim Peters, 16 Jun 1998

Every language has its partisans, usually among folks deeply immersed in their
particular theology, triumphant in having divined the inner meaning of some
esoteric operations, like a medieval Jesuit hot on the trail of the final
ontological proof, whose conciseness in solving a single problem makes them
almost swoon with ecstacy at the expected savings of many keystrokes, as if
those very keystrokes represented a lot of heavy lifting and hauling on their
part.
      -- John Holmgren, 18 Jun 1998

    > In general, the situation sucks.
    mind-if-i-use-that-as-my-epitaph<wink>?-ly y'rs - tim
      -- Timothy J. Grant and Tim Peters, 22 Jun 1998

    > Just for the record, on AIX, the following C program:
    Oh no you don't! I followed AIX threads for the first year it came out, but
eventually decided there was no future in investing time in baffling
discussions that usually ended with "oh, never mind -- turns out it's a bug"
<0.9 wink>.
      -- Vladimir Marangozov and Tim Peters, 23 Jun 1998

Python - why settle for snake oil when you can have the *whole* snake?
      -- Mark Jackson, 26 Jun 1998

The problem I have with "SETL sets" in Python is the same I have with every
other language's "killer core" in Python: SETL is much more than just "a set
type", Eiffel is much more than just fancy pre- and post- conditions, Perl's
approach to regexps is much more than just its isolated regexp syntax, Scheme
is much more than just first-class functions & lexical closures, and so on.
Good languages aren't random collections of interchangeable features: they have
a philosophy and internal coherence that's never profitably confused with their
surface features.
      -- Tim Peters, 10 Jul 1998

    "Since I'm so close to the pickle module, I just look at the pickles
directly, as I'm pretty good at reading pickles."
    "As you all can imagine, this trick goes over really well at parties."
      -- Jim Fulton and Paul Everitt on the Bobo list, 17 Jul 1998

My theory is that the churning of old threads and reminiscences (Continuations,
Icon influences, old-T-shirts, the pre news-group mailing list archive,
whitespace, closures, .... ) has brought some old messages to the surface, via
some mechanism similar to the way plankton and other nutrients are cycled in
the ocean.
      -- Steven D. Majewski, 23 Jul 1998

In general, Our Guido flees from schemes that merely change *which* foot gets
blown off <0.45 caliber wink>. Schemes that remove the firing pin entirely have
a much better, um, shot <wink>.
      -- Tim Peters, 25 Jul 1998

I don't know what "invert the control structure" means -- but if it's anything
like turning a hamster inside-out, I would *expect* it to be messy <wink>.
      -- Tim Peters, 25 Jul 1998

This makes it possible to pass complex object hierarchies to a C coder who
thinks computer science has made no worthwhile advancements since the invention
of the pointer.
      -- Gordon McMillan, 30 Jul 1998

The nice thing about list comprehensions is that their most useful forms could
be implemented directly as light sugar for ordinary Python loops, leaving
lambdas out of it entirely. You end up with a subtly different beast, but so
far it appears to be a beast that's compatible with cuddly pythons.
      -- Tim Peters, 6 Aug 1998

I wonder what Guido thinks he might do in Python2 (assuming, of course, that he
doesn't hire a bus to run over him before then <wink>).
      -- Tim Peters, 26 Aug 1998

After writing CGI scripts the traditional way for a few years, it is taking
awhile to reshape my thinking. No sledgehammer to the head yet, but lots of
small sculpting hammers...
      -- John Eikenberry on the Bobo list, 27 Aug 1998

I believe sometimes numbers creep into my programs as strings, so '4'/2 needs
to also be 2. Other languages do this. Since this is due in part to user input,
I guess 'four'/2, 'quattro/2', 'iv/2' etc. need to be 2 as well; don't know any
other language that does so, but Python could take the lead here in software
reliability. Any white space should be ignored, including between my ears. I
don't have time to write any useful software, so I've decided to devote myself
to proposing various changes to the Python interpreter.
      -- Donn Cave uses sarcasm with devastating effect, 28 Aug 1998

then-again-if-history-were-important-god-wouldn't-have-hid- it-in-the- past-ly
y'rs
      -- Tim Peters, 28 Aug 1998

> >( float ( / 1 3 ))
> 0.33333333333333331
 Now *that* one is impressive: it's the best possible 17-digit decimal
representation of the best possible 53-bit fp binary representation of 1/3, and
17 is the minimum number of decimal digits you need in general so that a 53-bit
binary fp value can be exactly reconstructed by a best-possible atof.
      -- Tim Peters, 2 Sep 1998

This is not a technical issue so much as a human issue; we are limited and so
is our time. (Is this a bug or a feature of time? Careful; trick question!)
      -- Fred Drake on the Documentation SIG, 9 Sep 1998

There are also some surprises [in the late Miocene Australia] some small
mammals totally unknown and not obviously related to any known marsupial
(appropriately awarded names such as _Thingodonta_ and _Weirdodonta_) and a
giant python immortalized as _Montypythonoides_.
      -- _The Book of Life_, found by Aaron Watters

    Can the denizens of this group enlighten me about what the advantages of
Python are, versus Perl ?
    "python" is more likely to pass unharmed through your spelling checker than
"perl".
      -- An unknown poster and Fredrik Lundh, 11 Sep 1998

I have to say that the Dragon book is good when you consider the alternatives,
but compared with the Platonic ideal it leaves much to be desired. In
particular the algorithm descriptions are described at such a low level it's
difficult to understand how they work -- and at a higher conceptual level
involving graph theoretical transforms of automata (which I got thanks to Jean
Gallier by word of mouth and effort of chalk) is nearly invisible for the
trees.
      -- Aaron Watters, 17 Sep 1998

... and at a higher conceptual level involving graph theoretical transforms of
automata (which I got thanks to Jean Gallier by word of mouth and effort of
chalk) ...
      -- Aaron Watters, 17 Sep 1998

Every clarity vanished? :-)
      -- Christian Tismer after answering a poster's question, 17 Sep 1998

    Take the "public" modifier off Joseph's interface, or leave it there but
nest the interface inside class "closure", or even move the interface to its
own printer.java file, and it compiles and runs without incident. Most of the
big boys I hang with aren't paralyzed by self-explanatory compiler msgs <wink>.
    not-to-mention-the-girls-ly y'rs
      -- Tim Peters, 24 Sep 1998

<shakes head ruefully> You kids today, with your piercings and your big pants
and your purple-and-green hair and your X-Files and your Paula Cole and your
espresso coffee and your Seattle grunge rock and your virtual machines and your
acid-washed jeans and your Ernest Hemingway and your object-oriented languages
and your fax machines and your hula hoops and your zoot suits and your strange
slang phrases like "That's so bogus" or "What a shocking bad hat" and those
atonal composers like Arnold Schoenberg and Milton Babbit that you kids seem to
like these days and your cubist painters and your Ally McBeal and that guy in
Titanic and your TCP/IP protocol and your heads filled with all that Cartesian
dualism these days and ... well, I just don't get you kids. <shakes head
ruefully again>
      -- A.M. Kuchling, 1 Oct 1998

    E.g., at the REBOL prompt I typed
send tim@email.msn.com "Did this work?"
     and in response it dialed my modem, connected to my ISP, and then REBOL
crashed after provoking an invalid page fault in kernel32.dll. Then my
connection broke, and the modem dialed and connected again. Then it just sat
there until it timed out.
    now-*that's*-user-friendly<wink>-ly y'rs
      -- Tim Peters, 24 Sep 1998

I've reinvented the idea of variables and types as in a programming language,
something I do on every project.
      -- Greg Ward, September 1998

    "The event/tree dualism reminds me why I always wanted to be able to do
pattern matching on trees."
    "'Honey, what is this guy doing up there?' 'Oh, I suppose it's Christian,
trying to match some patterns.' "
      -- Christian Tismer and Dirk Heise, 12 Oct 1998

Perl is worse than Python because people wanted it worse.
      -- Larry Wall, 14 Oct 1998

    "What's the opinion of the (wink) Python luminaries?"
    "The last time I saw a position paper from them, they came out strongly
against the suggestion that old people be put on ice floes and left to drift
out to sea to die.
    they-never-like-*any*-of-my-ideas-ly y'rs"
      -- Stuart Hungerford and Tim Peters, 14 Oct 1998

Rather than borrowing from our beauty-impaired ugly sibling, why not look at
Java, the beautiful, conceited sister? We could have something more like
JavaDoc.
      -- Paul Prescod, 18 Oct 1998

    It won't work. This is far too concrete a problem to interest Tim. I see 3
possible approaches:
    1) Claim that Python can't do a <some random combination of 'L', 'R', 'A'>
grammar. This will yield an irate response from Aaron which will draw Tim into
it and you'll get a solution in 3 months after lots of entertaining posts.
    2) Turn it into an optimization problem and get a solution from Marc- Andre
using mxTextTools next week.
    3) Turn it into an obfuscation problem and get competing solutions from
Greg Stein and Fredrik tomorrow morning.
    if-anybody's-found-don-beaudry's-sucker-button-let-me-know ly 'yrs
      -- Gordon McMillan, 16 Oct 1998

To my battle-scarred mind, documentation is never more than a hint. Read it
once with disbelief suspended, and then again with full throttle skepticism.
      -- Gordon McMillan, 19 Oct 1998

    Then let the record show that I hereby formally lobby for such an
optimization! I'd lay out some arguments, except that it's already implemented
<wink>.
    well-*that*-one-went-easy-ly y'rs - tim
      -- Tim Peters, 20 Oct 1998

We did requirements and task analysis, iterative design, and user testing.
You'd almost think programming languages were an interface between people and
computers.
      -- Steven Pemberton, one of the designers of Python's direct ancestor
         ABC

Not at all, although I agree here too <wink>. It's like saying a fork is broken
just because it's not that handy for jacking up a car. That is, Guido
implemented the syntax to support default arguments, and it works great for
that purpose! Using it to fake closures is a hack, and the "hey, this is cool!"
/ "hey, this really sucks!" mixed reaction thus follows, much as pain follows a
car falling on your skull. Stick to stabbing peas, or even teensy pea-sized
closures, and a fork serves very well.
      -- Tim Peters, 31 Oct 1998

My customers consider it a marketable skill that I a) think for myself b) share
my thoughts with them.
      -- Paul Prescod, 2 Nov 1998

    Anyone else know what a Stanley #45 plane is? ... it's not what you use if
you aren't looking to produce intricate moldings. If you want to make a
tabletop flat, and bring out the natural beauty of the wood, you use a big,
long and flat bench plane. The beauty is in the wood, not the tool, the tool is
just the right one to let you see that and to let others see it too.
    And that's a very impressive kind of beauty in itself, isn't it? The kind
of beauty some say is homely--an uninteresting face, boring angles, few if any
parts, no curly flowers. It's just a tool, and not beautiful at all. But look,
that tool makes beauty. It makes it *easy* to make beautiful things, to see
deep into the the grain of whatever material you're working.
    Maybe it gets us a little closer to art.
      -- Ivan Van Laningham, 3 Nov 1998

You might think "That's illegal." That's not illegal; that's *cool*.
      -- Paul Dubois at IPC7, on recursive template definitions in C++

This supports reflection, which is the 90s way of writing self- modifying code.
      -- John Aycock at IPC7, during his parsing talk

It turns out that docstrings are the only way to associate information with
functions, which is what led you to abuse them in such a fascinating and
stomach-churning way.
      -- Jim Hugunin at IPC7, on embedding BNF parsing rules in docstrings

    "The Mayans looked on the integers as gods."
    "What did the Mayans think of integer division?"
      -- Ivan Van Laningham and an unknown audience member at IPC7

Y2K problem? The Mayans didn't have a *millennium*-2K problem!
      -- Eric S. Raymond at IPC7, on learning that the Mayan calendar takes
         28 octillion years to wrap around

"Generic identifier" -- think about it too much and your head explodes.
      -- Sean McGrath at IPC7, discussing SGML terminology

Nothing I've ever written has reached 1.0.
      -- Greg Ward at IPC7, on using small version numbers

Well, that's a little thing -- the specification.
      -- Guido van Rossum at IPC7

    "We've got a name (Module Distribution Utilities) that gives us a good
3-letter acronym to group things under: MDU."
    "<thpftbt>"
      -- Greg Ward and Jeremy Hylton at IPC7

Mailman is designed to be extensible *and* comprehensible. Without
comprehensibility, enhancement is self-limiting -- functionality may be
improved, but further enhancement gets increasingly difficult.
      -- Ken Manheimer at IPC7

"Generating Usable Installations" -- OK, you've got the GUI SIG.
      -- Barry Warsaw at IPC7, on the choice of name for a SIG to discuss
         extension building

Performance is a lot like drugs -- it doesn't do much for you, but it occupies
a lot of your time.
      -- Jeremy Hylton at IPC7, on the need for a Performance SIG

I made some slides, but they suck, so I won't bother with them.
      -- Andrew Kuchling at IPC7

    "What's Python?"
    "It's a computer programming language."
    "You mean, like DOS?"
      -- Some guy in a bar and Eric S. Raymond (who was wearing a
         conference T-shirt) at IPC7

Excellent plan! Devious minds are attracted to Python, like mimes to
unappreciative crowds.
      -- Tim Peters, 13 Nov 1998

Ha! If we had only started numbering dimensions with one, we'd already be
living in a 4-D world, and Mental Organons would be *all over the place*!
      -- Tim Peters, 13 Nov 1998

Well, during those periods when I was me, there was most assuredly only one of
me. But during some of the more intense discussions, I was not me, and while
all the rest of the attendees were also not me, it is difficult to say whether
they were the same not me that I was or wasn't at the time.
      -- Gordon McMillan, 18 Nov 1998

    If Python strays into trying to be something completely new it will fail,
like Scheme, K and Smalltalk. There are both technical and sociological reasons
for this. If you stray too far technically, you make mistakes: either you make
modelling mistakes because you don't have an underlying logical model (i.e. C++
inheritance) or you make interface mistakes because you don't understand how
your new paradigm will be used by real programmers.
    Let research languages innovate. Python integrates.
      -- Paul Prescod, 21 Nov 1998

    "I got a little mad at the way python polynomials were written -- the code
looked like its author knew neither polynomials nor Python."
    "That would be me :-)."
      -- Moshe Zadka and Guido van Rossum, 22 Nov 1998

I would recommend not wasting any more time on the naming issue. (This is a
recurring theme in my posts -- remember, I spent about 0.3 microseconds
thinking about whether "Python" would be a good name for a programming
language, and I've never regretted it.)
      -- Guido van Rossum, 25 Nov 1998

    "My course members are almost all coming from Math, and the first question
was 'why isn't it complete?' Just a matter of elegance."
    "Oh, don't worry. My background is math. This is actually good for them --
like discovering that Santa Claus doesn't really exist."
      -- Christian Tismer and Guido van Rossum, 2 Dec 1998

One of my cheap entertainments is axiomatizing characterizations of [Tim
Peters]. I think I've come up with a minimal one: the only c.l.p poster more
concerned with working non-legal code than non-working legal code.
      -- Cameron Laird, 2 Dec 1998

PYTHON = (P)rogrammers (Y)earning (T)o (H)omestead (O)ur (N)oosphere.
      -- Seen in Sean McGrath's .sig, 3 Dec 1998

I never realized it before, but having looked that over I'm certain I'd rather
have my eyes burned out by zombies with flaming dung sticks than work on a
conscientious Unicode regex engine.
      -- Tim Peters, 3 Dec 1998

"Python? Oh, I've heard of that. I have a friend at the NSA who uses it."
      -- Overhead at a meeting, quoted in c.l.p on 3 Dec 1998

I think Gordon has priority on this one, since it's clearly a consequence of
his observation that tim_one despises and deplores anything useful. Which has
greater explanatory power, since I've often noted that tim_one complains about
legal working code too! Anything that works may be useful, right? Brrrrr. Must
destroy.
      -- Tim Peters in the third person, 3 Dec 1998

"Eric has a way of explaining what we're doing and why we're doing it," says
Guido van Rossum, the inventor of a programming language called Python and a
prominent figure among open-source proponents. Van Rossum, a gawky Dutchman who
now lives in Reston, invited Raymond to address a group of Python software
developers in Houston...
      -- From the _Washington Post_, 3 Dec 1998

Subclassing with a mixin doesn't let you, for example, interfere with how an
existing attribute is accessed. The general idea here is to kidnap the object,
skin it, then waltz around in public impersonating it. All without letting the
programmer / user know he's been bamboozled.
      -- Gordon McMillan, 3 Dec 1998

    Hey, while they're all eating dinner, let's sneak in a keyword!
    emancipate variable: declare absolute freedom for one variable. It can be
whatever it wants whenever it wants in whatever form it wants in whatever
language it wants on whatever computer it wants. In the ensuing chaos it will
get nothing done, but it will give programmers stories to tell for years to
come...
      -- Mike Fletcher, 25 Dec 1998

    "Can we kill this thread? The only thing it does as far as I'm concerned is
increase the posting statistics. :-)"
    "don't-open-cans-of-worms-unless-you're-looking-for-a-new-diet-ly y'rs"
      -- Guido van Rossum and Tim Peters, 6 Jan 1999

    Hey, that was the first truly portable laptop! Of course I'm nostalgic.
Came with a mighty 24Kb RAM standard, & I popped the extra $80 to max it out at
32Kb. Much of Cray's register assigner was developed on that beast: unlike the
prototype Crays of the time, the M100 was always available and never crashed.
Even better, I could interrupt it any time, poke around, and resume right where
it left off <wink>.
    m100-basic-reminded-me-a-lot-of-python-except-that-it-sucked-ly y'rs
      -- Tim Peters remembering the Model 100, 10 Jan 1999

    "Heh -- all it really broke so far was my resistance to installing Tk. I
suppose wizardry is inevitable after one installs something, though <wink>."
    "Spoken like a truly obsessive-compulsive wizard! It-takes-one-to-know
-one..."
      -- Tim Peters and Guido van Rossum, 6 Jan 1999

Note, however, that architectural forms are completely declarative and can be
implemented in a highly optimized fashion. The sorts of extensions that
Microsoft has proposed for XSL (<xsl:eval>...</>) would completely destroy
those features. Architectural mapping would, in general, be as reliable and
high performance as ordinary software -- (not at all).
      -- Paul Prescod, 6 Jan 1999

Darned confusing, unless you have that magic ingredient *coffee*, of which I
can pay you Tuesday for a couple pounds of extra-special grind today.
      -- John Mitchell, 11 Jan 1999

That's so obvious that someone has already got a patent on it.
      -- Guido van Rossum, 12 Jan 1999

I have to stop now. I've already told you more than I know.
      -- Wolf Logan, 14 Jan 1999

I really don't have any incisive insights about the economic mechanisms or
viability of free software and open source, but I do have a strong, clear sense
that such things make it possible for me to do my job, as a programmer and a
facilitator of/participant in online communities, better and more easily than I
otherwise could do.
      -- Ken Manheimer, 24 Jan 1999

    Every standard applies to a certain problem domain and a certain level. A
standard can work perfectly and save the world economy billions of dollars and
there will still be software and hardware compatibility problems. In fact,
solving one level of compatibility just gives rise to the next level of
incompatibility. For example, connecting computers together through standard
protocols gives rise to the problem of byte endianness issues. Solving byte
endianness gives rise to the problem of character sets. Solving character sets
gives rise to the problem of end-of-line and end-of-file conventions. Solving
that gets us to the problem of interpreting the low-level syntax (thus XML).
Then we need to interpet that syntax in terms of objects and properties (thus
RDF, WDDX, etc.). And so forth.
    We could judge a standard's success by its ability to reveal another level
of standardization that is necessary.
      -- Paul Prescod, 24 Jan 1999

I just want to go on the record as being completely opposed to computer
languages. Let them have their own language and soon they'll be off in the
corner plotting with each other!
      -- Steven D. Majewski, 25 Jan 1999

Constraints often boost creativity.
      -- Jim Hugunin, 11 Feb 1999

Programming is no different - it's only by going outside what you know, and
looking from another direction (working, if you like, your brain, so that it
can be more powerful :-) that you can improve further.
      -- Andrew Cooke, 12 Feb 1999

any-technology-indistinguishable-from-magic-is-too-mysterious- to- trust-ly
y'rs
      -- Tim Peters, 16 Feb 1999

    "I don't think we've thought of this, and it's actually a good idea."
    "I'd better go patent it!"
      -- Uche Ogbuji and Paul Prescod, 16 Feb 1999

Contrary to advertising, no parsing system is "easy to learn", in or out of the
Python world -- parsing is a hard problem. Most are easy enough to use after
practice, though. Ironically, the trickiest system of all to master (regexps)
is also the feeblest and the most widely used.
      -- Tim Peters, 17 Feb 1999

So Python's only cross-platform choices were to mimic the C/POSIX API or invent
its own new x-platform API; only one of those is realistic (as Java proves
every day <wink>).
      -- Tim Peters, 21 Feb 1999

Yes: the code in ntpath.split is too clever to have any hope of working
correctly <wink>.
      -- Tim Peters, 19 Mar 1999

Thanks. The sooner I get discouraged and quit, the more time I'll save overall.
      -- Frank Sergeant, 28 Mar 1999

But it's a general way to debug: tell someone what right things your program is
doing. Chances are that you will see the wrong thing(s) before the other person
has said anything... I just stick a picture of a face on my monitor and talk to
it to find bugs.
      -- Richard van de Stadt, 9 Apr 1999

Might just be nostalgia, but I think I would give an arm or two to get that
(not necessarily my own, though).
      -- Fredrik Lundh, 13 May 1999

    1. Beautiful is better than ugly.
    2. Explicit is better than implicit.
    3. Simple is better than complex.
    4. Complex is better than complicated.
    5. Flat is better than nested.
    6. Sparse is better than dense.
    7. Readability counts.
    8. Special cases aren't special enough to break the rules.
    9. Although practicality beats purity.
    10. Errors should never pass silently.
    11. Unless explicitly silenced.
    12. In the face of ambiguity, refuse the temptation to guess.
    13. There should be one -- and preferably only one -- obvious way to do it.
    14. Although that way may not be obvious at first unless you're Dutch.
    15. Now is better than never.
    16. Although never is often better than *right* now.
    17. If the implementation is hard to explain, it's a bad idea.
    18. If the implementation is easy to explain, it may be a good idea.
    19. Namespaces are one honking great idea -- let's do more of those!
      -- Tim Peters' 19 Pythonic Theses, 4 Jun 1999

    "However, I've heard that after about 10K items in a dict, it starts having
problems."
    "11,523 to be exact. After that, dicts drink to excess and show up for work
late the morning after. We don't like to talk about it, though."
      -- Aahz Maruch and Tim Peters, 8 Jun 1999

Stackless Python 0.2, a plug-in replacement for the Python core that does not
use the C stack, has been announced by Christian Tismer as the best way to
prove that it was possible without a major rewrite to the core. Neel
Krishnaswami commented to Christian, "This is very neat, and you are completely
deranged".
      -- From Linux Weekly News, 17 Jul 1999

... we need more people like him, who are willing to explore without being
driven to argue with people about it.
      -- William Tanksley on Chuck Moore, inventor of Forth, 2 Jul 1999

Sorry for the term, I picked it up from Jim Fulton back when it was an
about-to-be-added feature for Principia/Aqueduct. As with so many Fultonisms,
it's vivid and tends to stick in one's (non-pluggable) brain.
      -- Paul Everitt on the term "pluggable brains", 5 Jul 1999

I picture a lump of inanimate flesh (a result from a relational database query)
being infused with the spark of life (object behavior, aka class).
      -- Jim Fulton on the term "pluggable brains", 5 Jul 1999

This is good. It means that while Ionesco is dead, his spirit lives on.
      -- Gordon McMillan on how Windows attaches meaning to 3-character
         file extensions, 30 Jul 1999

(On the statement print "42 monkeys"+"1 snake") BTW, both Perl and Python get
this wrong. Perl gives 43 and Python gives "42 monkeys1 snake", when the answer
is clearly "41 monkeys and 1 fat snake".
      -- Jim Fulton, 10 Aug 1999

I expect that what you really object to is the absence of control structures
other than goto, and the LT/GE/etc spelling of comparison operators. That was
common enough in its day, and even by the time Pascal came around the keypunch
I used still didn't have a semicolon key. It looks ugly in retrospect only
because it is <wink>.
      -- Tim Peters on SNOBOL4, 17 Aug 1999

Theory and reality rarely are kissing cousins.
      -- Christopher Petrilli, 1 Sep 1999

Features generally don't exist in isolation, and you have to look at all the
consequences, not just the one that attracts you at first sight.
      -- Tim Peters, 3 Sep 1999

The danger in this line of thinking is not realizing that the computational
effort involved in big NP complete problems is *so* huge that even in optimized
micro-code, the algorithm might take a million years to run. Tweezers or shovel
-- it makes little difference when you are trying to move a universe...
      -- Sean McGrath, 4 Sep 1999

On a scale of one to ten I'd give it a tim.
      -- William Tanksley, 13 Sep 1999

Statistical analysis shows that the junk looks like human text, which clearly
shows that it is actually used in some yet unknown way. (docstrings?)
      -- Fredrik Lundh, writing about junk DNA, 5 Oct 1999

If I engineer code that I expect to be in use for N years, I make damn sure
that every internal limit is at least 10x larger than the largest I can
conceive of a user making reasonable use of at the end of those N years. The
invariable result is that the N years pass, and fewer than half of the users
have bumped into the limit <0.5 wink>.
      -- Tim Peters, 11 Nov 1999

I don't think the bytecodehacks, while sufficiently dark and useless to be a
tim-ism, qualify me in any way for a Pythonic Wizard Hat...
      -- Michael Hudson, 16 Nov 1999

The bottom tier is what a certain class of wanker would call "business
objects"...
      -- Greg Ward, 9 Dec 1999

Since I've done fewer than my normal quota of futile things this week, I
thought I'd post to remind people that ...
      -- Phil Austin, 9 Dec 1999

There are useful diagrams in UML, (eg, the state and transition diagrams).
Unfortunately, the one most tools use to generate code (and draw from reverse
engineering) has everything to do with language structure, and nothing to do
with what actually happens at runtime. To put it bluntly: people spend most of
their time designing the wrong thing. Worse, they get it wrong, but it's carved
in stone now; so the final system is either needlessly complex and marginally
functional, or bears no resemblance to the "design".
      -- Gordon McMillan, 15 Dec 1999

The secret to good performance is to prototype and prototype, then code the
bottlenecks in a faster language. The secret to large systems is to prototype
and prototype, until you've got clean separation of the system into managable
pieces, then code in whatever language most suits the need of each piece.
      -- Gordon McMillan, 15 Dec 1999

When Jim [Fulton] says "tricky" it means your brain could explode.
      -- Michel Pelletier, 15 Dec 1999

You have start-tags, attributes, end-tags and character data. We have all seen
"XML applications" and "XML parsers" which handle this gang- of-four concepts.
... Now we can peer over the parapet and shout "your parser smells of
elderberries" or "I wave my mixed content at your ankles", as long as we like
but the simple gang-of-four base apps will not go away.
      -- Sean McGrath, 19 Dec 1999

Abstraction is one of those notions that Python tosses out the window, yet
expresses very well.
      -- Gordon McMillan, 6 Jan 2000

The set of naming conventions has a cardinality equal to the number of Python
users.
      -- Gordon McMillan, 6 Jan 2000

The way to build large Python applications is to componentize and
loosely-couple the hell out of everything.
      -- Aahz Maruch, 6 Jan 2000

It's not the mail volume that bothers me -- I can ignore 100s of messages a day
very quickly. It's the time it takes to respond to all of them.
      -- Guido van Rossum, 20 Jan 2000

This is the way of Haskell or Design by Contract of Eiffel. This one is like
wearing a XV century armor, you walk very safely but in a very tiring way.
      -- Manuel Gutierrez Algaba, 26 Jan 2000

Life's better without braces.
      -- Unofficial motto of IPC8, coined by Bruce Eckel

"Aggressive" means "sometimes wrong".
      -- John Aycock at IPC8, during his "Agressive Type Inferencing" talk

Do I do everything in C++ and teach a course in advanced swearing?
      -- David Beazley at IPC8, on choosing a language for teaching

Alice is 3D Logo on steroids.
      -- Randy Pausch at IPC8

I was willing to grant this one at once, but, now that I look back at it all --
the loyalty oaths, the relentless self-criticism sessions, the midnight visits
from the Ministry of Love -- I'm afraid what we really have here is unspeakably
more sinister.
      -- Tim Peters after a reference to "Python's cult-like following", 2
         Feb 2000

Guido (like us!) is a bit schizophrenic here: he wants to be a benevolent
dictator, but also wants to treat people like grownups. This probably worked
better before Python got a large American audience <0.9 wink>.
      -- Tim Peters, 10 Feb 2000

I have formal proofs that any change of the indentation rules results in 35%
increase of the page faults for only 63.7% of the cache misses. The net effect
is an overall slowdown of 10%.
      -- Vladimir Marangozov after Yet Another indentation flamewar, 16 Feb
         2000

... let me just say that my least-favourite Python error message is
"SyntaxError: invalid syntax", which somehow manages to be both overly terse
and redundant at the same time.
      -- Greg Ward, 15 Feb 2000

    See, functional programmers are an insular lot. You rarely see them in
public, except at parades when they all have antler- hats and silly shoes on.
So they completely missed the infamous "goto considered harmful" thread and
didn't even realize they were doing anything wrong.
    Now, let's pretend you're writing a 'bot that can pass as a functional
programmer. There's a complex protocol here. When two functional programmers
see each other on the street, they recognize each other by the antler hats. But
in certain parts of the Midwest, regular people wear antler hats, too. So
there's a protocol. First a <wink wink>. Then the secret handshake. Then you
sniff each other's armpits and stamp your foot 3 times.
    OK, so you've written a bot, and it works fine on the street. Now you send
it to a cocktail party. It sees a potential functional programmer and gives the
<wink wink>. Now it tries to move into position to do the secret handshake, but
discovers that it's antler-hat is entangled with someone else's. Oops. <wink
wink> at the new guy. Handshake. But before it can sniff, the first one has
moved up for his handshake. Ay yi yi. Your bot crashes and is exposed.
    So now you rewrite your bot to use a finite state machine so it can handle
multiple sessions. That means throwing out all the code that worked on the
street. But if you'd used continuations, it would be a relatively minor
adjustment of that code. 'Course you wouldn't have had to write the bot to
begin with.
      -- Gordon McMillan, 18 Feb 2000

IIRC, Guido went to CNRI to work on bots and agents or something similar. Could
the timbot and the effbot be an offshoot of that? Next, he's going to start a
company with timbot and effbot as the main products. Van Rossum's Universal
Robots?
      -- Bernhard Herzog, 21 Feb 2000

So those are the extremes: Boehm-Demers-Weiser avoids blame by refusing to do
anything. Java avoids blame by exposing an impossibly baroque
implementation-driven finalization model. Scheme avoids blame by refusing to do
anything "by magic", but helps you to shoot yourself with the weapon of your
choice. The bad news is that I don't know of a scheme *not* at an extreme!
      -- Tim Peters on the knotty problem of finalizers and cycles, 3 Mar
         2000

It's extremely un-Pythonic to let things leak (despite that it has let things
leak for a decade <wink>), but also extremely un-Pythonic to make some wild-ass
guess.
      -- Tim Peters on garbage collection, 3 Mar 2000

IOW, the only people who lose under this scheme are the ones begging to lose,
and their "loss" consists of taking responsibility.
      -- Tim Peters, 3 Mar 2000

An axiom is accepted without proof: we have plenty of proof that there's no
thoroughly good answer (i.e., every language that has ever addressed this issue
-- along with every language that ever will <wink>).
      -- Tim Peters on garbage collection, 3 Mar 2000

    I can see the FAQ now...
    Q1.1.2.3: Why can't I divide integers?
    A: You drooling moron! You need a 10-page owners manual and instructional
video to handle the notational complexity of Tic-Tacs, don't you? As every
schoolboy knows, the integers are a *ring*, not a field, you simpering
simpleton. Oh wait! Let me guess! I have to spell it out for you, you festering
wombat boil. You can't divide integers by integers and get integers. Understand
now? Now go out there and don't do it. And read Herstein, while you're at it.
      -- Johann Hibschman, 4 Mar 2000

Actually, I believe you understand me fine, you'd just rather not believe it:
floating point sucks, rationals suck, refusing to allow int division sucks, the
constructive reals suck, symbolic manipulation sucks, ..., but all in different
ways for different reasons. Every one bristles with its own brands of both
shallow and deep "surprises". So it goes -- seeking to represent the infinite
by the finite is an inherently unreachable goal. This is also why people die
<wink>.
      -- Tim Peters, 4 Mar 2000

    The reason I'm right is that I said there won't be any *single* "survivor"
of the evolutionary struggle, and that the efforts to crown one's favorite as
such are just so much noise. The software ecosystem of the foreseeable future
will always have its own form of "diversity": there will be lions *and*
elephants *and* fish *and* seals *and* birds, because there will be many
diverse "habitats" where the particular adaptations of each will be
needed/advantageous.
    The reasoned debates (as opposed to religious wars) may lead to lions with
opposable thumbs, or elephants that can see in the infrared, but there will
never be a 2000-pound fish with a mane and wings. Well, not outside the lab,
anyway.
      -- Ran, 5 Mar 2000

"Complexity" seems to be a lot like "energy": you can *transfer* it from the
end user to one/some of the other players, but the total amount seems to remain
pretty much constant for a given task.
      -- Ran, 5 Mar 2000

LaTeX2HTML is pain.
      -- Fred Drake in a documentation checkin message, 14 Mar 2000

Here, have some cycles of reversed kielbasa. And ten (10 (0xa (101010b)))
Usenet Points, redeemable in comp.lang.python for increased local prestige.
Some prestige may depend upon your own actions. Local Prestige may or may not
have any effect on your actual life (or lack thereof).
      -- William Tanksley, 21 Mar 2000

Mucking with builtins is fun the way huffing dry erase markers is fun. Things
are very pretty at first, but eventually the brain cell lossage will more than
outweigh that cheap thrill.
      -- Barry Warsaw, 23 Mar 2000

    >Have you ever looked at the output of a bib | tbl | eqn pipeline?
    Are you kids still using that as a pick-up line?
      -- Roy Smith and Cameron Laird, 4 Apr 2000

This is like getting lost in a dictionary. What does quincuncial mean anyhow?
      -- Dennis Hamilton, 4 Apr 2000

UTF-8 has a certain purity in that it equally annoys every nation, and is
nobody's default encoding.
      -- Andy Robinson, 10 Apr 2000

    "Now if we could figure out where python programmers are from, someone
could write a book and get rich."
    "Yorkshire."
      -- Quinn Dunkan and Warren Postma, 11 Apr 2000

If I didn't have my part-time performance art income to help pay the bills, I
could never afford to support my programming lifestyle.
      -- Jeff Bauer, 21 Apr 2000

Of course, this brought me face to face once again with Python's _pons
asinorum_, the significance of whitespace.
      -- Eric S. Raymond, in the _Linux Journal_'s Python supplement

Surprisingly enough, Python has taught me more about Lisp than Lisp ever did
;-).
      -- Glyph Lefkowitz, 3 May 2000

How about we notate the hungarian notation with the type of hungarian notation,
you know, hungarian meta notation: HWND
aWin32ApiHandleDefinedInWindowsDotH_hwndWindowHandle;
      -- Warren Postma, 4 May 2000

Note that Python's licence is in fact the MIT X11 licence, with MIT filed off
and CNRI written in its place in crayon.
      -- A.M. Kuchling, 5 May 2000

Once you've read and understood _The Art of the Metaobject Protocol_ you are
one quarter of the way to provisional wizard status. (The other three-fourths
are b) understanding Haskell's monads, c) grokking Prolog, and d) becoming
handy with a combinator- based language by implementing a Forth.)
      -- Neel Krishnaswami, 9 May 2000

"The future" has arrived but they forgot to update the docs.
      -- R. David Murray, 9 May 2000

/* This algorithm is from a book written before the invention of structured
programming... */
      -- Comment in parser/pgen.c, noted by Michael Hudson

For more information please see my unpublished manuscript on steam driven
turing machines. [2000pp in crayon donated to the harvard library -- they never
told me whether they filed it under mathematics, philosophy, logic, mechanical
engineering, or computational science]
      -- Aaron Watters, 12 May 2000

    Me? I hate the whole lambda calculus, not because of what it is, but
because of what many people think it is. They think that it's the whole of
computer science, the ultimate way to express and reason about programs, when
in reality it's merely a shabby and incomplete model of how Fortran fails to
work. The first thing SICP has to do is teach everyone how bad the lambda
calculus model is -- as part of teaching them about a language allegedly based
on lambda calculus.
    I'm sorry, was my bias showing again? :-)
      -- William Tanksley, 13 May 2000

I never got beyond starting the data-structures in C++, I never got beyond
seeing how it would work in Scheme. I finished it in one Python -filled
afternoon, and discovered the idea sucked big time. I was glad I did it in
Python, because it only cost me one afternoon to discover the idea sucks.
      -- Moshe Zadka, 13 May 2000

In truth, we use 'j' to represent sqrt(-1) for exactly the same reason we use a
convention for the direction of current which is exactly the opposite of the
direction the electrons actually travel: because it drives physicists crazy.
(And if we pick up a few mathematicians or whatever along the way, well, that's
just gravy. ;-)
      -- Grant R. Griffin, 14 May 2000

Unicode: everyone wants it, until they get it.
      -- Barry Warsaw, 16 May 2000

I saw a hack you sent me a few months ago and approved of its intent and was
saddened by its necessity.
      -- Jim Fulton, 16 May 2000

Suspicions are most easily dispelled/confirmed via evidence, and taking the
trouble to do this has the pleasant side-effect that you can either cease
expending effort worrying, or move directly to taking positive action to
correct the problem.
      -- Neel Krishnaswami, 21 May 2000

Thanks to the overnight turnaround and the early interpreter's habit of
returning nothing at all useful if faced with a shortage of )s, one could
easily detect the LISP users: they tended to walk around with cards full of
)))))))... in their shirt pockets, to be slapped onto the end of submitted card
decks: one at least got something back if there were too many )s.
      -- John W. Baxter, 21 May 2000

Python: embodies a harmony of chocolate kisses with hints of jasmine and rose.
Trussardi's wild new fragrance.
      -- From _Marie Claire_, Australian edition, May 2000; noted by Fiona
         Czuczman

In arts, compromises yield mediocre results. The personality and vision of the
artist has to go through. I like to see Python as a piece of art. I just hope
the artist will not get too tainted by usability studies.
      -- François Pinard, 22 May 2000

In fact, I've never seen an argument about which I cared less. I'm completely
case insensitivity insensitive.
      -- William Tanksley, 23 May 2000

They boo-ed when Dylan went electric. But for me its about the instincts of a
designer, and the faith of a fan. Not science. So much the better.
      -- Arthur Siegel, 23 May 2000

Burroughs did something very odd with COBOL at one point (and no, it wasn't The
Naked Lunch).
      -- Will Rose, 27 May 2000

Code generators are hacks. Sometimes necessary hacks, but hacks nevertheless.
      -- Paul Prescod, 7 Jun 2000

Very rough; like estimating the productivity of a welder by the amount of
acetylene used.
      -- Paul Svensson, on measuring programmer productivity by lines of
         code, 19 Jun 2000

I vote for backward compatibility for now, and not only because that will
irritate /F the most.
      -- Tim Peters, 30 Jun 2000

A comment is in order then. If the code is smarter than it looks, most people
aren't going to think it looks very smart.
      -- Jeremy Hylton, 6 Jul 2000

You and I think too much alike ?!ng. And if that doesn't scare you now, you
should have a talk with Gordon.
      -- Barry Warsaw, 12 Jul 2000

Isn't it somewhat of a political statement to allow marriages of three or more
items? I always presumed that this function was n-ary, like map().
      -- Paul Prescod, on the proposed name marry() for a function to
         combine sequences, 12 Jul 2000

Since my finger was slowest reaching my nose, I got elected Editor. On the
positive side of that, I get to make the early decisions that will be cursed
for generations of Python hackers to come.
      -- Barry Warsaw, 12 Jul 2000

Hey, you know, we can work this in. Sailor Moon + Giant Robots + Tentacle
Demons + Python Conference == Bizarre hilarity ensues!
      -- Alexander Williams, 4 Aug 2000

The rapid establishment of social ties, even of a fleeting nature, advance not
only that goal but its standing in the uberconscious mesh of communal psychic,
subjective, and algorithmic interbeing. But I fear I'm restating the obvious.
      -- Will Ware, 28 Aug 2000

The comp.lang.python newsgroup erupted last week with a flurry of posts that
accused the Python development team of creeping featurism, selling out the
language to corporate interests, moving too fast, and turning a deaf ear to the
Python community. What triggered this lava flow of accusations? The development
team accepted a proposal to change the syntax of the print statement.
      -- Stephen Figgins, 30 Aug 2000

    INTERVIEWER: Tell us how you came to be drawn into the world of pragmas.
    COMPILER WRITER: Well, it started off with little things. Just a few
boolean flags, a way to turn asserts on and off, debug output, that sort of
thing. I thought, what harm can it do? It's not like I'm doing anything you
couldn't do with command line switches, right? Then it got a little bit
heavier, integer values for optimisation levels, even the odd string or two.
Before I knew it I was doing the real hard stuff, constant expressions,
conditionals, the whole shooting box. Then one day when I put in a hook for
making arbitrary calls into the interpreter, that was when I finally realised I
had a problem...
      -- Greg Ewing, 31 Aug 2000

The modules people have built for Python are like the roads the Romans built
through Europe. On this solid ground, you can move fast as you work on aspects
of program design that aren't so analytical -- user interface, multi-threaded
event dispatching models, all kinds of things that can be done a lot of
different ways and are hard to get right the first time through.
      -- Donn Cave, 3 Sep 2000

Python 2.0 beta 1 is now available from BeOpen PythonLabs. There is a long list
of new features since Python 1.6, released earlier today. We don't plan on any
new releases in the next 24 hours.
      -- Jeremy Hylton, in the 2.0b1 announcement, 5 Sep 2000

Fortunately, you've left that madness behind, and entered the clean, happy, and
safe Python world of transvestite lumberjacks and singing Vikings.
      -- Quinn Dunkan, 17 Sep 2000

Regular expressions are among my most valued tools, along with goto, eval,
multiple inheritance, preemptive multithreading, floating point, run-time type
identification, a big knife, a bottle of bleach, and 120VAC electricity. All of
these things suck sometimes.
      -- Kragen Sitaker, 27 Sep 2000

    IIRC, he didn't much care for regexps before, but actually writing a regexp
engine drives most people who do it to intense hatred.
    Just more of the magic of Python! Transmuting a few peoples' intense agony
into the subject of others' idle amusement <wink>.
      -- Tim Peters, 27 Sep 2000

"I do not love thee, lambda; let me count the ways..."
      -- Aahz Maruch, 27 Sep 2000

They are called "Exceptions" because to any policy for handling them, imposed
in advance upon all programmers by the computer system, some programmers will
have good reason to take exception.
      -- William Kahan, quoted by Tim Peters, 13 Oct 2000

"Interim steps" have a tendency to become permanent in our industry, where
"Compatibility" is the way the sins of the fathers are inflicted upon the third
and fourth generations ...
      -- William Kahan, quoted by Huaiyu Zhu, 16 Oct 2000

The most successful projects I've seen and been on *did* rewrite all the code
routinely, but one subsystem at a time. This happens when you're tempted to add
a hack, realize it wouldn't be needed if an entire area were reworked, and mgmt
is bright enough to realize that hacks compound in fatal ways over time. The
"ain't broke, don't fix" philosophy is a good guide here, provided you've got a
very low threshold for insisting "it's broke".
      -- Tim Peters, 25 Oct 2000

Humour is a tricky thing. Some people can't even get the spelling right.
      -- Richard Brodie, 30 Oct 2000

    The same way as you get the name of that cat you found on your porch: the
cat (object) itself cannot tell you its name, and it doesn't really care -- so
the only way to find out what it's called is to ask all your neighbours
(namespaces) if it's their cat (object)...
    ....and don't be surprised if you'll find that it's known by many names, or
no name at all!
      -- Fredrik Lundh, 3 Nov 2000, in answer to the question "How can I
         get the name of a variable from C++ when I have the PyObject*?"

These are mostly nice features, to be sure, but they're also just that:
features. C++ has features. Python doesn't have a stellar score on my
elegance-o-meter, but for me its major win is the lack of features, and lack of
ambiguities. It fits in my brain.
      -- Quinn Dunkan, 18 Nov 2000

When explaining programming I sometimes compare programmers to photographers:
amateur photographers talk about cameras and lenses and gadgets. They know how
to make their camera do almost anything, and they are keen to argue the merits
of their favorite tools. Professional photographers talk about contrast and
lighting and composition. The camera is almost irrelevant. Ansel Adams used
cameras that were less sophisticated than a supermarket disposable, back when
photography was slow and tedious (like batch-oriented programming). Because the
technology was so primitive, he carefully planned his photographs and developed
discipline so he could reliably make excellent photographs over and over.
      -- Greg Jorgensen, 26 Nov 2000

As you might have guessed, I didn't do this just for fun. It is the old game of
explaining what is there, convincing everybody that you at least know what you
are talking about, and then three days later coming up with an improved
application of the theory.
      -- Christian Tismer, 11 Dec 2000

Have they sprouted a new timbot, more geared towards newbies, more polite and
friendly maybe, with a touch of human fallibility (hence the occasional slip of
the keyboard) and named it Alex?
      -- Carel Fellinger, 12 Dec 2000

I'm spending most of my waking hours understanding this patch -- it is a true
piece of wizardry.
      -- GvR, discussing a patch from Neil Schemenauer, 13 Dec 2000

    Maybe they took solidity *for granted*, because, in their (Renaissance)
times and in their (Architecture) calling, compromises regarding solidity were
simply unthinkable. Well, we're not so lucky, in the software field, today; the
Firmitas of *by far* most software around is imperfect.
    We *must* live by "do the simplest thing that can possibly work" -- give
solidity its proper, foremost place. One of the debilitating factors for much
current software is a misplaced emphasis on assumed 'convenience' (funky GUIs,
quirky shortcuts, special cases aplenty) to the detriment of solidity. A small
but crucial step to reverse this trend, is to start by putting the order right
once more... the way Vitruvius had it!
      -- Alex Martelli, 13 Dec 2000

The Martellibot Mark 1 has a completely European flavour to it, and adds a
cosmopolitan touch of linguistics to its output, sprinkling foreign language
references in. It is similar to the timbot in its overall erudition, but can be
distinguished from it by its tendency to indulge in flamewars (which, I
believe, it does mostly to convince us it is human).
      -- Steve Holden, 13 Dec 2000

    In keeping with the religious nature of the battle-- and religion offers
precise terms for degrees of damnation! --I suggest:
    struggling -- a supported feature; the initial state of all features; may
transition to Anathematized
    anathematized -- this feature is now cursed, but is supported; may
transition to Condemned or Struggling; intimacy with Anathematized features is
perilous
    condemned -- a feature scheduled for crucifixion; may transition to
Crucified, Anathematized (this transition is called "a pardon"), or Struggling
(this transition is called "a miracle"); intimacy with Condemned features is
suicidal
    crucified -- a feature that is no longer supported; may transition to
Resurrected
    resurrected -- a once-Crucified feature that is again supported; may
transition to Condemned, Anathematized or Struggling; although since
Resurrection is a state of grace, there may be no point in human time at which
a feature is identifiably Resurrected (i.e., it may *appear*, to the
unenlightened, that a feature moved directly from Crucified to Anathematized or
Struggling or Condemned -- although saying so out loud is heresy).
      -- Tim Peters, 18 Dec 2000

my-python-code-runs-5x-faster-this-month-thanks-to-dumping-$2K- on-a-
new-machine-ly y'rs
      -- Tim Peters, 26 Dec 2000

Really, I should pronounce on that PEP (I don't like it very much but haven't
found the right argument to reject it :-) ) so this patch can either go in or
be rejected.
      -- GvR, 04 Jan 2001, in a comment on patch #101264

The rest is history: the glory, the fame, the riches, the groupies, the
adulation of my peers. We won't mention the financial scandal and subsequent
bankruptcy lest it discourage you for no good reason <wink>.
      -- Tim Peters, 14 Jan 2001

If you're using anything besides US-ASCII, I *stringly* suggest Python 2.0.
      -- Uche Ogbuji (A fortuitous typo?), 29 Jan 2001

    "There goes Tim, browsing the Playboy site just for the JavaScript.
Honest."
    "Well, it's not like they had many floating-point numbers to ogle! I like
'em best when the high-order mantissa bits are all perky and regular, standing
straight up, then go monster insane in the low-order bits, so you can't guess
*what* bit might come next! Man, that's hot. Top it off with an exponent field
with lots of ones, and you don't even need any oil. Can't say I've got a
preference for sign bits, though -- zero and one can both be saucy treats. Zero
is more of a tease, so I guess it depends on the mood."
      -- Barry Warsaw and Tim Peters, 3 Feb 2001

We were sincerely hoping that the Python core team would teach their employers
how to code Python, instead of the other way around...
      -- Pieter Nagel, 5 Feb 2001

This bug fix brought to you by the letters b, c, d, g, h, ... and the reporter
Ping.
      -- Jeremy Hylton in a checkin message for Python/compile.c, 12 Feb
         2001

    "It's in ClassModules.py you dumb f**k - can't you tell by the name?"
    "Furthermore, RTFM is much more effective if you do it gently and make them
feel nicely embarrassed, rather than having them just say 'well, fuck you too'
when reading the first insult, and not learn a thing."
    "Thanks. I'll try to keep that in mind the next time I flame myself."
      -- Phlip, following up to a query he'd posted earlier, and Thomas
         Wouters, 18 Feb 2001

    "Also, does the simple algorithm you used in Cyclops have a name?"
    "Not officially, but it answers to "hey, dumb-ass!"
      -- Neil Schemenauer, interested in finding strongly connected
         components in graphs, and Tim Peters, 23 Feb 2001

Make this IDLE version 0.8. (We have to skip 0.7 because that was a CNRI
release in a corner of the basement of a government building on a planet
circling Aldebaran.)
      -- GvR, in a CVS commit message, 22 Mar 2001

Python: programming the way Guido indented it.
      -- Digital Creations T-shirt slogan at IPC9

Stackless Python: programming the way Guido prevented it.
      -- Christian Tismer's title slide, at IPC9

I don't think we should use rational numbers for money because money isn't
rational.
      -- Moshe Zadka, at IPC9

We can't stop people from complaining but we can influence what they complain
about.
      -- Tim Peters, at IPC9

Perl is like vise grips. You can do anything with it but it is the wrong tool
for every job.
      -- Bruce Eckel, at IPC9

Given the choice between a good text editor and a good source control system,
i'll take the source control, and use "cat" to write my code.
      -- Greg Wilson, at IPC9

here's the eff-bot's favourite lambda refactoring rule:
1) write a lambda function
2) write a comment explaining what the heck that lambda does
3) study the comment for a while, and think of a name that captures
   the essence of the comment
4) convert the lambda to a def statement, using that name
5) remove the comment
      -- Fredrik Lundh, 01 Apr 2001

The GPL tried to protect the freedom of end-users to modify and redistribute
their code. Most people do not believe that this is a legitimate freedom like
freedom of speech or assembly but Richard Stallman does. I don't think that
there is an argument that that will persuade a person one way or another. If
freedoms could be proven, that famous document would probably start: "Not
everyone holds these truths to be self-evident, so we've worked up a proof of
them as Appendix A."
      -- Paul Prescod, 11 Apr 2001

That is one of the first goals. Also, we want to handle a C++ SAX stream with
Python, and vice versa (feed a Python SAX stream into Xalan). Bi-SAXuality, in
a sense. :)
      -- Jürgen Hermann, 11 Apr 2001

As you seem totally unwilling or unable to understand that _Weltanschauung_ to
any extent, I don't see how you could bring Python any constructive enhancement
(except perhaps by some random mechanism akin to monkeys banging away on
typewriters until 'Hamlet' comes out, I guess).
      -- Alex Martelli, 17 Apr 2001

    "Are we more likely to add different concrete subclasses of Consumable in
the future, or different concrete subclasses of Consumer? I suspect the former
is more likely."
    "With genetic engineering being the latest growth industry, I'm not sure
that's true. Although I expect that any new models of cow, etc. will have a
backwards compatible food-consumption protocol."
      -- Alex Martelli and Greg Ewing, 19 Apr 2001

This property is called confluence, and the proof is called the Church -Rosser
theorem. I'm sure you know this, of course, but somewhere out there there's a
college student who is being shocked that CS is actually turning out to be
relevant, for sufficiently small values of relevance.
      -- Neelakantan Krishnaswami, 20 Apr 2001

if the style mafia finds out, you may find a badly severed list comprehension
in your bed one morning, but I'd say the risk is very low.
      -- Fredrik Lundh, 10 May 2001

1495 is a *deservedly* unpopular number. After all, Lorenzo de' Medici ("il
Magnifico") died in 1492, and Giovanni de' Medici ("dalle Bande Nere") wasn't
born until 1498, so 1495 fell right in the middle of a very boring and unusual
lull where no really outstanding member of the Medici family (either branch)
was around.
      -- Alex Martelli, 24 May 2001

    "What do you call the thing that pops up and says `Searching' or something
to reassure the user that his computer hasn't crashed and the application is
still running?"
    "On Windows, that's called 'a miracle'."
      -- Laura Creighton and Tim Peters, 28 May 2001

In general, my conclusion after doing numerical work for a while is that the
desire to look at algorithms crucial to your research as black boxes is futile.
In the end, I always had to dig into the details of the algorithms because they
were either never black-boxable or the black-box versions didn't do a good
enough job.
      -- David Ascher, 28 May 2001

    "Oh, read *all* Kahan has written, and if you emerge still thinking you
*know* what you're doing when floating point is involved, you're either Tim
Peters, or the world champ of hubris."
    "I find it's possible to be both <wink>."
      -- Alex Martelli and Tim Peters, 20 May 2001

Wow, this almost looks like a real flamefest. ("Flame" being defined as the
presence of metacomments.)
      -- GvR, 13 Jun 2001

    "Maybe we also have a smaller brain than the typical Lisper -- I would say,
that would make us more normal, and if Python caters to people with a
closer-to-average brain size, that would mean more people will be able to
program in Python. History will decide..."
    "I thought it already has, pretty much."
      -- GvR and A.M. Kuchling, 14 Jun 2001

Did Guido use the time machine to get a copy of the GoF book before he started
working on the first version of Python, or are Patterns just a transparent
attempt to cover for chronically inexpressive languages like C++ and Java which
can't generally implement these mind-numbingly simple constructs in code?
      -- Glyph Lefkowitz, 7 Jun 2001

Google confuses me; if you search for "michael hudson" my page is the third hit
-- but my name doesn't actually appear anywhere on the linked page! The "did
you mean to search for..." feature is also downright uncanny. They've clearly
sold their souls to the devil -- there's no other explanation.
      -- Michael Hudson, 28 Jun 2001

You didn't say what you want to accomplish. If the idea of "provably correct"
programs appeals to you, Eiffel will give you more help than any other
practical language I know of. But since your post didn't lay out your
assumptions, your goals, or how you view language characteristics as fitting in
with either, you're not a *natural* candidate for embracing Design by Contract
<0.6 wink>.
      -- Tim Peters, 3 Jun 2001

    The static people talk about rigorously enforced interfaces, correctness
proofs, contracts, etc. The dynamic people talk about rigorously enforced
testing and say that types only catch a small portion of possible errors. The
static people retort that they don't trust tests to cover everything or not
have bugs and why write tests for stuff the compiler should test for you, so
you shouldn't rely on *only* tests, and besides static types don't catch a
small portion, but a large portion of errors. The dynamic people say no program
or test is perfect and static typing is not worth the cost in language
complexity and design difficulty for the gain in eliminating a few tests that
would have been easy to write anyway, since static types catch a small portion
of errors, not a large portion. The static people say static types don't add
that much language complexity, and it's not design "difficulty" but an
essential part of the process, and they catch a large portion, not a small
portion. The dynamic people say they add enormous complexity, and they catch a
small portion, and point out that the static people have bad breath. The static
people assert that the dynamic people must be too stupid to cope with a real
language and rigorous requirements, and are ugly besides.
    This is when both sides start throwing rocks.
      -- Quinn Dunkan, 13 Jul 2001

I am becoming convinced that Unicode is a multi-national plot to take over the
minds of our most gifted (and/or most obsessive) programmers, in pursuit of an
elusive, unresolvable, and ultimately, undefinable goal.
      -- Ken Manheimer, 19 Jul 2001

Unicode is the first technology I have to deal with which makes me hope I die
before I really really *really* need to understand it fully.
      -- David Ascher, 19 Jul 2001

Moore's law is slowly making type declarations irrelevant...
      -- Paul Prescod, 29 Jul 2001

The mark of a mature programmer is willingness to throw out code you spent time
on when you realize it's pointless.
      -- Bram Cohen, 20 Sep 2001

Generators and iterators are among the most loving features ever introduced.
They will give and give, without ever asking anything from you save the
privilege of gracing your code, waiting with eager anticipation for you to
resume them at your pleasure, or even to discard them if you tire of their
charms. In fact, they're almost pathologically yielding.
      -- Tim Peters, 18 Oct 2001

IMO a bunch of the frustration I sometimes feel with Python comes from its
originally being intended as a "glue" language. It's too good for that, and
finds itself used as a work horse or even a race horse. Neither type of horse
belongs in the glue factory ;-).
      -- Paul Rubin, 30 Oct 2001

    "Which inevitably has the followup rhyme 'There was a young man from
Verdun'."
    "But somehow no one ever seems to be able to remember what it was about the
man from Abdero."
      -- Simon Callan and Gareth McCaughan, 04 Nov 2001, after someone
         quoted the limerick "There was a young man from Wooloomooloo /
         Whose limericks always finished on line two."

Sometimes I feel like I'm reinventing Zope, but at least it's a Zope I
understand.
      -- Quinn Dunkan, 05 Nov 2001 on the quixote-users list

Homological algebra beckons -- brain relief in this context!
      -- Michael Hudson, 07 Nov 2001, in a discussion of Stackless Python

If you're talking "useful", I'm not your bot.
      -- Tim Peters, 08 Nov 2001

    "How do you do a range of floats?"
    "Bring flowers, and buy them all nice dinners. Try not to be *too* obvious
that you're out to do them, though."
      -- Thomas Wouters and Tim Peters, 09 Nov 2001

Changing diapers reminded Guido that he wanted to allow for some measure of
multiple inheritance from a mix of new- and classic-style classes.
      -- Tim Peters in a checkin message, 14 Nov 2001

My late father-in-law, Ray Pigozzi, was an extremely talented architect (he was
made a fellow of the AIA in the late 70's or early 80's), and although he was
by all accounts an excellent mentor to younger architects in the firm he
cofounded, he also had the well- deserved reputation of being quite laconic
(this I know from personal experience ;-). Early in his career, he received an
award from some masonry organization for his use of brick in building OWP (now
OWP&P) had designed. This necessitated the usual awards ceremony with dinner
and speeches. The recipients who preceeded Ray to the podium all spoke at
length about their work. Ray's entire acceptance speech was, "The building
speaks for itself."
      -- Skip Montanaro, 4 Jan 2002

The Lisp community is like a ghost town, with the occasional banshee howl
echoing darkly around the chamber in lament of what might have been.
      -- Courageous, 19 Jan 2002

I'll lend you _Calendrical Calculations_. Even *skimming* the chapters on some
of the world's other calendrical delights makes our date gimmicks blind via the
intensity of their clarity.
      -- Tim Peters, 05 Mar 2002

The joy of coding Python should be in seeing short, concise, readable classes
that express a lot of action in a small amount of clear code -- not in reams of
trivial code that bores the reader to death.
      -- GvR, 20 Mar 2002

A bot may injure a human being, or, preferably, through inaction, allow a human
being to come to harm, although laughing about either in the hearing of humans
is MACNAM-017B3^H.
      -- Tim Peters, 26 Mar 2002

"It works in Scheme" doesn't give me the warm fuzzy feeling that it's been
tried in real life.
      -- GvR, 02 Oct 2002

Most recipes are short enough for the attention span of the average Python
programmer.
      -- GvR, in the introduction to the _Python Cookbook_

We read Knuth so you don't have to.
      -- Tim Peters, _Python Cookbook_

Here's another technique that is faster and more obvious but that is often
avoided by those who mistakenly believe that writing two lines of code where
one might do is somehow sinful.
      -- Tim Peters, _Python Cookbook_

A fruitful approach to problem solving is known as "divide and conquer", or
making problems easier by splitting their different aspects apart. Making
problems harder by joining several aspects together must be an example of an
approach known as "unite and suffer!"
      -- Alex Martelli, _Python Cookbook_

compromise-is-the-art-of-spreading-misery-ly y'rs
      -- Tim Peters, 11 Dec 2002

As for Grail, it was certainly a "hot product" in the Python community in 1995
because of the restricted execution environment which I evaluated for a project
involving mobile software agents. How priorities and trends have changed since
then! Who would have thought that Microsoft Outlook would be the premier
platform for mobile code?
      -- Paul Boddie, 16 Jan 2004

    I mean, if I think about my open-source contributions, nobody wants to see
talks with these titles:
    * The Zope API Reference: Ouch
    * A Random Handful Of Bugs I've Fixed In Other Peoples' Code
    * An Old Crufty Project I Inherited That Has Zero Relevance To You
    * The Joy of Preemptive Abandonware: Release Late, If Ever (or, Software
Design as a Nihilistic Abstract Art Form) (or, Sourceforge as a Medium for
Cryptic Time Capsules)
      -- Paul Winkler, 14 Mar 2005

Syntax should not look like grit on my monitor.
      -- Anthony Baxter, 02 Jun 2005

Can this not be resolved by carefully adjusting the order of finalization? If
code can be bootstrapped it can be strootbapped.
      -- Kristján Jónsson, 30 Jun 2006

Python resembles Lisp like an octopus eye resembles a mammalian eye: they have
lots in common because they're both pretty good solutions to similar problems.
Deciding whether it's Python or Lisp that has the retina fitted back-to-front
is left as an exercise for the reader.
      -- Gareth McCaughan, 11 Jul 2006

As Neal said, we are not perfect; bugs happen. If we all gave up on a piece of
software after two bugs we would not be able to turn our computers.
      -- Brett Cannon, 13 Jul 2006

... I've come to believe that some people have the personality traits that let
them tolerate redoing the same work over and over again for no reason other
than management "furniture rearranging", whereas others start to resent having
their (working) life repeatedly flashed before their eyes, but in slightly
different colours, over a longer period of time.
      -- Paul Boddie, 29 Aug 2006

    I am the very model of a modern major database,
     For gigabytes of information gathered out in userspace.
     For banking applications to a website crackers will deface,
     You access me from console or spiffy user interface.
    My multi-threaded architecture offers you concurrency,
     And loads of RAM for caching things reduces query latency.
     The data is correctly typed, a fact that I will guarantee,
     Each datum has a data type, it's specified explicitly.
      -- Tim Chase, 12 Sep 2006

t/data/simpsons_quotes.csv  view on Meta::CPAN

"Quote","Attribution Name","Attribution Source","Category","Rating"
"I hope this has taught you kids a lesson: kids never learn.","Chief Wiggum","The Simpsons","Humor",9
"Sideshow Bob has no decency.  He called me Chief Piggum. (laughs) Oh wait, I get it, he's all right.","Chief Wiggum","The Simpsons","Humor",8
"Can't you people take the law into your own hands? I mean, we can't be policing the entire city!","Chief Wiggum","The Simpsons","Humor",7
"She didn't reckon with the awesome power of the Chief of Police. Now where did I put my badge? Hey, that duck's got it.","Chief Wiggum","The Simpsons","Humor",4
"When I grow up I'm going to Bovine University.","Ralph Wiggum","The Simpsons","Humor",7
"Oh boy! Sleep! That's when I'm a Viking!","Ralph Wiggum","The Simpsons","Humor",9.4
"When i grow up, I want to be a principal or a caterpillar.","Ralph Wiggum","The Simpsons","Humor",9.1
"I bent my wookie.","Ralph Wiggum","The Simpsons","Humor",10
"Prinskipper Skippel... Primdable Skimpsker... I found something!","Ralph Wiggum","The Simpsons","Humor",10
"I ate the blue ones ... they taste like burning.","Ralph Wiggum","The Simpsons","Humor",9.5
"Principal Skinner, I got car sick in your office.","Ralph Wiggum","The Simpsons","Humor",9.3
"Hi, Super Nintendo Chalmers!","Ralph Wiggum","The Simpsons","Humor",10
"Me fail English? That's unpossible.","Ralph Wiggum","The Simpsons","Humor",8.7
"My cat's breath smells like cat food.","Ralph Wiggum","The Simpsons","Humor",9.8
"I dropped my popstickle in your toy chest","Ralph Wiggum","The Simpsons","Humor",4
"Milhouse: Why does Bart have a comic book? Comic Book Guy: Your questions have become more redundant and annoying then the last three ""Highlander"" movies.","Comic Book Guy","The Simpsons","Humor",7
"But, Aquaman, you cannot marry a woman without gills. You're from two different worlds... Oh, I've wasted my life.","Comic Book Guy","The Simpsons","Humor",4
"Bart: It's valuable, huh?! Comic Book Guy: Ooh, your powers of deduction are exceptional. I can't allow you to waste them here when there are so many crimes going unsolved at this very moment. Go, go, for the good of the city.","Comic Book Guy","The...
"Last night's 'Itchy and Scratchy Show' was, without a doubt, the worst episode ever. Rest assured, I was on the internet within minutes, registering my disgust throughout the world.","Comic Book Guy","The Simpsons","Humor",5
"Stop right there! I have the only working fazer ever built. It was fired only once to keep William Shatner from making another album.","Comic Book Guy","The Simpsons","Humor",7
"The Internet King? I wonder if he could provide faster nudity... .","Comic Book Guy","The Simpsons","Humor",4.3
"Oh, loneliness and cheeseburgers are a dangerous mix.","Comic Book Guy","The Simpsons","Humor",3
"That is a rare photo of Sean Connery signed by Roger Moore.","Comic Book Guy","The Simpsons","Humor",4.7
"Hello. I am not interested in buying your house, but I would like to use your rest room, flip through your magazines, rearrange your carefully shelved items and handle your food products in an unsanitary manner. Ha! Now you know how it feels!)","Apu...
"Hey, hey, this is not a lending library. If you're not going to buy that thing put it down or I'll blow your heads off","Apu Nahasapemapetilon","The Simpsons","Humor",6
"Shiva H. Vishnu!","Apu Nahasapemapetilon","The Simpsons","Humor",6
"Big deal! When I was a pup, we got spanked by presidents 'til the cows came home! Grover Cleveland spanked me on two non-consecutive occasions!","Grandpa Simpson","The Simpsons","Humor",6
"My Homer is not a communist.  He may be a liar, a pig, an idiot, a communist, but he is not a porn star.","Grandpa Simpson","The Simpsons","Humor",9
"Dear Mr. President, There are too many states nowadays. Please eliminate three. P.S. I am not a crackpot.","Grandpa Simpson","The Simpsons","Humor",9.5

t/data/simpsons_quotes.tsv.csv  view on Meta::CPAN

Quote	Attribution Name	Attribution Source	Category	Rating
I hope this has taught you kids a lesson: kids never learn.	Chief Wiggum			
Sideshow Bob has no decency.  He called me Chief Piggum. (laughs) Oh wait, I get it, he's all right.	Chief Wiggum			
Can't you people take the law into your own hands? I mean, we can't be policing the entire city!	Chief Wiggum			
She didn't reckon with the awesome power of the Chief of Police. Now where did I put my badge? Hey, that duck's got it.	Chief Wiggum			
When I grow up I'm going to Bovine University.	Ralph Wiggum			
Oh boy! Sleep! That's when I'm a Viking!	Ralph Wiggum			
When i grow up, I want to be a principal or a caterpillar.	Ralph Wiggum			
I bent my wookie.	Ralph Wiggum			
Prinskipper Skippel... Primdable Skimpsker... I found something!	Ralph Wiggum			
I ate the blue ones ... they taste like burning.	Ralph Wiggum			
Principal Skinner, I got car sick in your office.	Ralph Wiggum			
Hi, Super Nintendo Chalmers!	Ralph Wiggum			
Me fail English? That's unpossible.	Ralph Wiggum			
My cat's breath smells like cat food.	Ralph Wiggum			
I dropped my popstickle in your toy chest	Ralph Wiggum			
Milhouse: Why does Bart have a comic book? Comic Book Guy: Your questions have become more redundant and annoying then the last three 'Highlander' movies.	Comic Book Guy			
But, Aquaman, you cannot marry a woman without gills. You're from two different worlds... Oh, I've wasted my life.	Comic Book Guy			
Bart: It's valuable, huh?! Comic Book Guy: Ooh, your powers of deduction are exceptional. I can't allow you to waste them here when there are so many crimes going unsolved at this very moment. Go, go, for the good of the city.	Comic Book Guy			
Last night's 'Itchy and Scratchy Show' was, without a doubt, the worst episode ever. Rest assured, I was on the internet within minutes, registering my disgust throughout the world.	Comic Book Guy			
Stop right there! I have the only working fazer ever built. It was fired only once to keep William Shatner from making another album.	Comic Book Guy			
The Internet King? I wonder if he could provide faster nudity... .	Comic Book Guy			
Oh, loneliness and cheeseburgers are a dangerous mix.	Comic Book Guy			
That is a rare photo of Sean Connery signed by Roger Moore.	Comic Book Guy			
Hello. I am not interested in buying your house, but I would like to use your rest room, flip through your magazines, rearrange your carefully shelved items and handle your food products in an unsanitary manner. Ha! Now you know how it feels!)	Apu Na...
Hey, hey, this is not a lending library. If you're not going to buy that thing put it down or I'll blow your heads off	Apu Nahasapemapetilon			
Shiva H. Vishnu!	Apu Nahasapemapetilon			
Big deal! When I was a pup, we got spanked by presidents 'til the cows came home! Grover Cleveland spanked me on two non-consecutive occasions!	Grandpa Simpson			
My Homer is not a communist.  He may be a liar, a pig, an idiot, a communist, but he is not a porn star.	Grandpa Simpson			
Dear Mr. President, There are too many states nowadays. Please eliminate three. P.S. I am not a crackpot.	Grandpa Simpson			

t/data/utf8.csv  view on Meta::CPAN

"Quote"	"Attribution Name"	"Attribution Source"	"Category"	"Rating"
"¥ · £ · € · $ · ¢ · ₡ · ₢ · ₣ · ₤ · ₥ · ₦ · ₧ · ₨ · ₩ · ₪ · ₫ · ₭ · ₮ · ₯"	"UTF-8 Sampler Currency"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10
"我能吞下玻璃而不伤身体。"	"I can eat grass (Chinese)"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10
"私はガラスを食べられます。それは私を傷つけません。"	"I can eat grass (Japanese)"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10
"나는 유리를 먹을 수 있어요. 그래도 아프지 않아요"	"I can eat grass (Korean)"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10
"Tsésǫʼ yishą́ągo bííníshghah dóó doo shił neezgai da. "	"I can eat grass (Navajo)"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10
"Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα."	"I can eat grass (Greek)"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10
"मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती."	"I can eat grass (Hindi)"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10
"אני יכול לאכול זכוכית וזה לא מזיק לי"	"I can eat grass (Hebrew)"	"http://www.columbia.edu/kermit/utf8.html"	"Educational"	10

t/data/www.amk.ca/quotations/python-quotes/index.html  view on Meta::CPAN

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head profile="http://www.amk.ca/foaf/author">
<meta name="generator" content=
"HTML Tidy for Mac OS X (vers 1 September 2005), see www.w3.org" />
<link rel="stylesheet" href="/base.css" type="text/css" />
<meta name="ICBM" content="38.87510,-77.28705" />
<meta name="geo.position" content="38.87510; -77.28705" />
<meta name="DC.title" content="Python Quotes, page 1 of 10" />
<meta name="FOAF:sha1sum_mbox" content=
"2ddc453144de22607a168e585c487967d035cd27" />
<title>Python Quotes, page 1 of 10</title>
<meta name="MSSmartTagsPreventParsing" content="TRUE" />
<link rel="Next" href="page-2" />
<meta name="keywords" content=
"quotations, quotes, Python Quotes, open source, free software, python, software engineering, software development" />
<link rel="openid.server" href=
"http://www.livejournal.com/openid/server.bml" />
<link rel="openid.delegate" href=
"http://akuchling.livejournal.com/" />
</head>
<body>
<div class="navbar"><a href="/">Home</a> &gt;&nbsp;<a href=
"/quotations/">Quotations</a> &gt;&nbsp;
<h1 class="title">Python Quotes, page 1 of 10</h1>
<div style="text-align:right">[<a href="/search">Search
amk.ca</a>]<br />
This page last modified: 30 Sep 2006</div>
</div>
<div class="sidebar">
<p><span class="link-heading">Other Software Quotes:</span>
<a class="sidebar-link" href="index">1</a> | <a class=
"sidebar-link" href="page-2">2</a> | <a class="sidebar-link" href=
"page-3">3</a> | <a class="sidebar-link" href="page-4">4</a> |
<a class="sidebar-link" href="page-5">5</a> | <a class=
"sidebar-link" href="page-6">6</a> | <a class="sidebar-link" href=
"page-7">7</a> | <a class="sidebar-link" href="page-8">8</a> |
<a class="sidebar-link" href="page-9">9</a> | <a class=
"sidebar-link" href="page-10">10</a><br />
<span class="link-heading">Other&nbsp;Formats:</span> <a class=
"sidebar-link" href="../python-quotes.txt">ASCII</a> | <a class=
"sidebar-link" href="../python-quotes.ft">Fortune</a> | <a class=
"sidebar-link" href="../python-quotes.xml">XML</a><br />
<span class="link-heading">Other&nbsp;Collections:</span> <a class=
"sidebar-link" href="../">Index</a> | <a class="sidebar-link" href=
"../comics/">Comics</a> | <a class="sidebar-link" href=
"../cryptography/">Cryptography</a> | <a class="sidebar-link" href=
"../quotations/">Commonplace&nbsp;book</a> | <a class=
"sidebar-link" href="../doctor-who/">Doctor&nbsp;Who</a> |
<a class="sidebar-link" href="../neil-gaiman/">Neil&nbsp;Gaiman</a>
| <a class="sidebar-link" href=
"../peter-greenaway/">Peter&nbsp;Greenaway</a> | <a class=
"sidebar-link" href="../python-quotes/">Python</a> | <a class=
"sidebar-link" href=
"../robertson-davies/">Robertson&nbsp;Davies</a> | <a class=
"sidebar-link" href="../sherlock-holmes/">Sherlock&nbsp;Holmes</a>
| <a class="sidebar-link" href=
"../tom-baker/">Tom&nbsp;Baker</a></p>
</div>
<div class="content">
<hr />
<p class='quotation' id='q1'>We will perhaps eventually be writing
only small modules which are identified by name as they are used to
build larger ones, so that devices like indentation, rather than
delimiters, might become feasible for expressing local structure in
the source language.</p>
<p class='source'>Donald E. Knuth, "Structured Programming with
goto Statements", Computing Surveys, Vol 6 No 4, Dec. 1974</p>
<p class='quotation' id='q315'>Python's syntax succeeds in
combining the mistakes of Lisp and Fortran. I do not construe that
as progress.</p>
<p class='source'>Larry Wall, May 12 2004</p>
<p class='quotation' id='q2'>Some rejected alternate names for
"Monty Python's Flying Circus": 1 2 3 / It's Them! / Arthur Megapode's Flying
Circus / The Horrible Earnest Megapode / The Panic Show / The
Plastic Mac Show / Ow! It's Colin Plint! / Vaseline Review /
Vaseline Parade / The Keen Show / Brian's Flying Circus / The Year
of the Stoat / Cynthia Fellatio's Flying Circus / Owl Stretching
Time / The Whizzo Easishow! (Guaranteed to last 1/2 hour! Money
back if not!)</p>
<p class='source'>From Kim "Howard" Johnson's <cite>Life Before and
After Monty Python</cite>. It's interesting to contemplate what
Python would have been called if one of these names had been
chosen.</p>
<p class='quotation' id='q3'>Anybody else on the list got an
opinion? Should I change the language or not?</p>
<p class='source'>Guido van Rossum, 28 Dec 1991</p>
<p class='quotation' id='q4'>
in-any-case-the-best-christmas-present-i-got-today!-ly y'rs -
tim</p>
<p class='source'>Tim Peters, 29 Dec 1991 [First occurrence of Tim
Peters's long-phrase-ly idiom.]</p>
<p class='quotation' id='q5'>
but-i'm-not-even-motivated-enough-to-finish-this-sig-</p>
<p class='source'>Tim Peters, 20 Dec 2000</p>
<p class='quotation' id='q6'>Ha -- you have done me the favor of
underestimating my ignorance &lt;smile&gt;.</p>
<p class='source'>Tim Peters, 30 Dec 1991</p>
<p class='quotation' id='q7'>I prefer (all things being equal)
regularity/orthogonality and logical syntax/semantics in a language
because there is less to have to remember. (Of course I
<em>know</em> all things are NEVER really equal!)</p>
<p class='source'>Guido van Rossum, 6 Dec 1991</p>
<p class='quotation' id='q8'>The details of that silly code are
irrelevant.</p>
<p class='source'>Tim Peters, 4 Mar 1992</p>
<p class='quotation' id='q9'>Frankly, I'd rather not try to compete
with Perl in the areas where Perl is best -- it's a battle that's
impossible to win, and I don't think it is a good idea to strive
for the number of obscure options and shortcuts that Perl has
acquired through the years.</p>
<p class='source'>Guido van Rossum, 7 Jul 1992</p>
<p class='quotation' id='q10'>Python is a truly wonderful language.
When somebody comes up with a good idea it takes about 1 minute and
five lines to program something that almost does what you want.
Then it takes only an hour to extend the script to 300 lines, after
which it still does almost what you want.</p>
<p class='source'>Jack Jansen, 8 Jul 1992</p>
<p class='quotation' id='q11'>If you have a browser from CERN's WWW
project (World-Wide Web, a distributed hypertext system) you can
browse a WWW hypertext version of the manual...</p>
<p class='source'>Guido van Rossum, 19 Nov 1992 [First mention of
the Web on python-list.]</p>
<p class='quotation' id='q12'>Just a success note for Guido and the
list: Python 0.9.9, stdwin, readline, gmp, and md5 all go up on
linux 0.99 pl11 without much problems.</p>
<p class='source'>Allan Bailey, 2 Aug 1993 [First mention of Linux
on python-list.]</p>
<p class='quotation' id='q13'>Rule: "You shouldn't have to open up
a black box and take it apart to find out you've been pushing the
wrong buttons!" Corollary: "Every black box should have at least
TWO blinking lights: "Paper Jam" and "Service Required" (or
equivalent)."</p>
<p class='source'>Steven D. Majewski, 9 Sep 1993</p>
<p class='quotation' id='q14'>We've been through a couple of syntax
changes, but I have sort of assumed that by the time we get to
version 1.0 release, the language, (if not the implementation) will
essentially be stable.</p>
<p class='source'>Steven D. Majewski, 14 Sep 1993</p>
<p class='quotation' id='q15'>"Python tricks" is a tough one, cuz
the language is so clean. E.g., C makes an art of confusing
pointers with arrays and strings, which leads to lotsa neat pointer
tricks; APL mistakes everything for an array, leading to neat
one-liners; and Perl confuses everything period, making each line a
joyous adventure &lt;wink&gt;.</p>
<p class='source'>Tim Peters, 16 Sep 1993</p>
<p class='quotation' id='q16'>I've seen Python criticized as "ugly"
precisely because it <em>doesn't</em> have a trick-based view of
the world. In many ways, it's a dull language, borrowing solid old
concepts from many other languages &amp; styles: boring syntax,
unsurprising semantics, few automatic coercions, etc etc. But
that's one of the things I like about it.</p>
<p class='source'>Tim Peters, 16 Sep 1993</p>
<p class='quotation' id='q17'>One of the things that makes it
interesting, is exactly how much Guido has managed to exploit that
<em>one</em> implementation trick of 'namespaces'.</p>
<p class='source'>Steven D. Majewski, 17 Sep 1993</p>
<p class='quotation' id='q18'>Anyone familiar with Modula-3 should
appreciate the difference between a layered approach, with generic
Rd/Wr types, and the Python 'C with foam padding' approach.</p>
<p class='source'>John Redford, 24 Nov 1993</p>
<p class='quotation' id='q19'>People simply will not agree on what
should and shouldn't be "an error", and once exception-handling
mechanisms are introduced to give people a choice, they will far
less agree on what to do with them.</p>
<p class='source'>Tim Peters, 17 Dec 1993</p>
<p class='quotation' id='q20'>Note that because of its semantics,
'del' <em>can't</em> be a function: "del a" deletes 'a' from the
current namespace. A function can't delete something from the
calling namespace (except when written by Steve Majewski :-).</p>
<p class='source'>Guido van Rossum, 1 Aug 1994</p>
<p class='quotation' id='q21'>I don't know a lot about this
artificial life stuff -- but I'm suspicious of anything Newsweek
gets goofy about -- and I suspect its primary use is as another
money extraction tool to be applied by ai labs to the department of
defense (and more power to 'em). Nevertheless in wondering why free software is
so good these days it occurred to me that the propagation of free
software is one gigantic artificial life evolution experiment, but
the metaphor isn't perfect. Programs are thrown out into the harsh
environment, and the bad ones die. The good ones adapt rapidly and
become very robust in short order. The only problem with the metaphor is that the
process isn't random at all. Python <em>chooses</em> to include
Tk's genes; Linux decides to make itself more suitable for
symbiosis with X, etcetera. Free software is artificial life, but
better.</p>
<p class='source'>Aaron Watters, 29 Sep 1994</p>
<p class='quotation' id='q22'>I claim complete innocence and
ignorance! It must have been Tim. I wouldn't know a Trondheim
Hammer if it fell on my foot!</p>
<p class='source'>Steve Majewski, 10 Jan 1995</p>
<p class='quotation' id='q23'>(Aieee! Yet another thing on my TODO
pile!)</p>
<p class='source'>A.M. Kuchling, 10 Jan 1995</p>
<p class='quotation' id='q24'>[After someone wrote "...assignment
capability, a la djikstra"] Ehh, the poor old man's name is
Dijkstra. I should know, "ij" is a well known digraph in the Dutch
language. And before someone asks the obvious: his famous "P and V"
names for semaphores are derived for the Dutch words "Passeer" and
"Verlaat", or "Pass" and "Leave". And no, I haven't met him
(although he did work at CWI back in the fifties when it was
called, as it should still be today, Mathematical Centre). he
currently lives in Austin, Texas I believe. (While we're at it...
does anybody remember the Dijkstra font for Macintoshes? It was a
scanned version of his handwriting. I believe Luca Cardelli scanned
it -- the author of Obliq, a somewhat Python-like distributed
language built on Modula-3. I could go on forever... :-)</p>
<p class='source'>Guido van Rossum, 19 Jan 1995</p>
<p class='quotation' id='q25'>As always, I'll leave it to a
volunteer to experiment with this.</p>
<p class='source'>Guido van Rossum, 20 Jan 1995</p>
<p class='quotation' id='q26'>Non-masochists, please delete this
article NOW.</p>
<p class='source'>Aaron Watters, 20 Jan 1995</p>
<p class='quotation' id='q27'>If Perl weren't around, I'd probably
be using Python right now.</p>
<p class='source'>Tom Christiansen, in comp.lang.perl 2 Jun
1995</p>
<p class='quotation' id='q28'>GUI stuff is <em>supposed</em> to be
hard. It builds character.</p>
<p class='source'>Jim Ahlstrom, at one of the early Python
workshops</p>
<p class='quotation' id='q29'>&gt;VERY cool mod, Peter. I'll be
curious to see GvR's reaction to your syntax. Hm.</p>
<p class='source'>Nick Seidenman and Guido van Rossum, 1 Aug
1996</p>
<p class='quotation' id='q30'>Python is an experiment in how much
freedom programmers need. Too much freedom and nobody can read
another's code; too little and expressiveness is endangered.</p>
<p class='source'>Guido van Rossum, 13 Aug 1996</p>
<p class='quotation' id='q31'>[On regression testing] Another
approach is to renounce all worldly goods and retreat to a
primitive cabin in Montana, where you can live a life of purity,
unpolluted by technological change. But now and then you can send
out little packages....</p>
<p class='source'>Aaron Watters</p>
<p class='quotation' id='q32'>Ah, you're a recent victim of
forceful evangelization. Write your own assert module, use it, and
come back in a few months to tell me whether it really caught 90%
of your bugs.</p>
<p class='source'>Guido van Rossum, 7 Feb 1997</p>
<p class='quotation' id='q33'>The larger scientific computing
centers generally have a "theory" division and a "actually uses the
computer" &lt;wink&gt; division. The theory division generally
boasts some excellent theoreticians and designers, while the other
division generally boasts some excellent physical scientists who
simply want to get their work done. In most labs I've seen, the two
divisions hate each others' guts (or, rarely, blissfully ignore
each other), &amp; the politics is so thick you float on it even
after they embed your feet in cement blocks (hence even the simple
relief of death is denied you &lt;wink&gt;).</p>
<p class='source'>Tim Peters, 25 Mar 1997</p>
<p class='quotation' id='q34'>In one particular way the conflict is
fundamental &amp; eternal: the "working scientists" generally
understand the hardware du jour perfectly, and passionately resent
any attempt to prevent them from fiddling with it directly -- while
the theory folks are forever inventing new ways to hide the
hardware du jour. That two groups can both be so right and so wrong
at the same time is my seventh proof for the existence of God
...</p>
<p class='source'>Tim Peters, 25 Mar 1997</p>
<p class='quotation' id='q35'>You're going to be in a minority -
you're coming to Python programming from a language which offers
you a lot more in the way of comfortable operations than Python,
instead of coming from medieval torture chambers like C or Fortran,
which offer so much less.</p>
<p class='source'>Andrew Mullhaupt, 26 Jun 1997</p>
<p class='quotation' id='q36'>...although Python uses an obsolete
approach to memory management, it is a <em>good</em> implementation
of that approach, as opposed to S, which uses a combination of bad
implementation and demented design decisions to arrive at what may
very well be the worst memory behavior of any actually useful
program.</p>
<p class='source'>Andrew Mullhaupt, 26 Jun 1997</p>
<hr />
<p><!-- Creative Commons License -->
<a rel="license" href=
"http://creativecommons.org/licenses/by-sa/2.0/"><img alt=
"Creative Commons License" border="0" src=
"http://creativecommons.org/images/public/somerights20.gif" /></a><br />

This work is licensed under a <a rel="license" href=
"http://creativecommons.org/licenses/by-sa/2.0/">Creative Commons
License</a>. <!-- /Creative Commons License -->
<!--
<rdf:RDF xmlns="http://web.resource.org/cc/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Work rdf:about="">
   <dc:type rdf:resource="http://purl.org/dc/dcmitype/Text" />
   <license rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
</Work>

<License rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
   <permits rdf:resource="http://web.resource.org/cc/Reproduction" />
   <permits rdf:resource="http://web.resource.org/cc/Distribution" />
   <requires rdf:resource="http://web.resource.org/cc/Notice" />
   <requires rdf:resource="http://web.resource.org/cc/Attribution" />
   <permits rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
   <requires rdf:resource="http://web.resource.org/cc/ShareAlike" />
</License>
</rdf:RDF>
--></p>
<hr /></div>
<small>[<a href="mailto:comments@amk.ca">Contact me</a>]</small>
</body>
</html>

t/data/www.amk.ca/quotations/python-quotes/page-10.html  view on Meta::CPAN

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head profile="http://www.amk.ca/foaf/author">
<meta name="generator" content=
"HTML Tidy for Mac OS X (vers 1 September 2005), see www.w3.org" />
<link rel="stylesheet" href="/base.css" type="text/css" />
<meta name="ICBM" content="38.87510,-77.28705" />
<meta name="geo.position" content="38.87510; -77.28705" />
<meta name="DC.title" content="Python Quotes, page 10 of 10" />
<meta name="FOAF:sha1sum_mbox" content=
"2ddc453144de22607a168e585c487967d035cd27" />
<title>Python Quotes, page 10 of 10</title>
<meta name="MSSmartTagsPreventParsing" content="TRUE" />
<link rel="Prev" href="page-9" />
<link rel="openid.server" href=
"http://www.livejournal.com/openid/server.bml" />
<link rel="openid.delegate" href=
"http://akuchling.livejournal.com/" />
</head>
<body>
<div class="navbar"><a href="/">Home</a> &gt;&nbsp;<a href=
"/quotations/">Quotations</a> &gt;&nbsp;<a href=
"/quotations/python-quotes/">Python Quotations</a> &gt;&nbsp;
<h1 class="title">Python Quotes, page 10 of 10</h1>
<div style="text-align:right">[<a href="/search">Search
amk.ca</a>]<br />
This page last modified: 30 Sep 2006</div>
</div>
<div class="sidebar">
<p><span class="link-heading">Other Software Quotes:</span>
<a class="sidebar-link" href="index">1</a> | <a class=
"sidebar-link" href="page-2">2</a> | <a class="sidebar-link" href=
"page-3">3</a> | <a class="sidebar-link" href="page-4">4</a> |
<a class="sidebar-link" href="page-5">5</a> | <a class=
"sidebar-link" href="page-6">6</a> | <a class="sidebar-link" href=
"page-7">7</a> | <a class="sidebar-link" href="page-8">8</a> |
<a class="sidebar-link" href="page-9">9</a> | <a class=
"sidebar-link" href="page-10">10</a><br />
<span class="link-heading">Other&nbsp;Formats:</span> <a class=
"sidebar-link" href="../python-quotes.txt">ASCII</a> | <a class=
"sidebar-link" href="../python-quotes.ft">Fortune</a> | <a class=
"sidebar-link" href="../python-quotes.xml">XML</a><br />
<span class="link-heading">Other&nbsp;Collections:</span> <a class=
"sidebar-link" href="../">Index</a> | <a class="sidebar-link" href=
"../comics/">Comics</a> | <a class="sidebar-link" href=
"../cryptography/">Cryptography</a> | <a class="sidebar-link" href=
"../quotations/">Commonplace&nbsp;book</a> | <a class=
"sidebar-link" href="../doctor-who/">Doctor&nbsp;Who</a> |
<a class="sidebar-link" href="../neil-gaiman/">Neil&nbsp;Gaiman</a>
| <a class="sidebar-link" href=
"../peter-greenaway/">Peter&nbsp;Greenaway</a> | <a class=
"sidebar-link" href="../python-quotes/">Python</a> | <a class=
"sidebar-link" href=
"../robertson-davies/">Robertson&nbsp;Davies</a> | <a class=
"sidebar-link" href="../sherlock-holmes/">Sherlock&nbsp;Holmes</a>
| <a class="sidebar-link" href=
"../tom-baker/">Tom&nbsp;Baker</a></p>
</div>
<div class="content">
<hr />
<p class='quotation' id='q312'>The joy of coding Python should be
in seeing short, concise, readable classes that express a lot of
action in a small amount of clear code -- not in reams of trivial
code that bores the reader to death.</p>
<p class='source'>GvR, 20 Mar 2002</p>
<p class='quotation' id='q313'>A bot may injure a human being, or,
preferably, through inaction, allow a human being to come to harm,
although laughing about either in the hearing of humans is
MACNAM-017B3^H.</p>
<p class='source'>Tim Peters, 26 Mar 2002</p>
<p class='quotation' id='q314'>"It works in Scheme" doesn't give me
the warm fuzzy feeling that it's been tried in real life.</p>
<p class='source'>GvR, 02 Oct 2002</p>
<p class='quotation'>Most recipes are short enough for the
attention span of the average Python programmer.</p>
<p class='source'>GvR, In the introduction to the <cite>Python
Cookbook</cite></p>
<p class='quotation'>We read Knuth so you don't have to.</p>
<p class='source'>Tim Peters<cite>Python Cookbook</cite></p>
<p class='quotation'>Here's another technique that is faster and
more obvious but that is often avoided by those who mistakenly
believe that writing two lines of code where one might do is
somehow sinful.</p>
<p class='source'>Tim Peters<cite>Python Cookbook</cite></p>
<p class='quotation'>A fruitful approach to problem solving is
known as "divide and conquer", or making problems easier by
splitting their different aspects apart. Making problems harder by
joining several aspects together must be an example of an approach
known as "unite and suffer!"</p>
<p class='source'>Alex Martelli<cite>Python Cookbook</cite></p>
<p class='quotation' id='q313'>
compromise-is-the-art-of-spreading-misery-ly y'rs</p>
<p class='source'>Tim Peters, 11 Dec 2002</p>
<p class='quotation' id='q314'>As for Grail, it was certainly a
"hot product" in the Python community in 1995 because of the
restricted execution environment which I evaluated for a project
involving mobile software agents. How priorities and trends have
changed since then! Who would have thought that Microsoft Outlook
would be the premier platform for mobile code?</p>
<p class='source'>Paul Boddie, 16 Jan 2004</p>
<p class='quotation' id='q314'>I mean, if I think about my
open-source contributions, nobody wants to see talks with these
titles:
<p class='quotation'>* The Zope API Reference: Ouch
* A Random Handful Of Bugs I've Fixed In Other Peoples' Code
* An Old Crufty Project I Inherited That Has Zero Relevance To You
* The Joy of Preemptive Abandonware: Release Late, If Ever 
(or, Software Design as a Nihilistic Abstract Art
Form) (or, Sourceforge as a Medium for Cryptic Time Capsules)</p>
<p class='source'>Paul Winkler, 14 Mar 2005</p>
<p class='quotation' id='q316'>Syntax should not look like grit on
my monitor.</p>
<p class='source'>Anthony Baxter, 02 Jun 2005</p>
<p class='quotation' id='q315'>Can this not be resolved by
carefully adjusting the order of finalization? If code can be
bootstrapped it can be strootbapped.</p>
<p class='source'>Kristján Jónsson, 30 Jun 2006</p>
<p class='quotation' id='q317'>Python resembles Lisp like an
octopus eye resembles a mammalian eye: they have lots in common
because they're both pretty good solutions to similar problems.
Deciding whether it's Python or Lisp that has the retina fitted
back-to-front is left as an exercise for the reader.</p>
<p class='source'>Gareth McCaughan, 11 Jul 2006</p>
<p class='quotation' id='q318'>As Neal said, we are not perfect;
bugs happen. If we all gave up on a piece of software after two
bugs we would not be able to turn our computers.</p>
<p class='source'>Brett Cannon, 13 Jul 2006</p>
<p class='quotation' id='q319'>... I've come to believe that some
people have the personality traits that let them tolerate redoing
the same work over and over again for no reason other than
management "furniture rearranging", whereas others start to resent
having their (working) life repeatedly flashed before their eyes,
but in slightly different colours, over a longer period of
time.</p>
<p class='source'>Paul Boddie, 29 Aug 2006</p>
<p class='quotation' id='q320'>I am the very model of a modern
major database,
For gigabytes of information gathered out in userspace.
For banking applications to a website crackers will deface,
You access me from console or spiffy user interface.
    My multi-threaded architecture offers you
concurrency, And loads of RAM for caching things reduces query latency.<br />
The data is correctly typed, a fact that I will guarantee,<br />
Each datum has a data type, it's specified explicitly.</p>
<p class='source'>Tim Chase, 12 Sep 2006</p>
<hr /></div>
<small>[<a href="mailto:comments@amk.ca">Contact me</a>]</small>
</body>
</html>

t/data/www.amk.ca/quotations/python-quotes/page-2.html  view on Meta::CPAN

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head profile="http://www.amk.ca/foaf/author">
<meta name="generator" content=
"HTML Tidy for Mac OS X (vers 1 September 2005), see www.w3.org" />
<link rel="stylesheet" href="/base.css" type="text/css" />
<meta name="ICBM" content="38.87510,-77.28705" />
<meta name="geo.position" content="38.87510; -77.28705" />
<meta name="DC.title" content="Python Quotes, page 2 of 10" />
<meta name="FOAF:sha1sum_mbox" content=
"2ddc453144de22607a168e585c487967d035cd27" />
<title>Python Quotes, page 2 of 10</title>
<meta name="MSSmartTagsPreventParsing" content="TRUE" />
<link rel="Prev" href="index" />
<link rel="Next" href="page-3" />
<link rel="openid.server" href=
"http://www.livejournal.com/openid/server.bml" />
<link rel="openid.delegate" href=
"http://akuchling.livejournal.com/" />
</head>
<body>
<div class="navbar"><a href="/">Home</a> &gt;&nbsp;<a href=
"/quotations/">Quotations</a> &gt;&nbsp;<a href=
"/quotations/python-quotes/">Python Quotations</a> &gt;&nbsp;
<h1 class="title">Python Quotes, page 2 of 10</h1>
<div style="text-align:right">[<a href="/search">Search
amk.ca</a>]<br />
This page last modified: 30 Sep 2006</div>
</div>
<div class="sidebar">
<p><span class="link-heading">Other Software Quotes:</span>
<a class="sidebar-link" href="index">1</a> | <a class=
"sidebar-link" href="page-2">2</a> | <a class="sidebar-link" href=
"page-3">3</a> | <a class="sidebar-link" href="page-4">4</a> |
<a class="sidebar-link" href="page-5">5</a> | <a class=
"sidebar-link" href="page-6">6</a> | <a class="sidebar-link" href=
"page-7">7</a> | <a class="sidebar-link" href="page-8">8</a> |
<a class="sidebar-link" href="page-9">9</a> | <a class=
"sidebar-link" href="page-10">10</a><br />
<span class="link-heading">Other&nbsp;Formats:</span> <a class=
"sidebar-link" href="../python-quotes.txt">ASCII</a> | <a class=
"sidebar-link" href="../python-quotes.ft">Fortune</a> | <a class=
"sidebar-link" href="../python-quotes.xml">XML</a><br />
<span class="link-heading">Other&nbsp;Collections:</span> <a class=
"sidebar-link" href="../">Index</a> | <a class="sidebar-link" href=
"../comics/">Comics</a> | <a class="sidebar-link" href=
"../cryptography/">Cryptography</a> | <a class="sidebar-link" href=
"../quotations/">Commonplace&nbsp;book</a> | <a class=
"sidebar-link" href="../doctor-who/">Doctor&nbsp;Who</a> |
<a class="sidebar-link" href="../neil-gaiman/">Neil&nbsp;Gaiman</a>
| <a class="sidebar-link" href=
"../peter-greenaway/">Peter&nbsp;Greenaway</a> | <a class=
"sidebar-link" href="../python-quotes/">Python</a> | <a class=
"sidebar-link" href=
"../robertson-davies/">Robertson&nbsp;Davies</a> | <a class=
"sidebar-link" href="../sherlock-holmes/">Sherlock&nbsp;Holmes</a>
| <a class="sidebar-link" href=
"../tom-baker/">Tom&nbsp;Baker</a></p>
</div>
<div class="content">
<hr />
<p class='quotation' id='q37'>I suggested holding a "Python Object
Oriented Programming Seminar", but the acronym was unpopular.</p>
<p class='source'>Joseph Strout, 28 Feb 1997</p>
<p class='quotation' id='q38'>Strangely enough I saw just such a
beast at the grocery store last night. Starbucks sells Javachip.
(It's ice cream, but that shouldn't be an obstacle for the Java
marketing people.)</p>
<p class='source'>Jeremy Hylton, 29 Apr 1997</p>
<p class='quotation' id='q39'>A little girl goes into a pet show
and asks for a wabbit. The shop keeper looks down at her, smiles
and says:
"Would you like a lovely fluffy little white
rabbit, or a cutesy wootesly little brown rabbit?"
"Actually", says the little girl, "I don't
think my python would notice."</p>
<p class='source'>Told by Nick Leaton, 4 Dec 1996</p>
<p class='quotation' id='q40'>When I originally designed Perl 5's
OO, I thought about a lot of this stuff, and chose the explicit
object model of Python as being the least confusing. So far I
haven't seen a good reason to change my mind on that.</p>
<p class='source'>Larry Wall, 27 Feb 1997 on perl5-porters</p>
<p class='quotation' id='q41'>
PSA 1996 Budget
---------------
Income:
$1,093,276.54  'Guido for President' 
                 Campaign Contributions(1)
$        3.12  Milk Money Extortion Program
$    2,934.07  PSA Memberships
-------------
$1,096,213.73  Total Income

Expenses:
$  652,362.55  Monty Python Licencing Fees (2)
$   10,876.45  Pre-Release 2 Week Vacations (3)
$  369,841.59  Post-Release 2 Week Vacations (3)
$       15.01  Alien Abduction Insurance
$   62,541.72  Python Web Site Maintenance
$      554.65  Great Comfort Cream
-------------
$1,096,191.97  Total Expenses
$      (21.76) Total Profit (Loss)
Notes:
(1) Many of you many not be aware of the
fabulously successful 'Guido for President' Campaign. While Guido
has no interest in being the president, the PSA thought it would be
a cool way to collect money. The centerpiece of the campaign
featured an attractive offer to spend the night in Guido's spare
bedroom in exchange for a $50,000.00 contribution. (Mark Lutz
stayed TWICE!)
(2) Since the proliferation of Monty Python
related names (Python, Monty, Grail, Eric-the-Half-a-Compiler, et
al.) has increased over the past year, the PSA felt it would be
wise to licencing the Python name to forestall any lawsuits. An
added benefit is that John Cleese is teaching Guido how to walk
funny.
(3) Pre-Release vacations are spent in the
Catskills. Post-Release vacations are spent in the Bahamas. Guido
is currently working on a system which will allow him to make more
releases of Python; thus octupling the number of vacations he takes
in a year.</p>
<p class='source'>Matthew Lewis Carroll Smith, 4 Apr 1997</p>
<p class='quotation' id='q42'>I mean, just take a look at Joe
Strout's brilliant little "python for beginners" page. Replace all
print-statements with <code>sys.stdout.write( string.join(map(str,
args)) + "\n")</code> and you surely won't get any new beginners.
And That Would Be A Very Bad Thing.</p>
<p class='source'>Fredrik Lundh, 27 Aug 1996</p>
<p class='quotation' id='q43'>Ya, ya, ya, except ... if I were
built out of KSR chips, I'd be running at 25 or 50 MHz, and would
be wrong about ALMOST EVERYTHING almost ALL THE TIME just due to
being a computer! Think about it -- when's the last time you spent
20 hours straight debugging your
son/wife/friend/neighbor/dog/ferret/snake? And they <em>still</em>
fell over anyway? Except in a direction you've never seen before
each time you try it? The easiest way to tell you're dealing with a
computer is when the other side keeps making the same moronic
misteakes over and misteakes over and misteakes over and misteakes
over and misteakes over and misteakes CTRL-C again.</p>
<p class='source'>Tim Peters, 30 Apr 1997</p>
<p class='quotation' id='q44'>BTW, a member of the ANSI C committee
once told me that the only thing rand is used for in C code is to
decide whether to pick up the axe or throw the dwarf, and if that's
true I guess "the typical libc rand" is adequate for all but the
most fanatic of gamers &lt;wink&gt;.</p>
<p class='source'>Tim Peters, 21 June 1997.</p>
<p class='quotation' id='q45'>Things in Python are very clear, but
are harder to find than the secrets of wizards. Things in Perl are
easy to find, but look like arcane spells to invoke magic.</p>
<p class='source'>Mike Meyer, 6 Nov 1997</p>
<p class='quotation' id='q46'>Indeed, as Palin has come to
understand, being part of Python means never really knowing what
may lurk around the corner.
"We've never really followed any rules at all
with Python," he said. "We're a spontaneous lot. It's more fun that
way."</p>
<p class='source'>Michael Palin, quoted from a Reuters/Variety news
item titled "Rare Python Reunion", Jan 15 1998.</p>
<p class='quotation' id='q47'>Python is an excellent language for
learning object orientation. (It also happens to be my favorite OO
scripting language.)</p>
<p class='source'>Sriram Srinivasan<cite>Advanced Perl Programming</cite></p>
<p class='quotation' id='q48'>The point is that newbies almost
always read more into the semantics of release than are specified,
so it's worthwile to be explicit about how little is being said
&lt;wink&gt;.</p>
<p class='source'>Tim Peters, 12 Feb 1998</p>
<p class='quotation' id='q49'>Ah! "Never mind" to a bunch of what I
said before (this editor can't move backwards &lt;wink&gt;).</p>
<p class='source'>Tim Peters, 12 Feb 1998</p>
<p class='quotation' id='q50'>After 1.5 years of Python, I'm still
discovering richness (and still unable to understand what the hell
Jim Fulton is talking about).</p>
<p class='source'>Gordon McMillan, 13 Mar 1998</p>
<p class='quotation' id='q51'>Tabs are good, spaces are bad and
mixing the two just means that your motives are confused and that
you don't use enough functions.</p>
<p class='source'>John J. Lehmann, 19 Mar 1998</p>
<p class='quotation' id='q52'>... but whenever optimization comes
up, people get sucked into debates about exciting but elaborate
schemes not a one of which ever gets implemented; better to get an
easy 2% today than dream about 100% forever.</p>
<p class='source'>Tim Peters, 22 Mar 1998</p>
<p class='quotation' id='q53'>I've been playing spoilsport in an
attempt to get tabnanny.py working, but now that there's absolutely
no reason to continue with this, the amount of my life I'm willing
to devote to it is unbounded &lt;0.9 wink&gt;.</p>
<p class='source'>Tim Peters, 30 Mar 1998</p>
<p class='quotation' id='q54'>Python is a little weak in forcing
encapsulation. It isn't made for bondage and domination
environments.</p>
<p class='source'>Paul Prescod, 30 Mar 1998</p>
<p class='quotation' id='q55'>One of my first big programming
assignments as a student of computer science was a source formatter
for Pascal. The assignment was designed to show us the real-life
difficulties of group programming projects. It succeeded perhaps
too well. For a long time, I was convinced that source code
formatters were a total waste of time, and decided to write
beautiful code that no automatic formatter could improve upon. In
fact, I would intentionally write code that formatters could only
make worse.</p>
<p class='source'>Guido van Rossum, 31 Mar 1998</p>
<p class='quotation' id='q56'>You need to build a system that is
futureproof; it's no good just making a modular system. You need to
realize that your system is just going to be a module in some
bigger system to come, and so you have to be part of something
else, and it's a bit of a way of life.</p>
<p class='source'>Tim Berners-Lee, at the WWW7 conference</p>
<p class='quotation' id='q57'>From gotos to the evolution of life
in 10 posts; that's comp.lang.python for you!</p>
<p class='source'>A.M. Kuchling, 4 Apr 1998</p>
<p class='quotation' id='q58'>This is <em>Python</em>! If we didn't
care what code looked like, most of us would probably be hacking in
some version of Lisp -- which already covered most of Python's
abstract <em>semantics</em> way back when Guido was just a wee
snakelet frolicking in the lush Amsterdam jungle.</p>
<p class='source'>Tim Peters, 24 Apr 1998</p>
<p class='quotation' id='q59'>The infinities aren't contagious
except in that they often appear that way due to their large
size.</p>
<p class='source'>Tim Peters, on the IEEE 754 floating point
standard 27 Apr 1998</p>
<p class='quotation' id='q60'>The "of course, while <em>I</em> have
no problem with this at all, it's surely too much for a lesser
being" flavor of argument always rings hollow to me. Are you
personally confused by the meanings for "+" that exist today?
<em>Objecting</em> to the variations is a different story; I'm
wondering whether you personally stumble over them in practice. I
don't; Steven doesn't; I doubt that you do either. I'm betting that
almost <em>nobody</em> ever does, in which case those "less nimble
colleagues and students" must be supernaturally feeble to merit
such concern.</p>
<p class='source'>Tim Peters, 29 Apr 1998</p>
<p class='quotation' id='q61'>"Ideally, IMO, two messages with the
same name should have the same meaning but possibly different
implementations. Of course, "meaning" is somewhat relative, but the
notion that two messages with the same name should have the same
'meaning' is very useful."
"Like clothes.launder() vs money.launder(), or
shape.draw() vs blood.draw(), or matrix.norm() vs hi.norm()
&lt;wink&gt;? I'm afraid English thrives on puns, and the same word
routinely means radically different things across application
areas. Therefore, to insist that a word have "one true meaning" in
a programming language is insisting that the language cater to one
true application domain."</p>
<p class='source'>Jim Fulton and Tim Peters, in a discussion of
rich comparisons, 29 Apr 1998</p>
<p class='quotation' id='q62'>Indeed, when I design <em>my</em>
killer language, the identifiers "foo" and "bar" will be reserved
words, never used, and not even mentioned in the reference manual.
Any program using one will simply dump core without comment.
Multitudes will rejoice.</p>
<p class='source'>Tim Peters, 29 Apr 1998</p>
<p class='quotation' id='q63'>Too little freedom makes life
confusingly clumsy; too much, clumsily confusing. Luckily, the
tension between freedom and restraint eventually gets severed by
Guido's Razor.</p>
<p class='source'>Tim Peters, 29 Apr 1998</p>
<p class='quotation' id='q64'>In other words, I'm willing to see
dark corners added to the language, as long as I don't have to go
into them myself.</p>
<p class='source'>A.M. Kuchling, 29 Apr 1998</p>
<p class='quotation' id='q65'>This argument is specious. What on
earth would it mean to compare an object you created with another
object from someone else's code unless you knew exactly what each
object's semantics were? Do you really want to ask if my abstract
syntax tree is less then your HTTP connection object?</p>
<p class='source'>Jeremy Hylton, in a discussion of rich
comparisons, 29 Apr 1998</p>
<hr /></div>
<small>[<a href="mailto:comments@amk.ca">Contact me</a>]</small>
</body>
</html>

 view all matches for this distribution
 view release on metacpan -  search on metacpan

( run in 1.071 second using v1.00-cache-2.02-grep-82fe00e-cpan-1925d2aa809 )