package Rethinkdb; use Rethinkdb::Base -base; use Carp 'croak'; use Scalar::Util 'weaken'; use Rethinkdb::IO; use Rethinkdb::Protocol; use Rethinkdb::Query::Database; use Rethinkdb::Query::Table; use Rethinkdb::Query; use Rethinkdb::Util; our $VERSION = '0.14'; # this is set only when connect->repl() has 'io'; has 'term' => sub { Rethinkdb::Protocol->new->term; }; sub import { my $class = shift; my $package = caller; no strict; *{"$package\::r"} = \&r; return; } sub r { my $package = caller; my $self; if ($package::_rdb_io) { $self = __PACKAGE__->new( io => $package::_rdb_io ); $self->io->_rdb($self); } else { $self = __PACKAGE__->new; } return $self; } sub connect { my $self = shift; my $host = shift || 'localhost'; my $port = shift || 28015; my $db = shift || 'test'; my $auth_key = shift || ''; my $timeout = shift || 20; my $io = Rethinkdb::IO->new( _rdb => $self, host => $host, port => $port, default_db => $db, auth_key => $auth_key, timeout => $timeout ); weaken $io->{_rdb}; return $io->connect; } sub server { my $self = shift; return $self->io->server; } # DATABASES sub db_create { my $self = shift; my $args = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->db_create, args => $args, ); weaken $q->{_rdb}; return $q; } sub db_drop { my $self = shift; my $args = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->db_drop, args => $args ); weaken $q->{_rdb}; return $q; } sub db_list { my $self = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->db_list, ); weaken $q->{_rdb}; return $q; } sub db { my $self = shift; my $name = shift; my $db = Rethinkdb::Query::Database->new( _rdb => $self, name => $name, args => $name, ); weaken $db->{_rdb}; return $db; } # TABLE sub table { my $self = shift; my $name = shift; my $outdated = shift; my $optargs = {}; if ($outdated) { $optargs = { use_outdated => 1 }; } my $t = Rethinkdb::Query::Table->new( _rdb => $self, _type => $self->term->termType->table, name => $name, args => $name, optargs => $optargs, ); weaken $t->{_rdb}; return $t; } # DOCUMENT MANIPULATION sub row { my $self = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->implicit_var, ); weaken $q->{_rdb}; return $q; } sub literal { my $self = shift; my $args = ref $_[0] ? $_[0] : {@_}; my $q = Rethinkdb::Query->new( _type => $self->term->termType->literal, args => $args, ); return $q; } sub object { my $self = shift; my $args = ref $_[0] ? $_[0] : {@_}; my $q = Rethinkdb::Query->new( _type => $self->term->termType->object, args => $args, ); return $q; } # MATH AND LOGIC sub and { my $self = shift; my $args = \@_; my $q = Rethinkdb::Query->new( _type => $self->term->termType->and, args => $args, ); return $q; } sub or { my $self = shift; my $args = \@_; my $q = Rethinkdb::Query->new( _type => $self->term->termType->or, args => $args, ); return $q; } sub random { my $self = shift; my $args = [@_]; my $optargs = {}; if ( ref $args->[2] eq 'HASH' ) { $optargs = $args->[2]; } elsif ( scalar @{$args} > 2 and $args->[2] ) { $optargs->{float} = r->true; } # only keep the first two elements $args = [ splice @{$args}, 0, 2 ]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->random, args => $args, optargs => $optargs, ); return $q; } # DATES AND TIMES sub now { my $self = shift; my $q = Rethinkdb::Query->new( _type => $self->term->termType->now ); return $q; } sub time { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->time, args => $args ); return $q; } sub epoch_time { my $self = shift; my $args = shift; my $q = Rethinkdb::Query->new( _type => $self->term->termType->epoch_time, args => $args ); return $q; } sub iso8601 { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->iso8601, args => $args ); return $q; } sub monday { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->monday, args => $args ); return $q; } sub tuesday { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->tuesday, args => $args ); return $q; } sub wednesday { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->wednesday, args => $args ); return $q; } sub thursday { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->thursday, args => $args ); return $q; } sub friday { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->friday, args => $args ); return $q; } sub saturday { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->saturday, args => $args ); return $q; } sub sunday { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->sunday, args => $args ); return $q; } sub january { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->january, args => $args ); return $q; } sub february { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->february, args => $args ); return $q; } sub march { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->march, args => $args ); return $q; } sub april { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->april, args => $args ); return $q; } sub may { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->may, args => $args ); return $q; } sub june { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->june, args => $args ); return $q; } sub july { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->july, args => $args ); return $q; } sub august { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->august, args => $args ); return $q; } sub september { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->september, args => $args ); return $q; } sub october { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->october, args => $args ); return $q; } sub november { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->november, args => $args ); return $q; } sub december { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->december, args => $args ); return $q; } # CONTROL STRUCTURES sub args { my $self = shift; my $args = [@_]; my $q = Rethinkdb::Query->new( _type => $self->term->termType->args, args => $args ); return $q; } # TODO: figure out why the arguments have to be reversed here sub do { my $self = shift; my ( $one, $two ) = @_; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->funcall, args => [ $two, $one ], ); weaken $q->{_rdb}; return $q; } sub branch { my $self = shift; my ( $predicate, $true, $false ) = @_; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->branch, args => [ $predicate, $true, $false ], ); weaken $q->{_rdb}; return $q; } sub error { my $self = shift; my ($message) = @_; my $q = Rethinkdb::Query->new( _type => $self->term->termType->error, args => $message, ); return $q; } sub expr { my $self = shift; my $value = shift; return Rethinkdb::Util->_expr($value); } sub js { my $self = shift; my $args = shift; my $timeout = shift; my $optargs = {}; if ($timeout) { $optargs = { timeout => $timeout }; } my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->javascript, args => $args, optargs => $optargs, ); weaken $q->{_rdb}; return $q; } sub json { my $self = shift; my $value = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->json, args => $value, ); weaken $q->{_rdb}; return $q; } sub http { my $self = shift; my $value = shift; my $optargs = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->http, args => $value, optargs => $optargs, ); weaken $q->{_rdb}; return $q; } sub uuid { my $self = shift; my $value = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->uuid, args => $value, ); weaken $q->{_rdb}; return $q; } # GEO sub circle { my $self = shift; my $point = shift; my $radius = shift; my $optargs = shift; my $q = Rethinkdb::Query->new( _type => $self->term->termType->circle, args => [ $point, $radius ], optargs => $optargs, ); return $q; } sub distance { my $self = shift; my $point1 = shift; my $point2 = shift; my $optargs = shift; my $q = Rethinkdb::Query->new( _type => $self->term->termType->distance, args => [ $point1, $point2 ], optargs => $optargs, ); return $q; } sub geojson { my $self = shift; my $args = shift; my $q = Rethinkdb::Query->new( _type => $self->term->termType->geojson, args => $args, ); return $q; } sub line { my $self = shift; my $args = \@_; my $q = Rethinkdb::Query->new( _type => $self->term->termType->line, args => $args, ); return $q; } sub point { my $self = shift; my $args = \@_; my $q = Rethinkdb::Query->new( _type => $self->term->termType->point, args => $args, ); return $q; } sub polygon { my $self = shift; my $args = \@_; my $q = Rethinkdb::Query->new( _type => $self->term->termType->polygon, args => $args, ); return $q; } # MISC sub asc { my $self = shift; my $name = shift; my $q = Rethinkdb::Query->new( _type => $self->term->termType->asc, args => $name, ); return $q; } sub desc { my $self = shift; my $name = shift; my $q = Rethinkdb::Query->new( _type => $self->term->termType->desc, args => $name, ); return $q; } sub wait { my $self = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->wait, ); return $q; } sub minval { my $self = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->minval, ); return $q; } sub maxval { my $self = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->maxval, ); return $q; } sub round { my $self = shift; my $args = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->round, args => $args ); return $q; } sub ceil { my $self = shift; my $args = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->ceil, args => $args ); return $q; } sub floor { my $self = shift; my $args = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->floor, args => $args ); return $q; } sub grant { my $self = shift; my $user = shift; my $perms = shift; my $q = Rethinkdb::Query->new( _rdb => $self, _type => $self->term->termType->grant, args => [ $user, $perms ] ); return $q; } sub true { return Rethinkdb::_True->new; } sub false { return Rethinkdb::_False->new; } package Rethinkdb::_True; use overload '""' => sub {'true'}, 'bool' => sub {1}, 'eq' => sub { $_[1] eq 'true' ? 1 : 0; }, '==' => sub { $_[1] == 1 ? 1 : 0; }, fallback => 1; sub new { return bless {}, $_[0] } package Rethinkdb::_False; use overload '""' => sub {'false'}, 'bool' => sub {0}, 'eq' => sub { $_[1] eq 'false' ? 1 : 0; }, '==' => sub { $_[1] == 0 ? 1 : 0; }, fallback => 1; sub new { return bless {}, $_[0] } 1; __END__ =encoding utf8 =head1 NAME Rethinkdb - Pure Perl RethinkDB Driver =head1 SYNOPSIS package MyApp; use Rethinkdb; r->connect->repl; r->table('agents')->get('007')->update( r->branch( r->row->attr('in_centrifuge'), {'expectation': 'death'}, {} ) )->run; =head1 DESCRIPTION Rethinkdb enables Perl programs to interact with RethinkDB in a easy-to-use way. This particular driver is based on the official Python, Javascript, and Ruby drivers. To learn more about RethinkDB take a look at the L. =head1 ATTRIBUTES L implements the following attributes. =head2 io my $io = r->io; r->io(Rethinkdb::IO->new); The C attribute returns the current L instance that L is currently set to use. If C is not set by the time C is called, then an error will occur. =head2 term my $term = r->term; The C attribute returns an instance of the RethinkDB Query Langague protocol. =head1 METHODS L inherits all methods from L and implements the following methods. =head2 r my $r = r; my $conn = r->connect; C is a factory method to begin a new Rethink DB query. The C sub is exported in the importer's namespace so that it can be used as short-hand; similar to what the official drivers provide. In addition, to creating a new instance, if a L connection has been repl-ized, then that connection will be set via C in the new instance. =head2 connect my $conn1 = r->connect; my $conn2 = r->connect('localhost', 28015, 'test', 'auth_key', 20); Create a new connection to a RethinkDB shard. Creating a connection tries to contact the RethinkDB shard immediately and will fail if the connection fails. =head2 server r->server->run; Return information about the server being used by the default connection. The server command returns either two or three fields: =over =item C: the UUID of the server the client is connected to. =item C: a boolean indicating whether the server is a L. =item C: the server name. If proxy is Ctrue>, this field will not be returned. =back =head2 db_create r->db_create('test')->run; Create a database. A RethinkDB database is a collection of tables, similar to relational databases. =head2 db_drop r->db_drop('test')->run; Drop a database. The database, all its tables, and corresponding data will be deleted. =head2 db_list r->db_list->run; List all database names in the system. =head2 db r->db('irl')->table('marvel')->run; Reference a database. =head2 table r->table('marvel')->run; r->table('marvel', 1)->run; r->table('marvel')->get('Iron Man')->run; r->table('marvel', r->true)->get('Iron Man')->run; Select all documents in a table. This command can be chained with other commands to do further processing on the data. =head2 row r->table('users')->filter(r->row->attr('age')->lt(5))->run; r->table('users')->filter( r->row->attr('embedded_doc')->attr('child')->gt(5) )->run; r->expr([1, 2, 3])->map(r->row->add(1))->run; r->table('users')->filter(sub { my $row = shift; $row->attr('name')->eq(r->table('prizes')->get('winner')); })->run; Returns the currently visited document. =head2 literal r->table('users')->get(1)->update({ data: r->literal({ age => 19, job => 'Engineer' }) })->run; Replace an object in a field instead of merging it with an existing object in a merge or update operation. =head2 object r->object('id', 5, 'data', ['foo', 'bar'])->run; Creates an object from a list of key-value pairs, where the keys must be strings. C is equivalent to C. =head2 and r->and(true, false)->run; Compute the logical C of two or more values. =head2 or r->or(true, false)->run; Compute the logical C of two or more values. =head2 random r->random() r->random(number[, number], {float => true}) r->random(integer[, integer]) Generate a random number between given (or implied) bounds. C takes zero, one or two arguments. =head2 now r->table("users")->insert({ name => "John", subscription_date => r->now() })->run($conn); Return a time object representing the current time in UTC. The command C is computed once when the server receives the query, so multiple instances of C will always return the same time inside a query. =head2 time r->table('user')->get('John')->update({ birthdate => r->time(1986, 11, 3, 'Z') })->run; Create a time object for a specific time. =head2 epoch_time r->table('user')->get('John')->update({ "birthdate" => r->epoch_time(531360000) })->run; Create a time object based on seconds since epoch. The first argument is a double and will be rounded to three decimal places (millisecond-precision). =head2 iso8601 r->table('user')->get('John')->update({ birth => r->iso8601('1986-11-03T08:30:00-07:00') })->run; Create a time object based on an ISO 8601 date-time string (e.g. '2013-01-01T01:01:01+00:00'). We support all valid ISO 8601 formats except for week dates. If you pass an ISO 8601 date-time without a time zone, you must specify the time zone with the default_timezone argument. Read more about the ISO 8601 format at L. =head2 monday r->table('users')->filter( r->row('birthdate')->day_of_week()->eq(r->monday) )->run; L is a literal day of the week for comparisions. =head2 tuesday r->table('users')->filter( r->row('birthdate')->day_of_week()->eq(r->tuesday) )->run; L is a literal day of the week for comparisions. =head2 wednesday r->table('users')->filter( r->row('birthdate')->day_of_week()->eq(r->wednesday) )->run; L is a literal day of the week for comparisions. =head2 thursday r->table('users')->filter( r->row('birthdate')->day_of_week()->eq(r->thursday) )->run; L is a literal day of the week for comparisions. =head2 friday r->table('users')->filter( r->row('birthdate')->day_of_week()->eq(r->friday) )->run; L is a literal day of the week for comparisions. =head2 saturday r->table('users')->filter( r->row('birthdate')->day_of_week()->eq(r->saturday) )->run; L is a literal day of the week for comparisions. =head2 sunday r->table('users')->filter( r->row('birthdate')->day_of_week()->eq(r->sunday) )->run; L is a literal day of the week for comparisions. =head2 january r->table('users')->filter( r->row('birthdate')->month()->eq(r->january) )->run; L is a literal month for comparisions. =head2 february r->table('users')->filter( r->row('birthdate')->month()->eq(r->february) )->run; L is a literal month for comparisions. =head2 march r->table('users')->filter( r->row('birthdate')->month()->eq(r->march) )->run; L is a literal month for comparisions. =head2 april r->table('users')->filter( r->row('birthdate')->month()->eq(r->april) )->run; L is a literal month for comparisions. =head2 may r->table('users')->filter( r->row('birthdate')->month()->eq(r->may) )->run; L is a literal month for comparisions. =head2 june r->table('users')->filter( r->row('birthdate')->month()->eq(r->june) )->run; L is a literal month for comparisions. =head2 july r->table('users')->filter( r->row('birthdate')->month()->eq(r->july) )->run; L is a literal month for comparisions. =head2 august r->table('users')->filter( r->row('birthdate')->month()->eq(r->august) )->run; L is a literal month for comparisions. =head2 september r->table('users')->filter( r->row('birthdate')->month()->eq(r->september) )->run; L is a literal month for comparisions. =head2 october r->table('users')->filter( r->row('birthdate')->month()->eq(r->october) )->run; L is a literal month for comparisions. =head2 november r->table('users')->filter( r->row('birthdate')->month()->eq(r->november) )->run; L is a literal month for comparisions. =head2 december r->table('users')->filter( r->row('birthdate')->month()->eq(r->december) )->run; L is a literal month for comparisions. =head2 args r->table('people')->get_all('Alice', 'Bob')->run; # or r->table('people')->get_all(r->args(['Alice', 'Bob']))->run; C<< r->args >> is a special term that's used to splice an array of arguments into another term. This is useful when you want to call a variadic term such as L with a set of arguments produced at runtime. =head2 do r->do(r->table('marvel')->get('IronMan'), sub { my $ironman = shift; return $ironman->attr('name'); })->run; Evaluate the expr in the context of one or more value bindings. The type of the result is the type of the value returned from expr. =head2 branch r->table('marvel')->map( r->branch( r->row->attr('victories')->lt(100), r->row->attr('name')->add(' is a superhero'), r->row->attr('name')->add(' is a hero') ) )->run; Evaluate one of two control paths based on the value of an expression. C is effectively an C renamed due to language constraints. The type of the result is determined by the type of the branch that gets executed. =head2 error r->table('marvel')->get('IronMan')->do(sub { my $ironman = shift; r->branch( $ironman->attr('victories')->lt($ironman->attr('battles')), r->error('impossible code path'), $ironman ); })->run; Throw a runtime error. If called with no arguments inside the second argument to default, re-throw the current error. =head2 expr r->expr({a => 'b'})->merge({b => [1,2,3]})->run($conn); Construct a RQL JSON object from a native object. =head2 js r->js("'str1' + 'str2'")->run($conn); r->table('marvel')->filter( r->js('(function (row) { return row.age > 90; })') )->run($conn); r->js('while(true) {}', 1.3)->run($conn); Create a javascript expression. =head2 json r->json("[1,2,3]")->run($conn); Parse a JSON string on the server. =head2 http r->table('posts')->insert(r->http('httpbin.org/get'))->run; r->http('http://httpbin.org/post', { method => 'POST', data => { player => 'Bob', game => 'tic tac toe' } })->run($conn); Retrieve data from the specified URL over HTTP. The return type depends on the C option, which checks the C of the response by default. =head2 uuid r->uuid->run; Return a UUID (universally unique identifier), a string that can be used as a unique ID. =head2 circle r->circle( [ -122.423246, 37.770378359 ], 10, { unit => 'mi' } ) Construct a circular line or polygon. A circle in RethinkDB is a polygon or line approximating a circle of a given radius around a given center, consisting of a specified number of vertices (default 32). =head2 distance r->distance( r->point( -122.423246, 37.779388 ), r->point( -117.220406, 32.719464 ), { unit => 'km' } )->run; Compute the distance between a point and another geometry object. At least one of the geometry objects specified must be a point. =head2 geojson r->geojson( { 'type' => 'Point', 'coordinates' => [ -122.423246, 37.779388 ] } ) Convert a L object to a ReQL geometry object. =head2 line r->line( [ -122.423246, 37.779388 ], [ -121.886420, 37.329898 ] ) Construct a geometry object of type Line. The line can be specified in one of two ways: (1) Two or more two-item arrays, specifying latitude and longitude numbers of the line's vertices; (2) Two or more L objects specifying the line's vertices. =head2 point r->point( -122.423246, 37.779388 ) Construct a geometry object of type Point. The point is specified by two floating point numbers, the longitude (-180 to 180) and latitude (-90 to 90) of the point on a perfect sphere. =head2 polygon r->polygon( [ -122.423246, 37.779388 ], [ -122.423246, 37.329898 ], [ -121.886420, 37.329898 ], [ -121.886420, 37.779388 ] ) Construct a geometry object of type Polygon. The Polygon can be specified in one of two ways: (1) Three or more two-item arrays, specifying longitude and latitude numbers of the polygon's vertices; (2) Three or more L objects specifying the polygon's vertices. =head2 asc r->table('marvel')->order_by(r->asc('enemies_vanquished'))->run; Specifies that a column should be ordered in ascending order. =head2 desc r->table('marvel')->order_by(r->desc('enemies_vanquished'))->run; Specifies that a column should be ordered in descending order. =head2 wait r->wait->run; Wait on all the tables in the default database (set with the L command's C parameter, which defaults to C). A table may be temporarily unavailable after creation, rebalancing or reconfiguring. The L command blocks until the given all the tables in database is fully up to date. =head2 minval r->table('marvel')->between( r->minval, 7 )->run; The special constants L is used for specifying a boundary, which represent "less than any index key". For instance, if you use L as the lower key, then L will return all documents whose primary keys (or indexes) are less than the specified upper key. =head2 maxval r->table('marvel')->between( 8, r->maxval )->run; The special constants L is used for specifying a boundary, which represent "greater than any index key". For instance, if you use L as the upper key, then L will return all documents whose primary keys (or indexes) are greater than the specified lower key. =head2 round r->round(-12.567)->run; Rounds the given value to the nearest whole integer. For example, values of 1.0 up to but not including 1.5 will return 1.0, similar to L; values of 1.5 up to 2.0 will return 2.0, similar to L. =head2 ceil r->ceil(-12.567)->run; Rounds the given value up, returning the smallest integer value greater than or equal to the given value (the value's ceiling). =head2 floor r->floor(-12.567)->run; Rounds the given value down, returning the largest integer value less than or equal to the given value (the value's floor). =head2 grant r->grant('username', {read => r->true, write => r->false })->run; Grant or deny access permissions for a user account globally. =head2 true r->true->run; Helper literal since Perl does not have a C literal. =head2 false r->false->run; Helper literal since Perl does not have a C literal. =head1 AUTHOR Nathan Levin-Greenhaw, C. =head1 COPYRIGHT AND LICENSE Unless otherwise noted: Copyright (C) 2013-2014, Nathan Levin-Greenhaw A lot of the above documentation above was taken from the L. Copyright (C) 2010-2014 RethinkDB. This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0. =cut