package Term::ReadLine::Event; $Term::ReadLine::Event::VERSION = '0.05'; use 5.006; use strict; use warnings; use Term::ReadLine 1.09; use Scalar::Util qw(blessed); # ABSTRACT: Wrappers for Term::ReadLine's new event_loop model. sub _new { my $class = shift; my $app = shift; my $self = bless {@_}, $class; $self->{_term} = blessed $app ? $app : Term::ReadLine->new(ref $app ? @$app : $app); $self; } sub with_AnyEvent { my $self = _new(@_); $self->trl->event_loop( sub { my $data = shift; $data->[0] = AE::cv(); $data->[0]->recv(); }, sub { my $fh = shift; # The data for AE are: the file event watcher (which # cannot be garbage collected until we're done) and # a placeholder for the condvar we're sharing between # the AE::io callback created here and the wait # callback above. my $data = []; $data->[1] = AE::io($fh, 0, sub { $data->[0]->send() }); $data; } ); $self; } sub with_Coro { my $self = _new(@_); require Coro::Handle; $self->trl->event_loop( sub { # Tell Coro to wait until we have something to read, # and then we can return. shift->readable(); }, sub { # in Coro, we just need to unblock the filehandle, # and save the unblocked filehandle. my $fh = shift; Coro::Handle::unblock $fh; } ); $self; } sub with_IO_Async { my $self = _new(@_); $self->trl->event_loop( sub { my $ready = shift; $$ready = 0; $self->{loop}->loop_once while !$$ready; }, sub { my $fh = shift; # The data for IO::Async is just the ready flag. To # ensure we're referring to the same value, this is # a SCALAR ref. my $ready = \ do{my $dummy}; # Term::ReadLine::Gnu::XS relies on the fh being # able to do fileno. unless($fh->can('fileno')) { require IO::Handle; my $h = IO::Handle->new; $h->fdopen($fh, 'r') or die "could not fdopen - $!"; $fh = $h } $self->{loop}->add( $self->{watcher} = IO::Async::Handle->new( read_handle => $fh, on_read_ready => sub { $$ready = 1 }, ) ); $ready; } ); $self->{_cleanup} = sub { my $s = shift; $s->{loop}->remove($s->{watcher}); }; $self; } sub with_POE { my $self = _new(@_); my $waiting_for_input; POE::Session->create( inline_states => { # Initialize the session that will drive Term::ReadLine. # Tell Term::ReadLine to invoke a couple POE event handlers when # it's ready to wait for input, and when it needs to register an # I/O watcher. _start => sub { $self->trl->event_loop( $_[POE::Session->SESSION]->callback('term_readline_waitfunc'), $_[POE::Session->SESSION]->callback('term_readline_regfunc'), ); }, # This callback is invoked every time Term::ReadLine wants to # read something from its input file handle. It blocks # Term::ReadLine until input is seen. # # It sets a flag indicating that input hasn't arrived yet. # It watches Term::ReadLine's input filehandle for input. # It runs while it's waiting for input. # It turns off the input watcher when it's no longer needed. # # POE::Kernel's run_while() dispatches other events (including # "term_readline_readable" below) until $waiting_for_input goes # to zero. term_readline_waitfunc => sub { my $input_handle = $_[POE::Session->ARG1][0]; $waiting_for_input = 1; $_[POE::Session->KERNEL]->select_read($input_handle => 'term_readline_readable'); $_[POE::Session->KERNEL]->run_while(\$waiting_for_input); $_[POE::Session->KERNEL]->select_read($input_handle => undef); }, # This callback is invoked as Term::ReadLine is starting up for # the first time. It saves the exposed input filehandle where # the "term_readline_waitfunc" callback can see it. term_readline_regfunc => sub { my $input_handle = $_[POE::Session->ARG1][0]; return $input_handle; }, # This callback is invoked when data is seen on Term::ReadLine's # input filehandle. It clears the $waiting_for_input flag. # This causes run_while() to return in "term_readline_waitfunc". term_readline_readable => sub { $waiting_for_input = 0; }, }, ); $self; } sub with_Reflex { my $self = _new(@_); $self->trl->event_loop( sub { my $input_watcher = shift(); $input_watcher->next(); }, sub { my $input_handle = shift(); my $input_watcher = Reflex::Filehandle->new( handle => $input_handle, rd => 1, ); return $input_watcher; }, ); $self; } sub with_Tk { my $self = _new(@_); $self->trl->event_loop( sub { my $data = shift; Tk::DoOneEvent(0) until $$data; $$data = 0; }, sub { # save filehandle for unhooking later. $self->{tkFH} = shift; my $data; $$data = 0; Tk->fileevent($self->{tkFH}, 'readable', sub { $$data = 1 }); $data; }, ); $self->{_cleanup} = sub { my $s = shift; Tk->fileevent($s->{tkFH}, 'readable', ""); }; $self; } sub DESTROY { my $self = shift; local $@; eval { $self->trl->event_loop(undef); $self->{_cleanup}->($self) if $self->{_cleanup}; }; } sub trl { my $self = shift; $self->{_term}; } our $AUTOLOAD; sub AUTOLOAD { (my $f = $AUTOLOAD) =~ s/.*:://; no strict 'refs'; *{$f} = sub { shift->trl()->$f(@_); }; goto &$f; } 1; # End of Term::ReadLine::Event __END__ =pod =encoding UTF-8 =head1 NAME Term::ReadLine::Event - Wrappers for Term::ReadLine's new event_loop model. =head1 VERSION version 0.05 =head1 SYNOPSIS use AnyEvent; use Term::ReadLine::Event; my $term = Term::ReadLine::Event->with_AnyEvent('...'); my $input = $term->readline('Prompt >'); =head1 DESCRIPTION Provides many of the event loop interactions shown in the examples provided as a small change to your code rather than the longer code required. This may actually be sufficient for your use, or it may not. This likely depends on the loop being used. =head1 HISTORY This project started with a goal: to get L working under L. Since L used L, I thought that getting L to use AnyEvent instead of Tk directly would be a win. Conversations ensued, and it sounded like P5P generally liked the idea, but didn't want anything AnyEvent-specific when it could be more generic than that. Through a couple of iterations, the event_loop interface was born. However, since this was no longer as simple as the old Tk interface (C<$term-EtkRunning(1)>), I thought it would need some examples. Putting examples into the perl core seemed a bit strange, partly due to the extra overhead involved in updating them, so having a distribution on CPAN full of examples seemed to make sense. When looking for information on a module, my first place to look is CPAN, so that made sense to me. I then got some examples from other event loop developers (Paul "LeoNerd" Evans and Rocco Caputo), and added them to my AnyEvent and Coro examples, and that was the first release. If others submit further examples, I will add them and maybe this will grow. Even if it doesn't, as long as it helps people integrate Term::ReadLine into their event-loop-based apps, whether that's Reflex or POE or AnyEvent or Coro or even using select loops to just do some background processing, then it will have met its goal. Having compiled a list of examples, I then realised that most of the examples could be abstracted into a module. Again, directly into Term::ReadLine would be too specific for P5P, but just perfect for a distribution. If you choose to install this distribution instead of just swiping the example that suits you, you can integrate with an event loop in a single line (for L, that's significant savings, for L, not so much). Note that the examples are more complete than the test files as well. This is largely because testing full readline functionality in an automated fashion is not entirely trivial. The hope would be that anyone making changes to Term::ReadLine in the future be able to use these examples in their own testing to ensure they don't break anything. (If you do, be sure you're testing with L and L, testing with just L will miss things.) Hopefully, this makes it trivial enough to use event loops with T::RL such that anyone can do it. You can get back to focusing on your business logic instead of arcane rituals to get event loops integrated with Term::ReadLine. And hopefully that means we get more perl apps that have nice interfaces (with readline support), even when doing other things in the background (events). It is not my intention to provide wrappers for all event loops. It is my intention only to provide examples for as many loops as possible. The wrappers are intended to be examples as well (though covered under Artistic/GPL licensing whereas the examples themselves are more permissive), and not necessarily used as-is, partially due to their size (all the wrappers are in a single module). That being said, I would likely just use the wrapper because I'm too lazy to copy the examples into my code. If I were entirely concerned about RAM usage, I'd be writing in C. =head1 METHODS All constructors (C*) take as their first parameter one of: =over 4 =item * The name of the application, which gets passed in to Term::ReadLine's constructor. my $term = Term::ReadLine::Event->with_Foo('myapp'); =item * An array ref consisting of the name of the application, and the input and output filehandles. This is useful if you need to override these filehandles. my $term = Term::ReadLine::Event->with_Foo(['myapp', \*STDIN, \*STDOUT]); =item * A pre-constructed Term::ReadLine object. This is useful if you need to do other things with the Term::ReadLine object prior to setting up the event loop, or if you have a custom package derived from Term::ReadLine and you do not want Term::ReadLine::Event to create the default type. my $term = Term::ReadLine::Special->new('myapp', other => 'stuff'); $term = Term::ReadLine::Event->with_Foo($term); =back Parameters for setting up the event loop, if any are required, will be after this first parameter as named parameters, e.g.: Term::ReadLine::Event->with_IO_Async('myapp', loop => $loop); All constructors also assume that the required module(s) is(are) already loaded. That is, if you're using with_AnyEvent, you have already loaded AnyEvent (and thus the event loop it is using); if you're using with_POE, you have already loaded POE, etc. =head2 with_AnyEvent Creates a L object and sets it up for use with L. =head2 with_Coro Creates a L object and sets it up for use with L. =head2 with_IO_Async Creates a L object and sets it up for use with L. Parameters: =over 4 =item loop The IO::Async loop object to integrate with. =back =head2 with_POE Creates a L object and sets it up for use with L. =head2 with_Reflex Creates a L object and sets it up for use with L. =head2 with_Tk Creates a L object and sets it up for use with L. =head2 DESTROY During destruction, we attempt to clean up. Note that L does not like to have a second T::RL object created in the same process. This means that you should only ever let the object returned by the constructors to go out of scope when you will I. This largely makes destruction moot, but it can be nice in some scenarios to clean up after oneself. =head2 trl Access to the Term::ReadLine object itself. Since Term::ReadLine::Event is not a Term::ReadLine, but HAS a Term::ReadLine, this gives access to the underlying object in case something isn't exposed sufficiently. This should not be an issue since T::RL::E automatically maps any call it doesn't recognise directly on to the underlying T::RL. =head1 AUTHOR Darin McBride, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. 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 Term::ReadLine::Event You can also look for information at: =over 4 =item * RT: CPAN's request tracker (report bugs here) L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 ACKNOWLEDGEMENTS =over 4 =item Paul "LeoNerd" Evans For all the examples (IO-Async, IO-Poll, select, and fixes for AnyEvent). =item Rocco Caputo For a final patch to Term::ReadLine that helps reduce the number of variables that get closed upon making much of this easier to handle. For the POE and Reflex examples, and a push to modularise the examples. =item P5P For genericising my initial AnyEvent patch idea. =back =head1 AUTHOR Darin McBride =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2015 by Darin McBride and others. 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