CUFP
FreeBeerReekingMonk
There is more than one way to Choose your own Adventure.<P>
Abstract: Self contained single thread Perl web<strike>browser</strike>server with [http://perldancer.org/|dancer]-like URL paths and [https://mustache.github.io/mustache.5.html|moustache]-like templates ... but only using core modules. It uses [https://learn.jquery.com/about-jquery/how-jquery-works/|jquery] and [http://getbootstrap.com/getting-started/|bootstrap] Contains a simple way to write text and buttons<br/>
The basic HTML template comes after <U>_END_</U> which is the end of the perl program. It is read as "<DATA>".<P>
Step 1. Download and install [http://strawberryperl.com/|Perl for Windows].<br/><br/>
Step 2. Copy this file as story.pl to a directory on your harddrive.<br/><br/>
Step 3. You can run the story.pl Perl script by double clicking on it. It will ask for Windows Firewall permissions. Make sure only "Private Networks" is selected and "Public networks" is not. Then press on "Allow access". Oh, and download an image from the web, call it title.jpg and put it in the same directory.<br/><br/>
Step 4. Open a browser (preferably Firefox or Chrome, those do not complain about [HTTP://localhost] being a security risk)<br/><br/>
Step 5. Point your browser to: [HTTP://localhost:1337]<br/>
<br/>
<br/>
Here is the code, scroll to the bottom for a "save" button<br/><br/><P>
<code>
use strict; use warnings;
use IO::Socket;
my $DEBUG = 1;
print "STARTING($0)\n" if $DEBUG;
# first we define our story:
our %I;
sub get {$I{'GET'}{$_[0]} = $_[1]}
my $DEFAULTS = '*';
get $DEFAULTS => {
'title' => "The Foo of the Bar Baz",
'label' => 'story',
'paragraph' => 'story',
'link' => '/',
'content-page' => qq(
<div class="clearfix"></div>
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<div class="page">
<div class="title">
<h2>{{title}}</h2>
</div>
<div class="clearfix"></div>
<div id="paragraph">
{{paragraph}}
</div>
<div class="menupanel">
<br/>
<div class="menu">
{{#button}}
<a href="{{link}}" class="btn btn-primary" role="button">{{label}}</a>
{{/button}}
</div>
</div>
</div>
</div>
</div>
),
# Before and after parsing
'(' => sub {my($page,$HAVE,$msg)=@_; return $msg },
')' => sub {my($page,$HAVE,$msg)=@_;
print "msg=$msg\nDmsg=$$msg\n" if $DEBUG;
$$msg=~s{<div id="paragraph">([\s\S]*?)</div>}{PUTITBACKLATER};
$_ = $1;
if($_){
s{^\s*(\w)}{<span style="font-size: 150%"> $1</span>};
s{\b(drunkard)\b}{<span title="Just a silly ol' drunkard"><b>$1</b></span>}gi;
s{\b(pub)\b}{<span title="a tavern, really"><b>$1</b></span>}gi;
s{\b(cave)\b}{<span title="home sweat home... yup, it stinks"><b>$1</b></span>}gi;
s{\b(dragon)\b}{<span title="The protagonist of this story"><b>$1</b></span>}gi;
s{\n\n}{<br/><br/>}gi;
}
$$msg =~ s{PUTITBACKLATER}{<div id="paragraph">$_</div>};
},
};
get '/' => {
'title' => 'The story of the Thirsty Dragon',
'paragraph' => "In a little cave, there once lived a dragon. It was a scary dragon. Somewhat temperamental. And around it's abodes, there were small piles of skeletons. It was afternoon. And the dragon was thirsty.",
'&' => sub {
my($page,$HAVE,$msg)=@_;
$I{'HAVE'}{'STARTED'}++;
$msg;
},
'button' => [
{'label' => 'Go out and drink',
'link' => '/outside'},
{'label' => 'Stay inside',
'link' => '/sleep'},
],
};
get '/sleep' => {
'title' => 'sleepy...',
'paragraph' => "And night fell, and the dragon got sleepy. He rolled up and fell asleep. Kind of a boring story, don't you think? How about trying again?",
'&' => sub {
my($page,$HAVE,$msg)=@_;
if($I{'HAVE'}{'STARTED'} > 1){
$I{$HAVE}{$page}->{'button'}[0]->{'label'} = "Not AGAIN! ($I{HAVE}{STARTED})";
}
$msg;
},
'button' => [
{'label' => 'Try again',
'link' => '/'},
],
};
get '/outside' =>{
'title' => 'Where to?',
'paragraph' => "Yes, well... I don't mind going to the pub, but I could also go to the lake.",
'button' => [
{'label' => 'Go to the Pub',
'link' => '/pub'},
{'label' => 'Go to the Lake',
'link' => '/lake'},
],
};
get '/pub' =>{
'title' => 'Dragons not welcome',
'paragraph' => "I will have to be careful though. Those humans do not like dragons. And the pub is full of humans. Now, how do I disguise myself?",
'button' => [
{'label' => 'Put on a wig and walk like a chicken',
'link' => '/pubfrontchicken'},
{'label' => 'put on a hat and walk on back legs only',
'link' => '/pubfrontlegs'},
],
};
get '/pubfrontlegs' =>{
'title' => 'A gentleman with a hat',
'paragraph' => "The dragon produces a gentleman's hat and puts it on his head. he stands on his back legs, and starts walking towards the village. On the way, he picks up a stick, which he uses as a cane. Suddenly he bumps into a drunkard.
- Hic... pardon kind sir - And the drunkard walks off, a bottle in his hand. You are now in a back alley, next to the pub. You can hear laughter and loud noises. Want to try walking in from the main entrance, or use the back door?",
'button' => [
{'label' => 'Make a pompous entrance through the main gate...door...whatever',
'link' => '/grandentrance'},
{'label' => 'sneak in through the side window',
'link' => '/sneak'},
],
};
# http://grammarist.com/spelling/toward-towards/
get '/pubfrontchicken' =>{
'title' => 'Walk like an Egy^H^H^Hchicken',
'paragraph' => "This wig is too small, it barely covers the top of the head of the Dragon. But that was all the props he had. He took a deep breath, flapped his arms and walked towards the village. Near the village the dragon also started bobbing his head, like chickens do. A drunkard gazed with open mouth and dropped his bottle. But nobody else noticed. You are now in a back alley, next to the pub. You can hear laughter and loud noises. Want to try walking in from the main entrance, or use the back-door?",
'button' => [
{'label' => 'Make a pompous entrance through the main gate...door...whatever',
'link' => '/grandentrance'},
{'label' => 'sneak in through the side window',
'link' => '/sneak'},
],
};
get '/grandentrance' =>{
'title' => 'The grand entrance',
'paragraph' => "The dragon takes a deep breath, and walks into the pub bobbing his head and flapping his arms. The bartender looks up and points to a sign. The sign says 'No chickens allowed'. Patrons are standing up towards you and shooing you out. Now you are standing in the back alley. There is this other option: Use the window!",
'button' => [
{'label' => 'sneak in through the side window',
'link' => '/sneak'},
],
};
get '/sneak' =>{
'title' => 'sneaking in',
'paragraph' => "This window is too small for dragons. You try to twist and shove. The window cracks and bends... you are stuck",
'button' => [
{'label' => 'look around',
'link' => '/lookaround'},
],
};
get '/lake' =>{
'title' => 'Where to?',
'paragraph' => "Ah well... water is refreshing, although a bit boring. The dragon drank, then returned to the cave to sleep",
'button' => [
{'label' => 'Go back to the cave',
'link' => '/sleep'},
],
};
get '/404' => sub {
'Sorry. This page has not been created yet. (press back on your browser)'
};
# Some basic types we can serve. You can add your own of course.
my %TYPES = (
'PL' => "text/html",
'HTM' => "text/html",
'HTML' => "text/html",
'JS' => "text/javascript",
'CSS' => "text/css",
'ICO' => "image/vnd.microsoft.icon",
"GIF" => "image/gif",
"JPEG" => "image/jpeg",
"JPG" => "image/jpeg",
"BMP" => "image/bmp",
"PNG" => "image/png",
"SVG" => "image/svg+xml",
"SVGZ" => "image/svg+xml",
'TXT' => "text/plain",
);
my %CFG = (
'MYPORT' => 1337,
'MAXCONNECT' => 10
);
# read the html template at the end of this file (after __DATA__) in one gulp
{
local $/; $CFG{'TEMPLATE'} = <DATA>;
close DATA;
};
my %ESC_LIST = ('&'=>'&', '>'=>'>', '<'=>'<');
# open a listening port on your computer. Note: ports under 1000 require Administrator/superuser rights.
my $server = IO::Socket::INET->new(
LocalPort => $CFG{'MYPORT'},
Type => SOCK_STREAM,
Reuse => 1,
Listen => $CFG{'MAXCONNECT'},
Timeout => 2
) or die "Can not open port $CFG{'MYPORT'}: $!\n";
# Automatically start a webbrowser to http://localhost:$CFG{'MYPORT'}
if( ($0 =~/.exe$/i) && ($^O eq 'MSWin32') ){
system("start","http://localhost:$CFG{'MYPORT'}");
}
# This is an infinite loop. (press control C on the commandline to stop the webserver)
while(1){
my ($client,$client_adress,%request,$type);
while ( ($client,$client_adress) = $server->accept() ) {
my $DATA = '';
my ($client_port, $client_iaddr) = sockaddr_in($client_adress);
my $client_dot_ip = inet_ntoa($client_iaddr);
my $client_name = gethostbyaddr($client_iaddr, AF_INET);
print "\nAccepting $client_dot_ip:$client_port [$client_name] (".(scalar localtime).")\n" if $DEBUG;
eval {
local $SIG{ALRM} = sub { die 'TIMEOUT'; };
alarm 1; # does not work, default is 5 seconds... hardcoded somewhere
$client->recv($_,2048);
alarm 0;
};
alarm 0;
print "$$ Read ".length($_)." bytes:$_\n" if $DEBUG;
# Normally, here we would fork and let the child process (new process)
# do all the work, while the program is free to keep listening to new connections
# As webbrowsers make several connections at once, this is almost a requirement
# but makes the program harder to understand.
if($_){
m{^\s*(\w+)\s+(.+)\s+HTTP/(\d.\d)};
$request{METHOD} = uc $1; # GET
$request{URL} = $2; # /
$request{KEEPALIVE} = 1 if m{Keep-Alive}i;
$_ = $request{URL};
$_ = '/404' if m/\.\./; # not so waterproof way of not accessing any file on disk
$_ = '/' if m/^index.html$/i;
if(m/\./){
$DATA = slurp('./'.$_) || '';
s/.*\.//; # leave extension only
$type = uc($_); # the type of file is the extension name
}else{
$DATA = jap($request{METHOD},$_);
$type = "HTML";
}
}
my $HEADER = http_header("OK",length($DATA)||0,$TYPES{$type}, $request{KEEPALIVE});
print "$HEADER\n" if $DEBUG;
print $client $HEADER . $DATA;
if($request{KEEPALIVE}){
print "KEEPALIVE\n" if $DEBUG;
}else{
close($client);
print "CONNECTION CLOSED\n" if $DEBUG;
}
}
}
# setup an http header string
sub http_header {
my ($returnvalue, $size, $ctype, $keepalive) = @_;
my $HEADER = "HTTP/1.0 " . $returnvalue . Socket::CRLF
. ($ctype ? "Content-Type: ". $ctype ."; charset=utf-8". Socket::CRLF : '')
. (defined($size)? "Content-Length: ". $size . Socket::CRLF : '')
. "Connection: ". ($keepalive?'Keep-Alive':'close') . Socket::CRLF
. Socket::CRLF;
return $HEADER;
}
sub slurp{
return "" unless -r $_[0];
open(IN,'<:raw',$_[0]) or return ""; # Windows requires binmode
local $/;
my $X = <IN>;
close IN;
$X
}
sub jap{
my($HAVE,$page) = @_;
my $msg;
$page = '/404' unless $I{$HAVE}{$page};
my $me = $I{$HAVE}{$page};
my $default = $I{$HAVE}{$DEFAULTS};
print "page=$page;me=$me;\n" if $DEBUG;
# Run the default before
if(ref($default) eq 'HASH'){
&{$_}($page,$HAVE,\$msg) if $_=$default->{'('};
}
# First load template, then use sub code
if(ref($me) eq 'HASH'){
&{$_}($page,$HAVE,\$msg) if $_=$me->{'('};
if(($_=$me->{_}) && -f $_){
$msg = slurp $_
}elsif($_=$me->{'#'}){
$msg = $_;
}
if($_=$me->{'&'}){
$msg = &{$_}($page,$HAVE,$msg);
}
&{$_}($page,$HAVE,\$msg) if $_=$me->{')'};
}
# If there is no msg, use the appended template
$msg = $CFG{'TEMPLATE'} unless $msg;
# use the function as a post processing or independant $msg generator
if(ref($_=$me) eq 'CODE'){
$msg = &{$_}($page,$HAVE,$msg);
}
# Moustache
$msg = TinyMoustache($msg, $me, $default);
# Run the default after
if(ref($default) eq 'HASH'){
&{$_}($page,$HAVE,\$msg) if $_=$default->{')'};
}
return $msg;
}
# $escaped_string = esc("string>with<possible>html")
sub esc {
$_ = $_[0];
s/([&<>])/$ESC_LIST{$1}/gex;
$_
}
# template language; a subset of Moustache
# Escaped: {{var}}
# Non-Escaped: {{{var}}}
# Loops: {{#loop}} here add {{{var}}} or {{var}} then {{/loop}}
sub TinyMoustache {
my($TXT, $ME, $DEFAULTS) = @_;
my $i = 1;
do{
$TXT =~ s/\{\{([\#\^])\s*([\w\-]+)\s*\}\}([\s\S]*?)\{\{\/\s*\2\s*\}\}/loop($1,$2,$3,($ME->{$2}||$DEFAULTS->{$2}||""))/gem;
$TXT =~ s/\{\{\{\s*([\w\-]+)\s*\}\}\}/$ME->{$1}||$DEFAULTS->{$1}||""/gme;
$TXT =~ s/\{\{\s*([\w\-]+)\s*\}\}/esc($ME->{$1}||$DEFAULTS->{$1}||"")/gme unless $TXT=~/\{\{#/;
}while($TXT=~/\{\{/ && $i--);
$TXT;
}
# handle moustache {{^tag}} or {{#tag}} ... {{/tag}}
sub loop {
my($n,$a,$b,$t,$r)=@_;
print "loop($n,$a,$b,$t,$r)\n" if $DEBUG;
return ($t ? '' : $b) if $n eq '^';
$t = &$t($a,$b) if ref($t) eq 'CODE';
if(ref($t) eq 'ARRAY'){
print " loop ARRAY @$t\n" if $DEBUG;
$DEBUG && print " >> $_\n" for (@$t);
for my $x (@$t){
$r .= TinyMoustache($b,$x);
}
#$r .= TinyMoustache($b,$_) for @$t;
}elsif(ref($t) eq 'SCALAR'){
$r = $t;
}elsif(ref($t) eq 'SUB'){
$r = &$t($a,$b,$t);
}
$r;
}
__DATA__
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<!-- Meta, title, CSS, favicons, etc. -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{title}}</title>
<!-- Latest compiled and minified Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<!-- Font Awesome not used -->
<!-- Custom Theme Style -->
<link href="/css/custom.css" rel="stylesheet">
</head>
<body class="nav-md">
<div class="container body">
<div class="main_container">
<!-- page content -->
<div class="page-title">
<div class="title_left">
<img src="/title.jpg" height=50/>
</div>
</div>
{{{content-page}}}
<!-- footer content -->
<footer>
<div class="pull-right"> Story by Foo Bar </div>
</footer>
<!-- /footer content -->
<!-- /page content -->
</div>
</div>
<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
<!-- Latest compiled Bootstrap JavaScript -->
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<!-- Custom Theme Scripts (does not exist) -->
<script src="/js/custom.js"></script>
</body>
</html>
</code><P>
Ok, hopefully you made that work. Now, let's compile it to EXE!<P>
6. Drop to the cmd command shell and go to your directory with story.pl<br/><br/>
7. use pp to compile it:
<c>pp -o story.exe story.pl</c><br/><br/>
8. Unfortunately, the executable requires extra code to run (on other machines that do not have Perl installed), you need the following files:<br/>
* perl524.dll (might have another version number, that is ok)<br/>
* libstdc++-6.dll<br/>
* libwinpthread-1.dll<br/><P>
To know where these files are located, use the following commandline command: <c>where perl</c><P>
you can ship your story.exe program (the story.pl is not required, it's inside the story.exe) with these DLL's<P>
Have fun perusing and editing!<P>
edits: incorporated windows check as suggested by afoken.
<!-- Wiki2Monks {"version":1.1416} -->