Net::REPL::Client: Allow read/print portions to be overridden separately.
[thrasher.git] / perl / lib / Net / REPL / Client.pm
blobce1031ab586a3f6acee5164e40a931925336caae
1 package Net::REPL::Client;
2 use strict;
3 use warnings;
5 =pod
7 =head1 NAME
9 Net::REPL::Client - ReadLine + Socket to Eval Server = interactive development
11 =head1 DESCRIPTION
13 The client object connects L<Term::ReadLine> to a socket that speaks
14 the L<Net::REPL::Server> protocol. Perl code is sent over the socket
15 to be eval'd in the server process, which sends back a result for the
16 client to print.
18 Like the Server, this class can be used as-is or superclassed to
19 provide additional capabilities.
21 =head2 Methods
23 =cut
25 use base qw(Net::REPL::Base);
27 use IO::Socket::INET;
28 use Term::ReadLine;
30 =head3 new(argument => value...)
32 Arguments:
34 =over
36 =item C<socket_args>
38 Hashref of arguments for L<IO::Socket::INET>.
40 =item C<debug>
42 Debug level (refer to L<Net::REPL::Base>).
44 =back
46 =cut
48 sub new {
49 my $proto = shift;
50 my $class = ref($proto) || $proto;
51 my $self = {
52 'socket_args' => {},
53 'prompt' => '',
54 @_,
56 bless($self, $class);
58 my $client = IO::Socket::INET->new(
59 'Proto' => 'tcp',
60 %{$self->{'socket_args'}},
62 if (not $client) {
63 die("Can't connect client: $!\n");
65 $self->debug("Client PID $$ connected");
67 $self->{'fh'} = $client;
69 $self->setup_io();
71 return $self;
74 sub DESTROY {
75 my ($self) = @_;
77 if ($self->{'fh'}) {
78 $self->{'fh'}->close();
82 =head3 interact()
84 Run one iteration of the REPL. Returns true if the socket remains open
85 for further iterations.
87 Blocks until a line of input can be read from the terminal, sent to
88 the server, and the result received and printed.
90 =cut
92 sub interact {
93 my ($self) = @_;
95 my $input = $self->read();
96 if (not defined($input)) {
97 return 0;
99 elsif ($input eq '') {
100 return 1;
103 my $output;
104 if ($input =~ m{^/(.*)$}) {
105 # $input is a local command
106 my ($cmd, $input) = split(/\s+/, $1);
107 if ($cmd eq 'local_eval') {
108 my @output = $self->formatted_eval($input);
109 $output = "@output";
111 elsif ($cmd eq 'exit') {
112 return 0;
115 else {
116 # $input is for remote eval
117 $self->lv_send($input);
118 $output = $self->lv_receive();
119 if (not defined($output)) {
120 # Server has gone away?
121 return 0;
125 $self->print($output, "\n");
126 return 1;
129 =head3 setup_io()
131 Create the input source for L<read>() and output for L<print>().
133 =cut
135 sub setup_io {
136 my ($self) = @_;
138 $self->{'term'} ||= Term::ReadLine->new($self->{'prompt'});
139 $self->{'out_fh'} = $self->{'term'}->OUT();
142 =head3 read()
144 Read and return one line of user input.
146 =cut
148 sub read {
149 my ($self) = @_;
151 my $prompt = $self->{'prompt'} . '> ';
152 return $self->{'term'}->readline($prompt);
155 =head3 print()
157 Output the result string(s) from one L<interact>() iteration.
159 =cut
161 sub print {
162 my ($self, @output) = @_;
164 for my $s (@output) {
165 print { $self->{'out_fh'} } $s;