Skip to content

Instantly share code, notes, and snippets.

@jhthorsen
Last active October 20, 2015 18:08
Show Gist options
  • Save jhthorsen/832e745639341ce60815 to your computer and use it in GitHub Desktop.
Save jhthorsen/832e745639341ce60815 to your computer and use it in GitHub Desktop.
Mojo::Cursor - General purpose async iterator
use Mojo::Base -strict;
use Mojo::Cursor;
use Mojo::IOLoop::While;
use File::Basename 'dirname';
use Test::More;
my @items = (42, 24);
my $iterator = Mojo::Cursor->new(sub { return '', shift @items });
my @res;
$iterator->next(sub { shift; push @res, @_; });
$iterator->next(sub { shift; push @res, @_; });
$iterator->next(sub { shift; push @res, @_; Mojo::IOLoop->stop; });
Mojo::IOLoop->start;
is_deeply \@res, ['', 42, '', 24, '', undef], 'new(sub {...})';
@res = ();
Mojo::IOLoop::While->new(
Mojo::Cursor->new_from_dir(dirname(__FILE__), sub { /\.t$/ ? {file => $_} : undef }),
sub {
my ($delay, $err, $item) = @_;
return unless $item;
$delay->redo;
push @res, $item;
},
);
Mojo::IOLoop->start;
like $res[0]{file}, qr{\.t$}, 'got files';
@res = ();
$iterator = Mojo::Cursor->new_from_dir('no_such_directory', sub {$_});
$iterator->next(sub { shift; push @res, @_; Mojo::IOLoop->stop unless pop; });
Mojo::IOLoop->start;
ok $res[0], 'new_from_dir(no_such_directory) error';
is $res[1], undef, 'new_from_dir(no_such_directory) undef';
done_testing;
package Mojo::Cursor;
=head1 NAME
Mojo::Cursor - General purpose async iterator
=head1 DESCRIPTION
L<Mojo::Cursor> is a class for instantiating an iterator object.
=head1 SYNOPSIS
use Mojo::Cursor;
my $iterator = Mojo::Cursor->new(sub { return $err, $item; });
my $iterator = Mojo::Cursor->new(sub { return '', shift @items });
my $iterator = Mojo::Cursor->new_from_dir($path, sub { /^\w/ });
# async
$iterator->next(sub {
my ($iterator, $err, $item) = @_;
die $err if $err;
});
# sync
$item = $iterator->next;
=cut
use Mojo::Base -base;
use Mojo::IOLoop;
=head1 METHODS
=head2 next
See L</SYNOPSIS>.
=cut
sub next {
my ($self, $cb) = @_;
my $iterator = $self->{iterator};
return $iterator->($self) unless $cb;
Mojo::IOLoop->next_tick(
sub {
$cb->($self, $@, undef) unless eval { $cb->($self, $iterator->($self)) };
}
);
return $self;
}
=head2 new
$self = Mojo::Cursor->new(sub { ... });
See L</SYNOPSIS>.
=cut
sub new {
my ($class, $cb) = (shift, shift);
my $self = $class->SUPER::new(@_);
$self->{iterator} = $cb;
$self;
}
=head2 new_from_dir
$self = Mojo::Cursor->new_from_dir($path, $filter);
Creates a L</new> iterator object from a C<$path>. Each of the files
returned will need to pass the C<$filter> function.
=cut
sub new_from_dir {
my ($class, $path, $filter) = (shift, shift, shift);
my $self = $class->SUPER::new(@_);
if (opendir(my $DH, $path)) {
return $self->new(
sub {
local ($!, $_, $@);
while (readdir $DH) {
my $item = eval { $filter->($_) };
return $@, undef if $@;
return '', $item if defined $item;
}
return $!, undef;
}
);
}
else {
my $err = $!;
return $self->new(sub { return $err, undef; }, @_);
}
}
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2014, Jan Henning Thorsen
This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.
=head1 AUTHOR
Jan Henning Thorsen - C<jhthorsen@cpan.org>
=cut
1;
package Convos::IOLoop::While;
=head1 NAME
Convos::IOLoop::While - Go loopy of Mojo::IOLoop::Delay
=head1 DESCRIPTION
L<Convos::IOLoop::While> is a subclass of L<Mojo::IOLoop::Delay> which
can loop L<Mojo::IOLoop::Delay/remaining>.
=head1 SYNOPSIS
use Convos::IOLoop::While;
Convos::IOLoop::While->new(
$cursor_obj,
sub {
my ($delay, $err, $item) = @_;
die $err if $err; # $cursor_obj->next reported $err
return unless $item; # last item from $cursor_obj
$delay->redo; # get next() item from $cursor_obj
# do stuff with $item
},
);
=cut
use Mojo::Base 'Mojo::IOLoop::Delay';
=head1 ATTRIBUTES
=head1 METHODS
=head2 new
$self = Convos::IOLoop::While->new($cursor, @steps);
$self = Convos::IOLoop::While->new($cursor, sub {...}, ...);
Creates a new L<Convos::IOLoop::While> object.
=cut
sub new {
my ($class, @steps) = @_;
if (ref $steps[0] ne 'CODE') {
my $cursor = shift @steps;
unshift @steps, sub { $cursor->next(shift->begin) };
}
return $class->SUPER::new(steps => [@steps])->steps(@steps);
}
=head2 remaining
See L<Mojo::IOLoop::Delay/remaining>.
=cut
sub remaining {
my $self = shift;
return $self->SUPER::remaining unless @_;
delete $self->{steps} unless @{$_[0]};
$self->SUPER::remaining(@_);
}
=head2 redo
$self->redo;
Get the next item from the cursor and do all the
L<steps|Mojo::IOLoop::Delay/steps> again.
=cut
sub redo {
my $self = shift;
$self->remaining([@{$self->{steps}}]);
}
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2014, Jan Henning Thorsen
This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.
=head1 AUTHOR
Jan Henning Thorsen - C<jhthorsen@cpan.org>
=cut
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment