use 5.026;
use strict;
use warnings;
use experimental 'signatures';
package Model {
use Moo;
use experimental 'signatures';
has _words => ( is => 'ro', default => sub { +{} } );
sub add_words($self,$words) {
++$self->_words->{$_} for $words->@*;
return;
}
sub get_words($self) {
return +{ $self->_words->%* };
}
sub get_pairs($self) {
my $w = $self->_words;
return [ map { [ $_, $w->{$_} ] } keys $w->%* ];
}
sub get_most_common_pairs($self,$how_many) {
my $pairs = $self->get_pairs;
my @ranked_pairs = reverse sort { $a->[1] <=> $b->[1] } $pairs->@*;
splice @ranked_pairs,$how_many;
return \@ranked_pairs;
}
};
package View {
use Moo;
use experimental 'signatures';
sub words_from_input($self,$input) {
my @words = grep { length } split /\s+/, $input;
return \@words;
}
sub table_from_ranked_pairs($self,$ranked_pairs) {
return join "\n", map {
sprintf '%-15s %3d', $_->@*;
} $ranked_pairs->@*;
}
};
package HTTPController {
use Moo;
use experimental 'signatures';
use HTTP::Response;
has model => ( is => 'ro', required => 1 );
has view => ( is => 'ro', required => 1 );
sub on_request($self,$req) {
if ($req->method eq 'GET') {
$self->get_words($req);
}
elsif ($req->method eq 'POST') {
$self->post_words($req);
}
}
sub _text_response($self,$req,$text) {
my $response = HTTP::Response->new( 200 );
$response->add_content( "$text\n" );
$response->content_type( "text/plain" );
$response->content_length( length $response->content );
$req->respond($response);
}
sub get_words($self,$req) {
my $how_many = $req->query_param('n') || 10;
my $most_common_pairs = $self->model->get_most_common_pairs($how_many);
my $table = $self->view->table_from_ranked_pairs($most_common_pairs);
$self->_text_response($req,$table);
return;
}
sub post_words($self,$req) {
my $body = $req->body;
my $words = $self->view->words_from_input($body);
$self->model->add_words($words);
$self->_text_response($req,'ok');
}
};
package LineController {
use Moo;
use experimental 'signatures';
has model => ( is => 'ro', required => 1 );
has view => ( is => 'ro', required => 1 );
sub on_command($self,$stream,$command,$args_string) {
if ($command eq 'get') {
$self->get_words($stream,$args_string);
}
elsif ($command eq 'put') {
$self->put_words($stream,$args_string);
}
else {
$self->_reply($stream,"bad command, only 'get' and 'put'");
}
}
sub _reply($self,$stream,$text) {
$stream->write("$text\n");
}
sub get_words($self,$stream,$args_string) {
my $how_many = $args_string || 10;
my $most_common_pairs = $self->model->get_most_common_pairs($how_many);
my $table = $self->view->table_from_ranked_pairs($most_common_pairs);
$self->_reply($stream,$table);
return;
}
sub put_words($self,$stream,$args_string) {
my $words = $self->view->words_from_input($args_string);
$self->model->add_words($words);
$self->_reply($stream,'ok');
}
};
package TextServer {
use Moo;
use experimental 'signatures';
use IO::Async::Listener;
extends 'IO::Async::Notifier';
has listener => ( is => 'lazy', handles => ['listen'] );
sub _build_listener($self) {
my $listener = IO::Async::Listener->new(
on_stream => sub ($l,$stream,@) {
$stream->configure(
on_read => sub($stream,$buffref,$eof) {
while( $$buffref =~ s/^(.*\n)// ) {
$self->handle_command($stream,$1);
}
return 0;
},
);
$self->add_child($stream);
},
);
$self->add_child($listener);
return $listener;
}
has on_command => ( is => 'rwp' );
sub configure($self,%params) {
$self->_set_on_command(delete $params{on_command});
return $self->next::method(%params);
}
sub handle_command($self,$stream,$command_line) {
my ($command,$args) = split /\s+/,$command_line,2;
$self->on_command->($stream,$command,$args);
return;
}
};
use Net::Async::HTTP::Server;
use IO::Async::Loop;
my $model = Model->new();
my $view = View->new();
my $loop = IO::Async::Loop->new();
my $httpcontroller = HTTPController->new({
model => $model,
view => $view,
});
my $httpserver = Net::Async::HTTP::Server->new(
on_request => sub($self,$req,@) {
$httpcontroller->on_request($req);
},
);
$loop->add( $httpserver );
my $linecontroller = LineController->new({
model => $model,
view => $view,
});
my $textserver = TextServer->new(
on_command => sub($stream,$command,$args_string) {
$linecontroller->on_command($stream,$command,$args_string);
},
);
$loop->add( $textserver );
$httpserver->listen(
addr => { family => "inet6", socktype => "stream", port => 8080 },
)->retain;
$textserver->listen(
addr => { family => "inet6", socktype => "stream", port => 2020 },
)->retain;
$loop->run;