package Exobrain;
use v5.010;
use strict;
use feature ();
use autodie;
use Moose;
use Carp qw(croak);
use POSIX qw(tzset);
# ABSTRACT: Automate your life with Exobrain
our $VERSION = '1.08'; # VERSION: Generated by DZP::OurPkg:Version
# This forms the top-level Exobrain dispatch class. It's mostly
# lazy attributes, and nice sugar for doing common operations.
has 'config' => (
is => 'ro',
isa => 'Exobrain::Config',
builder => '_build_config',
);
# Pub/Sub interfaces to our bus. These don't get generated unless
# our end code actually asks for them. Many things will only require
# one, or will use higher-level functions to do their work.
has 'pub' => (
is => 'ro',
isa => 'Exobrain::Bus',
builder => '_build_pub',
lazy => 1,
);
has 'sub' => (
is => 'ro',
isa => 'Exobrain::Bus',
builder => '_build_sub',
lazy => 1,
);
# import is called when we load the Exobrain class, and lets us
# automatically set strict, warnings, 5.10 features and Method::Signatures
# by default.
sub import {
strict->import();
warnings->import();
feature->import(':5.10');
# Method::Signatures needs a little more magic.
my $caller = caller;
## no critic (ProhibitStringyEval)
eval qq{
package $caller;
Method::Signatures->import;
1;
} or die $@;
## use critic
return;
}
# Right now we make sure anything using Exobrain is going to use
# the user's timezone (if set).
method BUILD(...) {
$self->_set_timezone();
return;
}
# This sets our timezone based upon what's in our config file,
# so any use of localtime() should use the user's local timezone.
method _set_timezone($tz?) {
$tz //= $self->config->{General}{timezone};
if ($tz) {
$ENV{TZ} = $tz; ## no critic RequireLocalizedPunctuationVars
tzset();
}
return;
}
sub _build_config { return Exobrain::Config->new; };
sub _build_pub { return Exobrain::Bus->new(type => 'PUB', exobrain => shift) }
sub _build_sub { return Exobrain::Bus->new(type => 'SUB', exobrain => shift) }
method watch_loop(
Str :$class!,
CodeRef :$filter,
CodeRef :$then!,
CodeRef :$debug?,
) {
# Load our component, because that means we immediately get
# an error if that class doesn't exist.
$self->_load_component($class);
while (my $event = $self->sub->get) {
my $namespace = $event->namespace;
if (grep { $_ eq $class } ($namespace, @{ $event->roles })) {
# Note that we have to cast it to the namespace on the
# packet (which is a class), and not the $class argument
# (which could be a role!)
#
# Yes, this code should be simplified/improved!
$event = $event->to_class($namespace);
$debug->($event) if $debug;
if ($filter) {
# Check our filter, and skip if required
local $_ = $event;
next unless $filter->($event);
}
# Everything passes! Trigger our callback
{ local $_ = $event; $then->($event); }
}
}
}
use constant NOTIFY => 'Notify';
method notify($message, @args) {
return $self->intent( NOTIFY,
message => $message,
@args,
);
}
method message(@args) {
return Exobrain::Message::Raw->new(
exobrain => $self,
@args,
);
}
method message_class($class, @args) {
$class = $self->_load_component($class);
return $class->new(
exobrain => $self,
@args,
);
}
use constant MEASURE_PREFIX => 'Measurement::';
method measure($type, @args) {
my $class = $self->_load_component( MEASURE_PREFIX . $type );
return $class->new(
exobrain => $self,
@args,
);
}
use constant INTENT_PREFIX => 'Intent::';
method intent($type, @args) {
my $class = $self->_load_component( INTENT_PREFIX . $type );
return $class->new(
exobrain => $self,
@args,
);
}
use constant AGENT_PREFIX => 'Agent::';
method run(Str $class) {
my $agent = $self->_load_component( AGENT_PREFIX . $class )->new;
return $agent->start;
}
method can_run(Str $class) {
my $agent = $self->_load_component( AGENT_PREFIX . $class );
if ($agent->DOES('Exobrain::Agent::Depends')) {
return $agent->check_dependencies;
}
return 1;
}
# Loads a class, automatically adding Exobrain if
# required. Returns the class loaded.
use constant CLASS_PREFIX => 'Exobrain::';
method _load_component(Str $class) {
$class = CLASS_PREFIX . $class;
eval "require $class";
croak $@ if $@;
return $class;
}
__PACKAGE__->meta->make_immutable;
1;
__END__
=pod
=head1 NAME
Exobrain - Automate your life with Exobrain
=head1 VERSION
version 1.08
=head1 SYNOPSIS
use Exobrain;
my $exobrain = Exobrain->new;
$exobrain->notify("Hello World");
=head1 DESCRIPTION
Exobrain is a collection of agents which collect, classify, and act
upon data. They share a common bus for communication. Think of it as
IFTTT, but free, open source, and *you* keep control of your privacy.
Examples of things that exobrain can currently do:
=over
=item *
Give you XP in HabitRPG when you update your beeminder goals
=item *
Let others add items to your RememberTheMilk ToDo lists via twitter
=item *
Update beeminder by checking your inbox sizes using IMAP
=item *
Reward with HabitRPG XP you for responding to email
=item *
Automatically log life events to idonethis
=back
You can find out more information about Exobrain, including the
full README, install instructions, current development, and more
L<on github|https://github.com/pjf/exobrain>. In particular,
is strongly suggested reading.
Using C<Exobrain> will automatically enable both C<strict>, C<warnings>,
and Perl 5.10 features (such as C<say>).
The following are methods provided by the top-level Exobrain object:
=head1 METHODS
=head2 watch_loop
$exobrain->watch_loop(
class => 'Measurement::Geo',
filter => sub { $_->is_me },
then => sub { ... },
);
When we see packets of a particular class, do a particular thing.
The C<class> need not strictly be a class, but may also be a
C<role>.
The 'Exobrain::' prefix should not be supplied to the class/roles
you are searching for.
If the optional C<debug> option is passed with a coderef, that will be run for
every event in the desired class, before the filter is evaluated.
The event is passed as the first argument to all coderefs. As a
convenience, it is also placed inside C<$_>.
Never returns, just runs the loop forever.
=head2 notify
$exobrain->notify($msg
priority => -1,
);
Takes a mandatory message, and any arguments that can be passeed to
L<Exobrain::Intent::Notify>, and notifies the user. At the time of
writing, notifications are done by the pushover end-point by default.
This is a thin wrapper around C< $exobrain->intent('Notify', ... >.
=head2 message
$exobrain->message( ... );
Shortcut to create a 'raw' message. The exobrain parameter will be passed
to the class constructor automatically.
The message I<will> be sent automatically, unless the C<nosend> parameter
is set to a true value.
=head2 measure
$exobrain->measure( 'Mailbox',
count => 42,
user => 'pjf',
server => 'imap.example.com',
fodler => 'INBOX',
)->send;
Preferred shortcut for creating a measurement of the desired class. The
C<exobrain> parameter will be passed to the measurement class constructor
automatically.
=head2 intent
my $intent = $exobrain->intent( 'Tweet',
tweet => 'Hello World',
);
Preferred shortcut for making an intent of the desired class. The
C<exobrain> parameter will be passed to the intent class constructor
automatically.
=head2 run
$exobrain->run($agent);
Runs the agent of the class specified. The agent name is
automatically prepended with "Exobrain::Agent::" and loaded
first. This method never returns.
This is usually called from the C<exobrain> cmdline program.
=head2 can_run
if ($exobrain->can_run('Action::Foo') { ... }
Checks to see if a given agent I<could> be run. (Ie, it can be loaded, and
all its dependencies are satisified.)
=for Pod::Coverage BUILD DEMOLISH CLASS_PREFIX INTENT_PREFIX MEASURE_PREFIX NOTIFY AGENT_PREFIX import
=head1 AUTHOR
Paul Fenwick <pjf@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2014 by Paul Fenwick.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut