Compare commits
No commits in common. 'gh-pages' and 'master' have entirely different histories.
@ -0,0 +1,4 @@
|
||||
# See http://bitbucket.org/haukex/htools/src/HEAD/htmlrescache
|
||||
# Set up via: htmlrescache -cweb/_cache init
|
||||
/web/*.html filter=htmlrescache
|
||||
/web/**/*.html filter=htmlrescache
|
||||
@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the issue, including any relevant code in the form of a representative [Short, Self Contained, Correct (Compilable), Example](http://sscce.org/), and sample input.
|
||||
|
||||
**Expected behavior vs. actual behavior**
|
||||
A clear and concise description of what you expected to happen, and what actually happened instead. Include expected output for the sample input given above, and the actual output you're getting including exact copies of any error messages.
|
||||
|
||||
**Versions**
|
||||
- Device: [desktop or mobile; specify model]
|
||||
- OS: [e.g. Ubuntu Linux 16.04, Windows 10, etc.]
|
||||
- Browser and version: [e.g. Firefox 61, Chrome 68, etc.]
|
||||
- WebPerl: [e.g. v0.01-beta]
|
||||
|
||||
If building:
|
||||
- Perl: [e.g. v5.26.2]
|
||||
- Emscripten: [e.g. 1.38.10]
|
||||
- Any other versions that may be relevant, such as compiler, libraries, etc.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here. If applicable, add screenshots to help explain your problem.
|
||||
@ -1,3 +1,13 @@
|
||||
/web/
|
||||
/*.zip
|
||||
/work/
|
||||
/emperl5/
|
||||
/web/emperl.js
|
||||
/web/emperl.wasm
|
||||
/web/emperl.data
|
||||
/web/_cache/
|
||||
/pages/
|
||||
/wiki/
|
||||
# For experimental P6 support:
|
||||
/web/perl6.js
|
||||
/web/6demo.html
|
||||
/web/test6.html
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
|
||||
WebPerl Changelog
|
||||
=================
|
||||
|
||||
|
||||
2019-08-03: v0.11-beta
|
||||
----------------------
|
||||
|
||||
- Updated for Emscripten 1.38.31 / latest Fastcomp (1.38.40) and Perl v5.30.0
|
||||
|
||||
|
||||
2019-03-03: v0.09-beta
|
||||
----------------------
|
||||
|
||||
- Updated for Emscripten 1.38.28 and Perl v5.28.1
|
||||
- Added experimental Perl 6 support
|
||||
- Added modules Future, Digest::MD5, and Digest::SHA
|
||||
- Added Perl.exitStatus
|
||||
- Updated regex_tester.html
|
||||
- Added "Code Demo Editor" in web/democode/
|
||||
- Added "cpanfile"s for dependencies
|
||||
- Minor fixes and updates to build.pl
|
||||
|
||||
|
||||
2018-09-04: v0.07-beta
|
||||
----------------------
|
||||
|
||||
- Updated regex_tester.html (improvements and bugfixes)
|
||||
- Added WebPerl::JSObject::jscode()
|
||||
|
||||
|
||||
2018-09-02: v0.05-beta
|
||||
----------------------
|
||||
|
||||
- Added Perl.addStateChangeListener and deprecated Perl.stateChanged
|
||||
- Added WebPerl::js_new()
|
||||
- Added regex_tester.html
|
||||
- Added Perl.noMountIdbfs
|
||||
- A few other minor fixes and updates
|
||||
|
||||
|
||||
2018-08-14: v0.03-beta
|
||||
----------------------
|
||||
|
||||
- Fixed an issue with WebPerl::JSObject::toperl()
|
||||
where JS objects were not being converted properly.
|
||||
- Added AJAX demo
|
||||
- Added WebPerl autoloading for script tags
|
||||
- Various small changes, bugfixes and enhancements
|
||||
(mostly not user-visible)
|
||||
- Added `runtests.html` and `experiments` dir
|
||||
|
||||
|
||||
2018-08-12: v0.01-beta
|
||||
----------------------
|
||||
|
||||
- First public release
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,158 @@
|
||||
NAME
|
||||
perlartistic - the Perl Artistic License
|
||||
|
||||
SYNOPSIS
|
||||
You can refer to this document in Pod via "L<perlartistic>"
|
||||
Or you can see this document by entering "perldoc perlartistic"
|
||||
|
||||
DESCRIPTION
|
||||
Perl is free software; you can redistribute it and/or modify it under
|
||||
the terms of either:
|
||||
|
||||
a) the GNU General Public License as published by the Free
|
||||
Software Foundation; either version 1, or (at your option) any
|
||||
later version, or
|
||||
|
||||
b) the "Artistic License" which comes with this Kit.
|
||||
|
||||
This is "The Artistic License". It's here so that modules, programs,
|
||||
etc., that want to declare this as their distribution license can link
|
||||
to it.
|
||||
|
||||
For the GNU General Public License, see perlgpl.
|
||||
|
||||
The "Artistic License"
|
||||
Preamble
|
||||
The intent of this document is to state the conditions under which a
|
||||
Package may be copied, such that the Copyright Holder maintains some
|
||||
semblance of artistic control over the development of the package, while
|
||||
giving the users of the package the right to use and distribute the
|
||||
Package in a more-or-less customary fashion, plus the right to make
|
||||
reasonable modifications.
|
||||
|
||||
Definitions
|
||||
"Package"
|
||||
refers to the collection of files distributed by the Copyright
|
||||
Holder, and derivatives of that collection of files created through
|
||||
textual modification.
|
||||
|
||||
"Standard Version"
|
||||
refers to such a Package if it has not been modified, or has been
|
||||
modified in accordance with the wishes of the Copyright Holder as
|
||||
specified below.
|
||||
|
||||
"Copyright Holder"
|
||||
is whoever is named in the copyright or copyrights for the package.
|
||||
|
||||
"You"
|
||||
is you, if you're thinking about copying or distributing this
|
||||
Package.
|
||||
|
||||
"Reasonable copying fee"
|
||||
is whatever you can justify on the basis of media cost, duplication
|
||||
charges, time of people involved, and so on. (You will not be
|
||||
required to justify it to the Copyright Holder, but only to the
|
||||
computing community at large as a market that must bear the fee.)
|
||||
|
||||
"Freely Available"
|
||||
means that no fee is charged for the item itself, though there may
|
||||
be fees involved in handling the item. It also means that recipients
|
||||
of the item may redistribute it under the same conditions they
|
||||
received it.
|
||||
|
||||
Conditions
|
||||
1. You may make and give away verbatim copies of the source form of the
|
||||
Standard Version of this Package without restriction, provided that
|
||||
you duplicate all of the original copyright notices and associated
|
||||
disclaimers.
|
||||
|
||||
2. You may apply bug fixes, portability fixes and other modifications
|
||||
derived from the Public Domain or from the Copyright Holder. A
|
||||
Package modified in such a way shall still be considered the
|
||||
Standard Version.
|
||||
|
||||
3. You may otherwise modify your copy of this Package in any way,
|
||||
provided that you insert a prominent notice in each changed file
|
||||
stating how and when you changed that file, and provided that you do
|
||||
at least ONE of the following:
|
||||
|
||||
a) place your modifications in the Public Domain or otherwise make
|
||||
them Freely Available, such as by posting said modifications to
|
||||
Usenet or an equivalent medium, or placing the modifications on
|
||||
a major archive site such as uunet.uu.net, or by allowing the
|
||||
Copyright Holder to include your modifications in the Standard
|
||||
Version of the Package.
|
||||
|
||||
b) use the modified Package only within your corporation or
|
||||
organization.
|
||||
|
||||
c) rename any non-standard executables so the names do not conflict
|
||||
with standard executables, which must also be provided, and
|
||||
provide a separate manual page for each non-standard executable
|
||||
that clearly documents how it differs from the Standard Version.
|
||||
|
||||
d) make other distribution arrangements with the Copyright Holder.
|
||||
|
||||
4. You may distribute the programs of this Package in object code or
|
||||
executable form, provided that you do at least ONE of the following:
|
||||
|
||||
a) distribute a Standard Version of the executables and library
|
||||
files, together with instructions (in the manual page or
|
||||
equivalent) on where to get the Standard Version.
|
||||
|
||||
b) accompany the distribution with the machine-readable source of
|
||||
the Package with your modifications.
|
||||
|
||||
c) give non-standard executables non-standard names, and clearly
|
||||
document the differences in manual pages (or equivalent),
|
||||
together with instructions on where to get the Standard Version.
|
||||
|
||||
d) make other distribution arrangements with the Copyright Holder.
|
||||
|
||||
5. You may charge a reasonable copying fee for any distribution of this
|
||||
Package. You may charge any fee you choose for support of this
|
||||
Package. You may not charge a fee for this Package itself. However,
|
||||
you may distribute this Package in aggregate with other (possibly
|
||||
commercial) programs as part of a larger (possibly commercial)
|
||||
software distribution provided that you do not advertise this
|
||||
Package as a product of your own. You may embed this Package's
|
||||
interpreter within an executable of yours (by linking); this shall
|
||||
be construed as a mere form of aggregation, provided that the
|
||||
complete Standard Version of the interpreter is so embedded.
|
||||
|
||||
6. The scripts and library files supplied as input to or produced as
|
||||
output from the programs of this Package do not automatically fall
|
||||
under the copyright of this Package, but belong to whoever generated
|
||||
them, and may be sold commercially, and may be aggregated with this
|
||||
Package. If such scripts or library files are aggregated with this
|
||||
Package via the so-called "undump" or "unexec" methods of producing
|
||||
a binary executable image, then distribution of such an image shall
|
||||
neither be construed as a distribution of this Package nor shall it
|
||||
fall under the restrictions of Paragraphs 3 and 4, provided that you
|
||||
do not represent such an executable image as a Standard Version of
|
||||
this Package.
|
||||
|
||||
7. C subroutines (or comparably compiled subroutines in other
|
||||
languages) supplied by you and linked into this Package in order to
|
||||
emulate subroutines and variables of the language defined by this
|
||||
Package shall not be considered part of this Package, but are the
|
||||
equivalent of input as in Paragraph 6, provided these subroutines do
|
||||
not change the language in any way that would cause it to fail the
|
||||
regression tests for the language.
|
||||
|
||||
8. Aggregation of this Package with a commercial distribution is always
|
||||
permitted provided that the use of this Package is embedded; that
|
||||
is, when no overt attempt is made to make this Package's interfaces
|
||||
visible to the end user of the commercial distribution. Such use
|
||||
shall not be construed as a distribution of this Package.
|
||||
|
||||
9. The name of the Copyright Holder may not be used to endorse or
|
||||
promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
The End
|
||||
|
||||
@ -0,0 +1,281 @@
|
||||
NAME
|
||||
perlgpl - the GNU General Public License, version 1
|
||||
|
||||
SYNOPSIS
|
||||
You can refer to this document in Pod via "L<perlgpl>"
|
||||
Or you can see this document by entering "perldoc perlgpl"
|
||||
|
||||
DESCRIPTION
|
||||
Perl is free software; you can redistribute it and/or modify it under
|
||||
the terms of either:
|
||||
|
||||
a) the GNU General Public License as published by the Free
|
||||
Software Foundation; either version 1, or (at your option) any
|
||||
later version, or
|
||||
|
||||
b) the "Artistic License" which comes with this Kit.
|
||||
|
||||
This is the "GNU General Public License, version 1". It's here so that
|
||||
modules, programs, etc., that want to declare this as their distribution
|
||||
license can link to it.
|
||||
|
||||
For the Perl Artistic License, see perlartistic.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 1, February 1989
|
||||
|
||||
Copyright (C) 1989 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The license agreements of most software companies try to keep users
|
||||
at the mercy of those companies. By contrast, our General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. The
|
||||
General Public License applies to the Free Software Foundation's
|
||||
software and to any other program whose authors commit to using it.
|
||||
You can use it for your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Specifically, the General Public License is designed to make
|
||||
sure that you have the freedom to give away or sell copies of free
|
||||
software, that you receive source code or can get it if you want it,
|
||||
that you can change the software or use pieces of it in new free
|
||||
programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of a such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must tell them their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software,
|
||||
and (2) offer you this license which gives you legal permission to
|
||||
copy, distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on,
|
||||
we want its recipients to know that what they have is not the original,
|
||||
so that any problems introduced by others will not reflect on the
|
||||
original authors' reputations.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any program or other work which
|
||||
contains a notice placed by the copyright holder saying it may be
|
||||
distributed under the terms of this General Public License. The
|
||||
"Program", below, refers to any such program or work, and a "work based
|
||||
on the Program" means either the Program or any work containing the
|
||||
Program or a portion of it, either verbatim or with modifications.
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this General Public License and to the absence of
|
||||
any warranty; and give any other recipients of the Program a copy of
|
||||
this General Public License along with the Program. You may charge a
|
||||
fee for the physical act of transferring a copy.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, and copy and distribute such modifications under the terms of
|
||||
Paragraph 1 above, provided that you also do the following:
|
||||
|
||||
a) cause the modified files to carry prominent notices stating that
|
||||
you changed the files and the date of any change; and
|
||||
|
||||
b) cause the whole of any work that you distribute or publish, that
|
||||
in whole or in part contains the Program or any part thereof,
|
||||
either with or without modifications, to be licensed at no charge
|
||||
to all third parties under the terms of this General Public License
|
||||
(except that you may choose to grant warranty protection to some or
|
||||
all third parties, at your option).
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the simplest and most usual way, to print or
|
||||
display an announcement including an appropriate copyright notice
|
||||
and a notice that there is no warranty (or else, saying that you
|
||||
provide a warranty) and that users may redistribute the program
|
||||
under these conditions, and telling the user how to view a copy of
|
||||
this General Public License.
|
||||
|
||||
d) You may charge a fee for the physical act of transferring a
|
||||
copy, and you may at your option offer warranty protection in
|
||||
exchange for a fee.
|
||||
|
||||
Mere aggregation of another independent work with the Program (or its
|
||||
derivative) on a volume of a storage or distribution medium does not
|
||||
bring the other work under the scope of these terms.
|
||||
|
||||
3. You may copy and distribute the Program (or a portion or
|
||||
derivative of it, under Paragraph 2) in object code or executable form
|
||||
under the terms of Paragraphs 1 and 2 above provided that you also do
|
||||
one of the following:
|
||||
|
||||
a) accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of
|
||||
Paragraphs 1 and 2 above; or,
|
||||
|
||||
b) accompany it with a written offer, valid for at least three
|
||||
years, to give any third party free (except for a nominal charge
|
||||
for the cost of distribution) a complete machine-readable copy of
|
||||
the corresponding source code, to be distributed under the terms of
|
||||
Paragraphs 1 and 2 above; or,
|
||||
|
||||
c) accompany it with the information you received as to where the
|
||||
corresponding source code may be obtained. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form alone.)
|
||||
|
||||
Source code for a work means the preferred form of the work for making
|
||||
modifications to it. For an executable file, complete source code
|
||||
means all the source code for all modules it contains; but, as a
|
||||
special exception, it need not include source code for modules which
|
||||
are standard libraries that accompany the operating system on which the
|
||||
executable file runs, or for standard header files or definitions files
|
||||
that accompany that operating system.
|
||||
|
||||
4. You may not copy, modify, sublicense, distribute or transfer the
|
||||
Program except as expressly provided under this General Public License.
|
||||
Any attempt otherwise to copy, modify, sublicense, distribute or
|
||||
transfer the Program is void, and will automatically terminate your
|
||||
rights to use the Program under this License. However, parties who
|
||||
have received copies, or rights to use copies, from you under this
|
||||
General Public License will not have their licenses terminated so long
|
||||
as such parties remain in full compliance.
|
||||
|
||||
5. By copying, distributing or modifying the Program (or any work
|
||||
based on the Program) you indicate your acceptance of this license to
|
||||
do so, and all its terms and conditions.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
|
||||
7. The Free Software Foundation may publish revised and/or new
|
||||
versions of the General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of the license which applies to it and "any
|
||||
later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Program does not specify a
|
||||
version number of the license, you may choose any version ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
8. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the
|
||||
author to ask for permission. For software which is copyrighted by the
|
||||
Free Software Foundation, write to the Free Software Foundation; we
|
||||
sometimes make exceptions for this. Our decision will be guided by the
|
||||
two goals of preserving the free status of all derivatives of our free
|
||||
software and of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||
EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
||||
THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS
|
||||
WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Appendix: How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to humanity, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these
|
||||
terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it
|
||||
does.>
|
||||
Copyright (C) 19yy <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License as
|
||||
published by the Free Software Foundation; either version 1, or (at
|
||||
your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA
|
||||
02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper
|
||||
mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) 19xx name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type
|
||||
'show w'. This is free software, and you are welcome to
|
||||
redistribute it under certain conditions; type 'show c' for
|
||||
details.
|
||||
|
||||
The hypothetical commands 'show w' and 'show c' should show the
|
||||
appropriate parts of the General Public License. Of course, the
|
||||
commands you use may be called something other than 'show w' and 'show
|
||||
c'; they could even be mouse-clicks or menu items--whatever suits your
|
||||
program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
program 'Gnomovision' (a program to direct compilers to make passes
|
||||
at assemblers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
theme: jekyll-theme-minimal
|
||||
title: WebPerl
|
||||
description: Run Perl in the browser with WebPerl!
|
||||
logo: webperl.png
|
||||
show_downloads: false
|
||||
exclude:
|
||||
- CNAME
|
||||
@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use 5.026;
|
||||
use FindBin ();
|
||||
use Path::Class qw/dir file/;
|
||||
|
||||
# A quick and dirty script for importing stuff from webperl/master to gh-pages
|
||||
|
||||
sub edit (&$$) {
|
||||
my ($code,$source,$dest) = @_;
|
||||
state $basedir = dir($FindBin::Bin)->parent->parent;
|
||||
local $_ = file($source)->absolute($basedir)->slurp(iomode=>'<:raw:encoding(UTF-8)');
|
||||
$code->();
|
||||
file($dest)->absolute($basedir)->spew(iomode=>'>:raw:encoding(UTF-8)', $_);
|
||||
}
|
||||
|
||||
edit {
|
||||
s{ iframe.perleditor\s*\{ [^\}]* border: \s* \K \N* (?=\n) }{1px dotted lightgrey;}xmsg==1 or die;
|
||||
s{ <!--(?<x>script\s+src="http.+?iframeResizer.min.js"[^>]+crossorigin[^>]+></script)--> }{<$+{x}>}xmsg==1 or die;
|
||||
s{ ^ \s* \K /[/*] (?= \s* iFrameResize ) }{}xmsg==2 or die;
|
||||
} 'web/democode/demo.html', 'pages/democode/index.html';
|
||||
|
||||
edit {
|
||||
s{ <!-- [^>]* \K demo.html (?= [^>]* --> ) }{index.html}xmsg==1 or die;
|
||||
s{ <!--(?<x>script\s+src="http.+?iframeResizer.contentWindow.min.js"[^>]+crossorigin[^>]+></script)--> }{<$+{x}>}xmsg==1 or die;
|
||||
} 'web/democode/perleditor.html', 'pages/democode/perleditor.html';
|
||||
|
||||
edit {
|
||||
s{ <!-- [^>]* \K demo.html (?= [^>]* --> ) }{index.html}xmsg==1 or die;
|
||||
s{ <(?<x>script\s+src="[^"]*webperl\.js"\s*></script)> }{<!--$+{x}-->}xmsg==1 or die;
|
||||
s{ <!--(?<x>script\s+src="http.+?webperl\.js"[^>]+crossorigin[^>]+></script)--> }{<$+{x}>}xmsg==1 or die;
|
||||
} 'web/democode/perlrunner.html', 'pages/democode/perlrunner.html';
|
||||
|
||||
edit {
|
||||
} 'web/democode/perleditor.css', 'pages/democode/perleditor.css';
|
||||
|
||||
edit {
|
||||
my $msg = <<'ENDMSG';
|
||||
This is essentially a copy of
|
||||
https://github.com/haukex/webperl/blob/master/web/regex_tester.html
|
||||
with the following differences:
|
||||
- webperl.js from CDN
|
||||
- $RUN_CODE_IN_IFRAME enabled
|
||||
- URL updated to https://github.com/haukex/webperl/blob/gh-pages/regex.html
|
||||
(see import_regex_tester.pl)
|
||||
ENDMSG
|
||||
s{ <(?<x>script\s+src="(?:webperl\.js|__WEBPERLURL__)"\s*></scr_*ipt)> }{<!--$+{x}-->}xmsg==2 or die;
|
||||
s{ <!--(?<x>script\s+src="http.+?webperl\.js"[^>]+crossorigin[^>]+></scr_*ipt)--> }{<$+{x}>}xmsg==2 or die;
|
||||
s{ ^ \s* our \s+ \$RUN_CODE_IN_IFRAME\s*=\s*\K[01](?=\s*;\s*) }{1}xmsg==1 or die;
|
||||
s{ https?://github.com/haukex/webperl/blob/\Kmaster/web/regex_tester.html }{gh-pages/regex.html}xmsg==1 or die;
|
||||
s{ \#\#\#\#\#\s*-->\n\K }{\n<!-- $msg-->\n}xmsg==1 or die;
|
||||
} 'web/regex_tester.html', 'pages/regex.html';
|
||||
@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use strict;
|
||||
use open qw/:std :utf8/;
|
||||
use FindBin ();
|
||||
|
||||
# Generate a preview of the site using `markdown`
|
||||
# (I use this mostly just to check for any Markdown syntax mistakes)
|
||||
#TODO Later: Use a markdown processor that handles GitHub's markdown enhancements?
|
||||
|
||||
my $dir = $FindBin::Bin.'/..';
|
||||
opendir my $dh, $dir or die $!;
|
||||
my @files = grep { ! -d } map { "$dir/$_" } sort grep {/\.md\z/i} readdir $dh;
|
||||
close $dh;
|
||||
|
||||
print <<'ENDHTML';
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl Site Preview</title>
|
||||
</head>
|
||||
<body>
|
||||
ENDHTML
|
||||
|
||||
print "<hr/>\n";
|
||||
for my $f (@files) {
|
||||
system('markdown',$f)==0
|
||||
or die "markdown failed, \$?=$?";
|
||||
print "<hr/>\n";
|
||||
}
|
||||
|
||||
print <<'ENDHTML';
|
||||
</body>
|
||||
</html>
|
||||
ENDHTML
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,497 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use 5.026;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Build script for WebPerl; see L<http://webperl.zero-g.net>.
|
||||
|
||||
build.pl [OPTIONS]
|
||||
OPTIONS:
|
||||
--showconf - Show configuration
|
||||
--reconfig - Force regeneration config.sh
|
||||
--forceext - Force fetching of extensions
|
||||
--applyconfig - Apply any changes to config.sh (sh Configure -S)
|
||||
--remakeout - Force rebuild of the output directory
|
||||
--forceemperl - Force rebuild of emperl.js
|
||||
--dist=FN - Create a distro file "FN.zip"
|
||||
--verbose - Be more verbose
|
||||
|
||||
=head1 Author, Copyright, and License
|
||||
|
||||
B<< WebPerl - L<http://webperl.zero-g.net> >>
|
||||
|
||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, L<http://www.igb-berlin.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl 5 itself: either the GNU General Public
|
||||
License as published by the Free Software Foundation (either version 1,
|
||||
or, at your option, any later version), or the "Artistic License" which
|
||||
comes with Perl 5.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
B<WITHOUT ANY WARRANTY>; without even the implied warranty of
|
||||
B<MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE>.
|
||||
See the licenses for details.
|
||||
|
||||
You should have received a copy of the licenses along with this program.
|
||||
If not, see L<http://perldoc.perl.org/index-licence.html>.
|
||||
|
||||
=cut
|
||||
|
||||
use Getopt::Long qw/ HelpMessage :config posix_default gnu_compat bundling auto_version auto_help /;
|
||||
use Hash::Util qw/lock_hash/;
|
||||
use Data::Dump;
|
||||
use Path::Class qw/file dir/;
|
||||
use IPC::Run3::Shell {show_cmd=>1}, qw/ :FATAL :run git make emmake /;
|
||||
use ExtUtils::MakeMaker qw/prompt/;
|
||||
use FindBin ();
|
||||
use Carp;
|
||||
use Cwd qw/getcwd abs_path/;
|
||||
use URI ();
|
||||
use IO::Socket::SSL 1.56 (); # for HTTP::Tiny
|
||||
use Net::SSLeay 1.49 (); # for HTTP::Tiny
|
||||
use HTTP::Tiny ();
|
||||
use Cpanel::JSON::XS qw/decode_json/;
|
||||
use File::Temp qw/tempdir/;
|
||||
use Archive::Tar ();
|
||||
use File::Copy::Recursive qw/dirmove/;
|
||||
use File::Replace qw/replace3/;
|
||||
use Pod::Strip ();
|
||||
use Archive::Zip qw/AZ_OK/;
|
||||
|
||||
GetOptions(\my %opts,'showconf','reconfig','forceext','applyconfig',
|
||||
'forceemperl','remakeout','dist=s','verbose')
|
||||
or HelpMessage(-exitval=>255);
|
||||
|
||||
# check that emperl_config.sh has been run
|
||||
die "Please run '. emperl_config.sh' to set up the environment variables.\n"
|
||||
unless $ENV{EMPERL_PERLVER};
|
||||
die "Please edit 'emperl_config.sh' to point it to the correct location of 'emsdk_env.sh'\n"
|
||||
unless $ENV{EMSCRIPTEN} && -d $ENV{EMSCRIPTEN} && $ENV{EMSDK} && -d $ENV{EMSDK};
|
||||
|
||||
# copy over config variables from environment
|
||||
my %C = map {$_=>$ENV{'EMPERL_'.$_}} qw/ EXTENSIONS
|
||||
HOSTPERLDIR OUTPUTDIR DOWNLOADDIR PERLSRCDIR
|
||||
PREFIX PERLVER
|
||||
PERL_REPO PERL_BRANCH CLOBBER_BRANCH /;
|
||||
$C{$_} = dir($C{$_}) for qw/ HOSTPERLDIR OUTPUTDIR DOWNLOADDIR PERLSRCDIR /;
|
||||
$C{EXTENSIONS} = [ split ' ', $C{EXTENSIONS} ];
|
||||
lock_hash %C; # typo prevention
|
||||
dd \%C if $opts{showconf};
|
||||
|
||||
my $VERBOSE = $opts{verbose}?1:0;
|
||||
my $needs_reconfig = !!$opts{reconfig};
|
||||
|
||||
# ##### ##### ##### Step: Patch Emscripten ##### ##### #####
|
||||
|
||||
{
|
||||
my $d = pushd( dir($ENV{EMSCRIPTEN}, 'src') );
|
||||
# Emscripten's fork() (and system()) stubs return EAGAIN, meaning "Resource temporarily unavailable".
|
||||
# So perl will wait 5 seconds and try again, which is not helpful to us, since Emscripten doesn't support those functions at all.
|
||||
# This patch fixes that on the Emscripten side, so the stubs return ENOTSUP.
|
||||
# first, we need to take a guess which version of the patch to apply.
|
||||
my $libraryjs = file($ENV{EMSCRIPTEN}, 'src', 'library.js')->slurp;
|
||||
my $patchf;
|
||||
if ( $libraryjs=~/\b\Q___setErrNo(ERRNO_CODES.\E(EAGAIN|ENOTSUP)\b/ )
|
||||
{ $patchf = 'emscripten_1.38.10_eagain.patch' }
|
||||
elsif ( $libraryjs=~/no shell available\s+setErrNo\Q({{{ cDefine('EAGAIN') }}})\E/ )
|
||||
{ $patchf = 'emscripten_1.39.16_eagain.patch' }
|
||||
elsif ( $libraryjs=~/\b\QcDefine('EAGAIN')\E/ ) # note that this appears in 1.38.1* versions too
|
||||
{ $patchf = 'emscripten_1.38.28_eagain.patch' }
|
||||
else { die "Could not figure out which library.js patch to use" }
|
||||
#TODO Later: we should probably verify the Emscripten version too, and in the future we may need different patches for different versions
|
||||
if ( try_patch_file( file($FindBin::Bin,$patchf) ) ) {
|
||||
say STDERR "# Emscripten was newly patched, forcing a rebuild";
|
||||
# not sure if the following is needed, but playing it safe:
|
||||
run 'emcc', '--clear-cache'; # force Emscripten to rebuild libs (takes a bit of time)
|
||||
$needs_reconfig=1;
|
||||
}
|
||||
}
|
||||
|
||||
# ##### ##### ##### Step: Check out Perl sources ##### ##### #####
|
||||
|
||||
if (!-e $C{PERLSRCDIR}) {
|
||||
say STDERR "# $C{PERLSRCDIR} doesn't exist, checking out";
|
||||
my $d = pushd($C{PERLSRCDIR}->parent);
|
||||
git 'clone', '--branch', $C{PERL_BRANCH}, $C{PERL_REPO}, $C{PERLSRCDIR}->basename;
|
||||
die "something went wrong with git clone" unless -d $C{PERLSRCDIR};
|
||||
$needs_reconfig=1;
|
||||
}
|
||||
GITSTUFF: {
|
||||
my $d = pushd($C{PERLSRCDIR});
|
||||
my $remhead;
|
||||
eval {
|
||||
git 'fetch';
|
||||
$remhead = git 'log', '-1', '--format=%h', 'origin/'.$C{PERL_BRANCH}, {chomp=>1,show_cmd=>$VERBOSE};
|
||||
1 } or do {
|
||||
warn $@;
|
||||
# Maybe we don't have network connectivity
|
||||
if (prompt("Whoops, 'git' failed. Continue anyway? [Yn]","y")=~/^\s*y/i)
|
||||
{ last GITSTUFF }
|
||||
else { die "git fetch failed, aborting" }
|
||||
};
|
||||
my $myhead = git 'log', '-1', '--format=%h', $C{PERL_BRANCH}, {chomp=>1,show_cmd=>$VERBOSE};
|
||||
say STDERR "# Local branch is at $myhead, remote is $remhead";
|
||||
if ($myhead ne $remhead) {
|
||||
git 'merge-base', '--is-ancestor', $remhead, $myhead, {allow_exit=>[0,1]};
|
||||
if ($?==0) {
|
||||
say STDERR "# However, it looks like $myhead is newer than $remhead, won't ask for update";
|
||||
last GITSTUFF }
|
||||
if (prompt("Would you like to update? WARNING: Unsaved local changes may be lost! [Yn]","y")=~/^\s*y/i) {
|
||||
eval {
|
||||
if ($C{CLOBBER_BRANCH}) {
|
||||
say "WARNING: I am about to clobber the branch $C{PERL_BRANCH} in $C{PERLSRCDIR}!";
|
||||
verify_perlsrc_modify(1);
|
||||
git 'checkout', '-q', $C{PERLVER};
|
||||
git 'branch', '-D', $C{PERL_BRANCH};
|
||||
git 'branch', $C{PERL_BRANCH}, 'origin/'.$C{PERL_BRANCH};
|
||||
git 'checkout', $C{PERL_BRANCH};
|
||||
}
|
||||
else {
|
||||
git 'checkout', $C{PERL_BRANCH};
|
||||
git 'pull';
|
||||
}
|
||||
1 } or die "$@\nA git step failed - perhaps you have uncommited changes in $C{PERLSRCDIR}?\n";
|
||||
$needs_reconfig=1;
|
||||
}
|
||||
}
|
||||
my $tags = git 'tag', '--list', {show_cmd=>$VERBOSE};
|
||||
die "could not find tag '$C{PERLVER}', is this the right repository?"
|
||||
unless $tags=~/^\Q$C{PERLVER}\E$/m;
|
||||
my $branches = git 'branch', '--list', {show_cmd=>$VERBOSE};
|
||||
die "could not find branch '$C{PERL_BRANCH}', is this the right repository?"
|
||||
. " (or the WebPerl author forgot to push tags to the emperl5 repo)"
|
||||
unless $branches=~/^\*?\s*\b\Q$C{PERL_BRANCH}\E$/m;
|
||||
say STDERR "# Found tag '$C{PERLVER}' and branch '$C{PERL_BRANCH}' in $C{PERLSRCDIR}";
|
||||
}
|
||||
sub verify_perlsrc_modify {
|
||||
my $force = shift;
|
||||
state $already_prompted = 0;
|
||||
$already_prompted=0 if $force;
|
||||
return if $already_prompted;
|
||||
if (prompt("WARNING: You will lose any changes to the working copy and index in $C{PERLSRCDIR}!\n"
|
||||
." Continue? [yN]","n")!~/^\s*y/i) {
|
||||
say STDERR "Aborting.";
|
||||
exit 1;
|
||||
} else { $already_prompted = 1 }
|
||||
}
|
||||
|
||||
# ##### ##### ##### Step: Check/build hostperl ##### ##### #####
|
||||
|
||||
sub verify_hostperl {
|
||||
my $miniperl = $C{HOSTPERLDIR}->file('miniperl');
|
||||
return 0 unless -e $miniperl;
|
||||
my $miniperlver = run $miniperl, '-e', 'print $^V', {show_cmd=>$VERBOSE};
|
||||
say STDERR "# Detected hostperl / miniperl '$miniperlver' (need '$C{PERLVER}')";
|
||||
my $perl = $C{HOSTPERLDIR}->file('perl');
|
||||
if (-e $perl) { # currently just an optional check
|
||||
my $perlver = run $perl, '-e', 'print $^V', {show_cmd=>$VERBOSE};
|
||||
say STDERR "# Detected hostperl / perl '$perlver'";
|
||||
die "miniperl ('$miniperlver') / perl ('$perlver') version mismatch"
|
||||
unless $miniperlver eq $perlver;
|
||||
}
|
||||
return $miniperlver eq $C{PERLVER};
|
||||
}
|
||||
if (!verify_hostperl()) {
|
||||
say STDERR "# A rebuild of hostperl is required";
|
||||
$C{HOSTPERLDIR}->rmtree(1);
|
||||
$C{HOSTPERLDIR}->mkpath(1);
|
||||
verify_perlsrc_modify();
|
||||
{
|
||||
my $d = pushd($C{PERLSRCDIR});
|
||||
git 'checkout', '-qf', $C{PERLVER};
|
||||
git 'clean', '-dxf';
|
||||
}
|
||||
{
|
||||
my $d = pushd($C{HOSTPERLDIR});
|
||||
run {stdin=>\undef}, 'sh', file($C{PERLSRCDIR},'Configure'),
|
||||
'-des', '-Dusedevel', '-Dmksymlinks';
|
||||
make 'miniperl';
|
||||
make 'minitest';
|
||||
make 'generate_uudmap';
|
||||
#TODO Later: do we really need the following full perl build as well? (good for testing?)
|
||||
# if we do, make the test for "perl" in verify_hostperl required, not optional
|
||||
make 'perl';
|
||||
make 'test';
|
||||
}
|
||||
$needs_reconfig=1;
|
||||
die "something went wrong with hostperl" unless verify_hostperl();
|
||||
}
|
||||
|
||||
# ##### ##### ##### Step: Prep "emperl" sources (for next steps) ##### ##### #####
|
||||
|
||||
my $config_sh = $C{PERLSRCDIR}->file('config.sh');
|
||||
if (!-e $config_sh) {
|
||||
say STDERR "# config.sh NOT found, forcing a reconfig";
|
||||
$needs_reconfig=1 }
|
||||
else { say STDERR "# config.sh found" }
|
||||
|
||||
if (-e $config_sh) {
|
||||
my $our_mtime = file($FindBin::Bin, 'emperl_config.sh')->stat->mtime;
|
||||
my $perl_mtime = $config_sh->stat->mtime;
|
||||
if ($perl_mtime>$our_mtime)
|
||||
{ say STDERR "# config.sh is newer than emperl_config.sh" }
|
||||
else {
|
||||
say STDERR "# config.sh is OLDER than emperl_config.sh";
|
||||
exit 1 if prompt("Did you remember to run '. emperl_config.sh'? [yN]","n")!~/^\s*y/i;
|
||||
say STDERR "# ok, forcing a reconfig";
|
||||
$needs_reconfig=1;
|
||||
}
|
||||
}
|
||||
|
||||
if ($needs_reconfig) {
|
||||
exit 1 if prompt("Looks like we need a full reconfig. Continue? [Yn]","y")!~/^\s*y/i;
|
||||
verify_perlsrc_modify();
|
||||
my $d = pushd($C{PERLSRCDIR});
|
||||
# Note: could get the current branch with: git 'rev-parse', '--abbrev-ref', 'HEAD', {chomp=>1};
|
||||
# but since we're clobbering anyway...
|
||||
git 'checkout', '-qf', $C{PERL_BRANCH};
|
||||
git 'clean', '-dxf';
|
||||
}
|
||||
|
||||
# ##### ##### ##### Step: Add custom extensions ##### ##### #####
|
||||
|
||||
if ($needs_reconfig || $opts{forceext}) {
|
||||
my $http = HTTP::Tiny->new;
|
||||
$C{DOWNLOADDIR}->mkpath(1);
|
||||
for my $modname ($C{EXTENSIONS}->@*) {
|
||||
my $apiuri = URI->new('https://fastapi.metacpan.org/v1/download_url');
|
||||
$apiuri->path_segments( $apiuri->path_segments, $modname );
|
||||
say STDERR "# Fetching $apiuri...";
|
||||
my $resp1 = $http->get($apiuri);
|
||||
die "$apiuri: $resp1->{status} $resp1->{reason}\n" unless $resp1->{success};
|
||||
my $apiresp = decode_json($resp1->{content});
|
||||
my $version = $apiresp->{version};
|
||||
my $dluri = URI->new($apiresp->{download_url});
|
||||
|
||||
my $file = $C{DOWNLOADDIR}->file( ($dluri->path_segments)[-1] );
|
||||
die "I don't know what to do with this file type (yet): $file"
|
||||
unless $file->basename=~/(?:\.tar\.gz|\.tgz)$/i;
|
||||
|
||||
say STDERR "# Fetching $dluri into $file...";
|
||||
my $resp2 = $http->mirror($dluri, $file);
|
||||
die "$dluri: $resp2->{status} $resp2->{reason}\n" unless $resp2->{success};
|
||||
say STDERR "# $dluri: $resp2->{status} $resp2->{reason}";
|
||||
|
||||
my $tempd = dir( tempdir(DIR=>$C{DOWNLOADDIR}, CLEANUP => 1) );
|
||||
{
|
||||
my $d = pushd($tempd);
|
||||
my @files = Archive::Tar->new->extract_archive($file, Archive::Tar::COMPRESS_GZIP);
|
||||
say STDERR "# Extracted ",0+@files," files into $tempd";
|
||||
}
|
||||
|
||||
my @dirs = $tempd->children;
|
||||
die "Can't handle the directory structure of this file (yet): $file"
|
||||
unless @dirs==1 && $dirs[0]->is_dir;
|
||||
my ($dirname) = $dirs[0]->basename =~ /^(.+)-\Q$version\E$/g
|
||||
or die "Failed to parse ".$dirs[0]->basename;
|
||||
my $targdir = $C{PERLSRCDIR}->subdir( 'ext', $dirname );
|
||||
my $domove = 1;
|
||||
if (-e $targdir) {
|
||||
if ( prompt("WARNING: $targdir exists, Keep or Delete? [Kd]","k")=~/^\s*d/i )
|
||||
{ $targdir->rmtree(1) }
|
||||
else { $domove=0 }
|
||||
}
|
||||
if ($domove) {
|
||||
say STDERR "# Moving $dirs[0] to $targdir";
|
||||
dirmove($dirs[0], $targdir)
|
||||
or die "move failed: $!";
|
||||
}
|
||||
}
|
||||
say STDERR "# Done setting up modules";
|
||||
}
|
||||
else { say STDERR "# Since we don't need a reconfig, not looking at extensions" }
|
||||
|
||||
# ##### ##### ##### Step: Run configure ##### ##### #####
|
||||
|
||||
if ($needs_reconfig) { # this means that we cleaned the source tree above
|
||||
say STDERR "# Running Configure...";
|
||||
my $d = pushd($C{PERLSRCDIR});
|
||||
# note that we don't use -Dmksymlinks here because something in the
|
||||
# Emscripten build process seems to have issues with the symlinks (?)
|
||||
run {stdin=>\undef}, 'emconfigure', 'sh', 'Configure', '-des',
|
||||
'-Dhintfile=emscripten';
|
||||
}
|
||||
elsif ($opts{applyconfig}) {
|
||||
say STDERR "# Running Configure -S...";
|
||||
my $d = pushd($C{PERLSRCDIR});
|
||||
run {stdin=>\undef}, 'emconfigure', 'sh', 'Configure', '-S';
|
||||
}
|
||||
|
||||
# ##### ##### ##### Step: Build perl into outputdir ##### ##### #####
|
||||
|
||||
my $destdir = dir($C{OUTPUTDIR},$C{PREFIX});
|
||||
if ($needs_reconfig || !-e $destdir || $opts{remakeout}) {
|
||||
say STDERR "# Rebuilding $destdir...";
|
||||
$destdir->rmtree(1);
|
||||
# make the target dir here so that nodeperl_dev_prerun.js can mount it during build
|
||||
$destdir->mkpath(1);
|
||||
|
||||
my $d = pushd($C{PERLSRCDIR});
|
||||
|
||||
emmake 'make', 'perl';
|
||||
|
||||
# a really basic test to see if the build succeeded
|
||||
my $perltest = run file($C{PERLSRCDIR},'perl'), '-e', q{print "$^O $^V"},
|
||||
{chomp=>1,show_cmd=>$VERBOSE,fail_on_stderr=>1};
|
||||
die "something went wrong building perl (got: '$perltest')"
|
||||
unless $perltest eq 'emscripten '.$C{PERLVER};
|
||||
|
||||
# note that installperl requires ./perl to be executable (our Makefile patch currently takes care of that)
|
||||
run $C{HOSTPERLDIR}.'/miniperl', 'installperl', '-p', '--destdir='.$C{OUTPUTDIR};
|
||||
|
||||
# clean out the stuff we really don't need
|
||||
$destdir->subdir('bin')->rmtree(1);
|
||||
$destdir->recurse( callback => sub {
|
||||
my $f = shift;
|
||||
return if $f->is_dir;
|
||||
if ( ( $f->basename=~/\.(?:h|a|pod)$/i ) || ( $f->basename eq 'extralibs.ld' && (-s $f)==1 )
|
||||
|| ( $f->basename eq '.packlist' ) ) {
|
||||
print STDERR "removing $f\n";
|
||||
$f->remove or die "failed to remove $f";
|
||||
}
|
||||
elsif ( $f->basename=~/\.(?:pm|pl)$/i && $f->basename ne 'WebPerl.pm' ) {
|
||||
print STDERR "stripping POD from $f\n";
|
||||
my $strip = Pod::Strip->new;
|
||||
my ($infh,$outfh,$repl) = replace3($f);
|
||||
$strip->output_fh($outfh);
|
||||
$strip->parse_file($infh);
|
||||
$repl->finish;
|
||||
}
|
||||
});
|
||||
CLEAN_EMPTY: {
|
||||
my @todel;
|
||||
$destdir->recurse( callback => sub { push @todel, $_[0] if $_[0]->is_dir && !$_[0]->children } );
|
||||
for my $f (@todel) {
|
||||
print STDERR "removing $f\n";
|
||||
$f->remove or die "failed to remove $f";
|
||||
}
|
||||
redo CLEAN_EMPTY if @todel;
|
||||
}
|
||||
|
||||
# Development aides:
|
||||
$destdir->subdir('dev')->mkpath(1);
|
||||
# we make them hard links so that edits to WebPerl.pm don't require a full
|
||||
# rebuild of the output directory (a rebuild of emperl.js is enough)
|
||||
safelink( $C{PERLSRCDIR}->file('ext','WebPerl','t','WebPerl.t'),
|
||||
$destdir->file('dev','WebPerl.t') );
|
||||
safelink( $C{PERLSRCDIR}->file('ext','WebPerl','lib','WebPerl.pm'),
|
||||
$destdir->file('lib',$C{PERLVER}=~s/^v(?=5)//r,'wasm','WebPerl.pm') );
|
||||
|
||||
#TODO Later: Provide an easy way for users to add files to the virtual file system
|
||||
|
||||
say STDERR "# Done rebuilding $destdir";
|
||||
}
|
||||
|
||||
# ##### ##### ##### Step: Build emperl.js ##### ##### #####
|
||||
|
||||
{
|
||||
say STDERR "# Making emperl.js...";
|
||||
my $targ = $C{PERLSRCDIR}->file('emperl.js');
|
||||
if ( ($opts{forceemperl} || $opts{remakeout}) && -e $targ )
|
||||
{ $targ->remove or die "failed to delete $targ: $!" }
|
||||
my $d = pushd($C{PERLSRCDIR});
|
||||
emmake 'make', 'emperl.js';
|
||||
die "Target file not generated?" unless -e $targ;
|
||||
say STDERR "# Done making emperl.js";
|
||||
}
|
||||
for my $f (qw/ emperl.js emperl.wasm emperl.data /) {
|
||||
$C{PERLSRCDIR}->file($f)
|
||||
->copy_to( dir($FindBin::Bin)->parent->subdir('web') )
|
||||
or die "failed to copy $f: $!";
|
||||
}
|
||||
say STDERR "# Copied emperl.* files to web dir";
|
||||
|
||||
# ##### ##### ##### Step: Build distro ##### ##### #####
|
||||
|
||||
if (my $dist = $opts{dist}) {
|
||||
my $basedir = dir($FindBin::Bin)->parent;
|
||||
my $zipfn = $basedir->file("$dist.zip");
|
||||
my $zip = Archive::Zip->new();
|
||||
$zip->addTree($basedir->subdir('web').'', dir($dist).'');
|
||||
$zip->addFile($basedir->file($_).'', dir($dist)->file($_).'') for
|
||||
qw/ README.md LICENSE_artistic.txt LICENSE_gpl.txt cpanfile /;
|
||||
$zip->writeToFileNamed("$zipfn") == AZ_OK or die "$zipfn write error";
|
||||
say STDERR "# Wrote to $zipfn:";
|
||||
my $unzip = Archive::Zip->new("$zipfn");
|
||||
say "\t$_" for $unzip->memberNames;
|
||||
}
|
||||
|
||||
|
||||
# ##### ##### ##### subs ##### ##### #####
|
||||
|
||||
sub safelink { # like link(OLDFILE,NEWFILE) but with extra checks
|
||||
my ($oldfile,$newfile) = @_;
|
||||
die "not a file: $oldfile" unless -f $oldfile;
|
||||
if (-e $newfile) {
|
||||
die "files don't match: $oldfile vs. $newfile"
|
||||
unless do { open my $fh, '<:raw', $oldfile or die "$oldfile: $!"; local $/; <$fh> }
|
||||
eq do { open my $fh, '<:raw', $newfile or die "$newfile: $!"; local $/; <$fh> };
|
||||
file($newfile)->remove or die "failed to remove $newfile: $!";
|
||||
}
|
||||
link($oldfile,$newfile)
|
||||
or die "link('$oldfile','$newfile'): $!";
|
||||
}
|
||||
|
||||
# First argument: the filename of the .patch file
|
||||
# Any following arguments are additionally passed to "patch" (e.g. "-p1")
|
||||
# Attempts to run "patch", will fail gracefully if the patch has already been applied.
|
||||
# Dies if anything goes wrong (patch not applied cleanly, etc.).
|
||||
# Returns false (0) if the patch was already applied previously, true (1) if the patch was newly applied.
|
||||
sub try_patch_file {
|
||||
my ($patchf,@args) = @_;
|
||||
say STDERR "# Attempting to apply patch $patchf...";
|
||||
run 'patch', @args, '-r-', '-sNi', $patchf, {allow_exit=>[0,1],show_cmd=>$VERBOSE};
|
||||
if ($?==1<<8) {
|
||||
# Slightly hackish way to test if the patch did not apply cleanly, or it's just already been applied:
|
||||
# Apply the patch in reverse once, and then apply it again, if both go through without errors all is ok.
|
||||
# There is probably a better way to do this, I'm just feeling a little lazy at the moment.
|
||||
run 'patch', @args, '-sRi', $patchf, {show_cmd=>$VERBOSE};
|
||||
run 'patch', @args, '-si', $patchf, {show_cmd=>$VERBOSE};
|
||||
say STDERR "# Verified that $patchf was previously applied";
|
||||
return 0;
|
||||
}
|
||||
elsif ($?) { die "patch $patchf \$?=$?" }
|
||||
else { say STDERR "# Successfully applied patch $patchf"; return 1 }
|
||||
}
|
||||
|
||||
# A simplified version of File::pushd that outputs debug info. TODO Later: should probably propose a patch for a debug option.
|
||||
sub pushd {
|
||||
if (not defined wantarray) { carp "pushd in void context"; return }
|
||||
croak "bad arguments to pushd" unless @_==1 && defined $_[0];
|
||||
my $targ = abs_path(shift);
|
||||
croak "not a directory: $targ" unless -d $targ;
|
||||
my $orig = getcwd;
|
||||
if ($targ ne $orig) {
|
||||
say STDERR "\$ cd $targ";
|
||||
chdir $targ or croak "chdir to $targ failed: $!";
|
||||
}
|
||||
return bless { orig=>$orig }, 'PushedDir';
|
||||
}
|
||||
sub PushedDir::DESTROY {
|
||||
my $self = shift;
|
||||
if (getcwd ne $self->{orig}) {
|
||||
say STDERR "\$ cd ".$self->{orig};
|
||||
chdir $self->{orig} or croak "chdir to ".$self->{orig}." failed: $!";
|
||||
}
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
#TODO Later: Fix the following (note setting d_getgrgid_r and d_getgrnam_r in the hints file didn't seem to help)
|
||||
warning: unresolved symbol: getgrgid
|
||||
warning: unresolved symbol: getgrnam
|
||||
warning: unresolved symbol: llvm_fma_f64
|
||||
warning: unresolved symbol: sigsuspend
|
||||
|
||||
#TODO Later: Fix the following "miniperl make_ext.pl" errors (warnings?)
|
||||
./miniperl -Ilib make_ext.pl lib/auto/Encode/Byte/Byte.a MAKE="make" LIBPERL_A=libperl.a LINKTYPE=static CCCDLFLAGS=
|
||||
Can't find extension Encode/Byte in any of cpan dist ext at make_ext.pl line 251.
|
||||
./miniperl -Ilib make_ext.pl lib/auto/Encode/Symbol/Symbol.a MAKE="make" LIBPERL_A=libperl.a LINKTYPE=static CCCDLFLAGS=
|
||||
Can't find extension Encode/Symbol in any of cpan dist ext at make_ext.pl line 251.
|
||||
./miniperl -Ilib make_ext.pl lib/auto/Encode/Unicode/Unicode.a MAKE="make" LIBPERL_A=libperl.a LINKTYPE=static CCCDLFLAGS=
|
||||
Can't find extension Encode/Unicode in any of cpan dist ext at make_ext.pl line 251.
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
|
||||
# Install the dependencies for "build" via:
|
||||
# $ cpanm --installdeps .
|
||||
|
||||
requires 'Data::Dump';
|
||||
requires 'Path::Class';
|
||||
requires 'IPC::Run3::Shell', '0.56';
|
||||
requires 'URI';
|
||||
requires 'Net::SSLeay', 1.49;
|
||||
requires 'IO::Socket::SSL', '1.56';
|
||||
requires 'Cpanel::JSON::XS';
|
||||
requires 'File::Copy::Recursive';
|
||||
requires 'File::Replace', '0.08';
|
||||
requires 'Pod::Strip';
|
||||
requires 'Archive::Zip';
|
||||
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This is the configuration file for building WebPerl.
|
||||
# You should edit it according to the comments below.
|
||||
# Remember to reload this file after making changes! (". emperl_config.sh")
|
||||
|
||||
# You must edit this to point to your Emscripten SDK's emsdk_env.sh.
|
||||
. $HOME/emsdk/emsdk_env.sh
|
||||
|
||||
# A whitespace-separated list of modules to download and add to the build.
|
||||
# Note: Cpanel::JSON::XS is required for WebPerl!
|
||||
export EMPERL_EXTENSIONS="Cpanel::JSON::XS Devel::StackTrace Future"
|
||||
|
||||
# Modules from the above list that have XS code need to be linked statically.
|
||||
# Add them here, separated by whitespace (see also the "static_ext" variable
|
||||
# in https://perl5.git.perl.org/perl.git/blob/HEAD:/Porting/Glossary ).
|
||||
export EMPERL_STATIC_EXT="Cpanel/JSON/XS"
|
||||
|
||||
# Do not edit (this gets this script's parent directory)
|
||||
BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. >/dev/null && pwd )"
|
||||
|
||||
# Various working directories, you normally don't need to edit these
|
||||
export EMPERL_PERLSRCDIR="$BASEDIR/emperl5"
|
||||
export EMPERL_HOSTPERLDIR="$BASEDIR/work/hostperl"
|
||||
export EMPERL_DOWNLOADDIR="$BASEDIR/work/download"
|
||||
export EMPERL_OUTPUTDIR="$BASEDIR/work/outputperl"
|
||||
|
||||
# Don't edit the following options unless you know what you're doing!
|
||||
# Note to self: In build.pl, we take advantage of the fact that on Perls >=v5.10.0, "$^V" is the same as the tag name.
|
||||
export EMPERL_PERLVER="v5.30.0"
|
||||
export EMPERL_PREFIX="/opt/perl"
|
||||
# Note: strace shows this is how file_packager.py is called: ["/usr/bin/python", "/home/haukex/emsdk/emscripten/1.38.28/tools/file_packager.py", "emperl.data", "--from-emcc", "--export-name=Module", "--preload", "/home/haukex/code/webperl/work/outputperl/opt/perl@/opt/perl", "--no-heap-copy"]
|
||||
export EMPERL_PRELOAD_FILE="$EMPERL_OUTPUTDIR$EMPERL_PREFIX@$EMPERL_PREFIX"
|
||||
export EMPERL_OPTIMIZ="-O2"
|
||||
# Note: We explicitly disable ERROR_ON_UNDEFINED_SYMBOLS because it was enabled by default in Emscripten 1.38.13.
|
||||
#TODO Later: Why does --no-heap-copy not get rid of the "in memory growth we are forced to copy it again" assertion warning? (https://github.com/emscripten-core/emscripten/commit/ec764ace634f13bab5ae932912da53fe93ee1b69)
|
||||
export EMPERL_LINK_FLAGS="--pre-js common_preamble.js --no-heap-copy -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s EXPORTED_FUNCTIONS=['_main','_emperl_end_perl','_Perl_call_sv','_Perl_call_pv','_Perl_call_method','_Perl_call_argv','_Perl_eval_pv','_Perl_eval_sv','_webperl_eval_perl'] -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap']"
|
||||
|
||||
export EMPERL_DEBUG_FLAGS=""
|
||||
#export EMPERL_DEBUG_FLAGS="-s ASSERTIONS=2 -s STACK_OVERFLOW_CHECK=2"
|
||||
# Note: not including "-s SAFE_HEAP=1" in the debug flags because we're building to WebAssembly, which doesn't require alignment
|
||||
#TODO Later: Can some of the SAFE_HEAP functionality (null pointer access I think?) be replaced by the WASM error traps?
|
||||
# http://kripken.github.io/emscripten-site/docs/compiling/WebAssembly.html#binaryen-codegen-options
|
||||
|
||||
# Location and branch of the perl git repository that contains the emperl branch
|
||||
export EMPERL_PERL_REPO="https://github.com/haukex/emperl5.git"
|
||||
export EMPERL_PERL_BRANCH="emperl_$EMPERL_PERLVER"
|
||||
# Enabling this setting causes the local emperl branch to be deleted and re-fetched from the origin.
|
||||
# This is useful during development, when rewrites of the (unpublished!) git history of the branch might happen.
|
||||
export EMPERL_CLOBBER_BRANCH=0
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
--- emscripten/src/library.js 2018-08-01 18:58:49.428051969 +0200
|
||||
+++ emsdk/emscripten/1.38.10/src/library.js 2018-08-01 18:58:03.288051969 +0200
|
||||
@@ -250,7 +250,7 @@
|
||||
// pid_t fork(void);
|
||||
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
|
||||
// We don't support multiple processes.
|
||||
- ___setErrNo(ERRNO_CODES.EAGAIN);
|
||||
+ ___setErrNo(ERRNO_CODES.ENOTSUP);
|
||||
return -1;
|
||||
},
|
||||
vfork: 'fork',
|
||||
@@ -545,7 +545,7 @@
|
||||
// int system(const char *command);
|
||||
// http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
|
||||
// Can't call external programs.
|
||||
- ___setErrNo(ERRNO_CODES.EAGAIN);
|
||||
+ ___setErrNo(ERRNO_CODES.ENOTSUP);
|
||||
return -1;
|
||||
},
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
--- library.js.orig 2019-03-02 16:08:24.404047130 +0100
|
||||
+++ library.js 2019-03-02 16:19:30.588047130 +0100
|
||||
@@ -291,7 +291,7 @@
|
||||
// pid_t fork(void);
|
||||
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
|
||||
// We don't support multiple processes.
|
||||
- ___setErrNo({{{ cDefine('EAGAIN') }}});
|
||||
+ ___setErrNo({{{ cDefine('ENOTSUP') }}});
|
||||
return -1;
|
||||
},
|
||||
vfork: 'fork',
|
||||
@@ -817,7 +817,7 @@
|
||||
// int system(const char *command);
|
||||
// http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
|
||||
// Can't call external programs.
|
||||
- ___setErrNo({{{ cDefine('EAGAIN') }}});
|
||||
+ ___setErrNo({{{ cDefine('ENOTSUP') }}});
|
||||
return -1;
|
||||
},
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
--- library.js.orig 2020-05-18 17:14:18.682328912 +0200
|
||||
+++ library.js 2020-05-18 17:14:48.366639562 +0200
|
||||
@@ -271,7 +271,7 @@
|
||||
// pid_t fork(void);
|
||||
// http://pubs.opengroup.org/onlinepubs/000095399/functions/fork.html
|
||||
// We don't support multiple processes.
|
||||
- setErrNo({{{ cDefine('EAGAIN') }}});
|
||||
+ setErrNo({{{ cDefine('ENOTSUP') }}});
|
||||
return -1;
|
||||
},
|
||||
vfork: 'fork',
|
||||
@@ -696,7 +696,7 @@
|
||||
// http://pubs.opengroup.org/onlinepubs/000095399/functions/system.html
|
||||
// Can't call external programs.
|
||||
if (!command) return 0; // no shell available
|
||||
- setErrNo({{{ cDefine('EAGAIN') }}});
|
||||
+ setErrNo({{{ cDefine('ENOTSUP') }}});
|
||||
return -1;
|
||||
},
|
||||
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
# Finds and displays TODOs for the WebPerl project.
|
||||
# the output can be piped into e.g. "less -R"
|
||||
if [ -z ${EMPERL_PERLVER+x} ]; then
|
||||
echo "Please source emperl_config.sh first"
|
||||
exit 1
|
||||
fi
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. >/dev/null && pwd )"
|
||||
TEMPFILE="`mktemp`"
|
||||
trap 'rm -f "$TEMPFILE"' EXIT
|
||||
(
|
||||
cd "$DIR/emperl5"
|
||||
# only look at files that have been added
|
||||
git diff --numstat --diff-filter=A $EMPERL_PERLVER $EMPERL_PERL_BRANCH \
|
||||
| BASEDIR=$DIR perl -wMstrict -MFile::Spec::Functions=abs2rel,rel2abs -nl0 \
|
||||
-e '/^\d+\s+\d+\s+(.+)$/ or die $_; -e $1 and print abs2rel(rel2abs($1),$ENV{BASEDIR})'
|
||||
) >>"$TEMPFILE"
|
||||
cd $DIR
|
||||
find . -mindepth 1 \( -path ./.git -o -path ./work -o -path ./emperl5 \) -prune \
|
||||
-o ! -name 'emperl.*' ! -type d -print0 \
|
||||
| perl -wMstrict -MFile::Spec::Functions=canonpath -n0le 'print canonpath($_)' >>"$TEMPFILE"
|
||||
xargs -0 -a "$TEMPFILE" \
|
||||
grep --color=always -C1 -niE '\bto.?do\b'
|
||||
@ -1,195 +0,0 @@
|
||||
|
||||
\[ [Using](using.html) -
|
||||
Building -
|
||||
[🦋](perl6.html) -
|
||||
[Notes](notes.html) -
|
||||
[Legal](legal.html) -
|
||||
[Wiki](https://github.com/haukex/webperl/wiki/Building-WebPerl) \]
|
||||
|
||||
Building WebPerl
|
||||
================
|
||||
|
||||
|
||||
**Notice: WebPerl is very much in beta.**
|
||||
Some things may not work yet, and parts of the API may still change.
|
||||
Your feedback is always appreciated!
|
||||
|
||||
This page documents the Perl 5 support, for the *experimental*
|
||||
Perl 6 support, see [here](perl6.html).
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
- Linux, a fairly modern release is strongly recommended, and `bash`
|
||||
(tested on a minimum of Ubuntu 16.04)
|
||||
- `git` (e.g. Ubuntu/Debian: `sudo apt-get install git-core`)
|
||||
- Build tools for `perl`; for example on Ubuntu:
|
||||
`sudo apt-get install build-essential` and `sudo apt-get build-dep perl`
|
||||
- Perl, at least v5.26 (for example via [Perlbrew](http://perlbrew.pl/))
|
||||
- [Emscripten](http://emscripten.org) SDK, 1.38.10 and up,
|
||||
please see the prerequisites and installation instructions at
|
||||
<http://kripken.github.io/emscripten-site/docs/getting_started/downloads.html#installation-instructions>
|
||||
- The build script has several CPAN dependencies. You can install these
|
||||
via [`cpanm`](https://metacpan.org/pod/App::cpanminus) and the provided
|
||||
`cpanfile`: `cd` to the `build` directory, then run `cpanm --installdeps .`
|
||||
(Otherwise, see the `cpanfile` or the source of `build.pl` and install these
|
||||
modules with the installer of your choice. Another possibility is to use
|
||||
[`lazy`](https://metacpan.org/pod/lazy), e.g. `perl -Mlazy build.pl --help`)
|
||||
- A working Internet connection is needed for installation and the first build.
|
||||
|
||||
|
||||
Source Code
|
||||
-----------
|
||||
|
||||
The source code is in two repositories:
|
||||
|
||||
- <https://github.com/haukex/webperl> - the main WebPerl repository
|
||||
|
||||
- <https://github.com/haukex/emperl5> - a fork of the Perl 5 source
|
||||
repository where the WebPerl-specific patches are applied
|
||||
|
||||
You only need to check out the first of the two, the `emperl5` repository
|
||||
is checked out by the build script.
|
||||
|
||||
Some of the central source files of WebPerl are:
|
||||
|
||||
- [`webperl/build/build.pl`](https://github.com/haukex/webperl/blob/master/build/build.pl)
|
||||
- [`emperl5/hints/emscripten.sh`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/hints/emscripten.sh)
|
||||
- [`emperl5/perlmain_noexit_patch`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/perlmain_noexit_patch)
|
||||
- [`emperl5/ext/WebPerl/WebPerl.xs`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/ext/WebPerl/WebPerl.xs)
|
||||
- [`emperl5/ext/WebPerl/lib/WebPerl.pm`](https://github.com/haukex/emperl5/blob/emperl_v5.28.1/ext/WebPerl/lib/WebPerl.pm)
|
||||
- [`webperl/web/webperl.js`](https://github.com/haukex/webperl/blob/master/web/webperl.js)
|
||||
|
||||
|
||||
Running the Build
|
||||
-----------------
|
||||
|
||||
1. Fetch the source code.
|
||||
|
||||
$ git clone https://github.com/haukex/webperl.git
|
||||
$ cd webperl
|
||||
|
||||
2. Install the [prerequisites](#prerequisites).
|
||||
|
||||
3. Edit the configuration file, `./build/emperl_config.sh`, to fit
|
||||
your system. For a first build, just make sure the path to
|
||||
`emsdk_env.sh` is correct.
|
||||
|
||||
4. Source the configuration file to set the environment variables.
|
||||
Remember to do this anytime you change variables. You may also
|
||||
add the sourcing of the configuration file to your `~/.bashrc`.
|
||||
|
||||
$ . ./build/emperl_config.sh
|
||||
|
||||
5. Run the build script:
|
||||
|
||||
$ build/build.pl
|
||||
|
||||
6. If the build succeeds, the output files `emperl.*` will be
|
||||
copied to the `web` directory of the repository. You can
|
||||
then use the files in the `web` directory as described in
|
||||
[Using WebPerl](using.html).
|
||||
|
||||
|
||||
Build Process Overview
|
||||
----------------------
|
||||
|
||||
The build script `build.pl` tries to take care of as much of the build process as
|
||||
possible. Most of the work happens in a subdirectory `work` of the repository.
|
||||
Similar to `make`, it tries to not run build steps that don't need to be rerun.
|
||||
|
||||
> A brief note on naming:
|
||||
>
|
||||
> - *`emperl`* is generally used for the build products of Emscripten
|
||||
> - *`emperl5`* is the Perl 5 source tree modified for WebPerl
|
||||
> - *WebPerl* is the finished product, including `emperl`
|
||||
> and the WebPerl APIs (`WebPerl.pm` and `webperl.js`)
|
||||
|
||||
The steps in the build process are roughly as follows.
|
||||
Since WebPerl is still in beta, they are subject to change.
|
||||
See
|
||||
[the source of the `build.pl` script](https://github.com/haukex/webperl/blob/master/build/build.pl)
|
||||
for the current details.
|
||||
|
||||
1. Patch Emscripten
|
||||
(currently just a minor patch, but important for Perl)
|
||||
|
||||
2. Fetch/update the `emperl5` Perl source tree
|
||||
|
||||
3. If necessary, build "host Perl" - in Perl's cross-compilation system,
|
||||
this is the Perl that is built for the host system architecture,
|
||||
i.e. in the case of Linux, a normal build of Perl for Linux. The
|
||||
`miniperl` from the host Perl will be used for some of the build
|
||||
steps for the target architecture.
|
||||
(Note: This step can take quite a while, but it usually only needs
|
||||
to be run once.)
|
||||
|
||||
4. Download and extract any CPAN modules, such as the required `Cpanel::JSON::XS`,
|
||||
into the Perl source tree so that they will be built as part of the normal
|
||||
build process and any XS extensions linked statically into the `perl` binary.
|
||||
(See ["Adding CPAN Modules"](#adding-cpan-modules))
|
||||
|
||||
5. Run Perl's `Configure` script using the custom "hints" file for the Emscripten
|
||||
architecture.
|
||||
|
||||
6. Run `make` to compile `perl`. This produces a file `perl.bc` with LLVM IR
|
||||
bitcode, which the Emscripten compiler will then compile to JavaScript/WebAssembly.
|
||||
Because some steps in the build process require a working `perl` binary,
|
||||
Emscripten's compiler is used together with a supporting JavaScript file to
|
||||
generate JavaScript/WebAssembly code that can be run with `node.js` (called `nodeperl_dev.js`).
|
||||
|
||||
8. Run the equivalent of `make install`, which copies all the Perl modules
|
||||
etc. into the target directory that will become part of the Emscripten
|
||||
virtual file system. Then, we clean this directory up by deleting anything
|
||||
that we don't need for WebPerl: additional binaries (it's a single-process
|
||||
environment), `*.pod` files, as well as stripping the POD out of `*.pm`
|
||||
files, etc. to reduce the download size.
|
||||
|
||||
9. The Emscripten compiler is used to take the previously compiled `perl.bc`
|
||||
and build the final output, `emperl.js` along with the corresponding
|
||||
`.wasm` and `.data` file. This step also includes the packaging of the
|
||||
virtual filesystem.
|
||||
|
||||
`build.pl` provides various command-line options that allow you to control
|
||||
parts of the build process. See `build.pl --help` for details.
|
||||
|
||||
|
||||
Adding CPAN Modules
|
||||
-------------------
|
||||
|
||||
In the configuration file `emperl_config.sh`, the variable `EMPERL_EXTENSIONS`
|
||||
is a whitespace-separated list of module names. `build.pl` will fetch these
|
||||
from CPAN and extract them into the `ext` directory of the Perl source tree
|
||||
so that they are compiled along with Perl. Any XS modules that need to be
|
||||
linked into `perl` need to be added to the variable `EMPERL_STATIC_EXT` in
|
||||
the format expected by Perl's `static_ext` configuration variable,
|
||||
so for example `Cpanel/JSON/XS` instead of `Cpanel::JSON::XS`
|
||||
(see <http://perl5.git.perl.org/perl.git/blob/HEAD:/Porting/Glossary>).
|
||||
|
||||
Note that the build script does **not** automatically fetch modules'
|
||||
dependencies, for now you will need to resolve them and add them to
|
||||
`EMPERL_EXTENSIONS` yourself. (This may be improved upon in the future.)
|
||||
You can try out the script
|
||||
[`experiments/depend.pl`](https://github.com/haukex/webperl/blob/master/experiments/depend.pl),
|
||||
which uses the MetaCPAN API to resolve dependencies.
|
||||
|
||||
|
||||
***
|
||||
|
||||
Additional notes on building WebPerl may be found in the
|
||||
[GitHub Wiki](https://github.com/haukex/webperl/wiki/Building-WebPerl).
|
||||
|
||||
***
|
||||
|
||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, <http://www.igb-berlin.de>
|
||||
|
||||
Please see the ["Legal" page](legal.html) for details.
|
||||
|
||||
***
|
||||
|
||||
You can find the source for this page at
|
||||
<https://github.com/haukex/webperl/blob/gh-pages/building.md>
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
|
||||
# Install the dependencies for "web" via:
|
||||
# $ cpanm --installdeps .
|
||||
|
||||
requires 'Cpanel::JSON::XS';
|
||||
requires 'Plack';
|
||||
requires 'Plack::Middleware::CrossOrigin';
|
||||
requires 'Plack::Middleware::Auth::Digest';
|
||||
@ -0,0 +1,20 @@
|
||||
use warnings;
|
||||
use 5.026;
|
||||
use Time::HiRes qw/gettimeofday tv_interval/;
|
||||
|
||||
my $t0 = [gettimeofday];
|
||||
my @primes = join ',', grep {prime($_)} 1..1000000;
|
||||
my $elapsed = tv_interval($t0);
|
||||
printf "%.3f\n", $elapsed;
|
||||
|
||||
# http://www.rosettacode.org/wiki/Primality_by_trial_division#Perl
|
||||
sub prime {
|
||||
my $n = shift;
|
||||
$n % $_ or return for 2 .. sqrt $n;
|
||||
$n > 1
|
||||
}
|
||||
|
||||
# A quick test: This program, when run
|
||||
# from WebPerl (Firefox): ~7.4s
|
||||
# natively (same machine): ~2.3s
|
||||
# => roughly 3.2 times slower
|
||||
@ -0,0 +1,8 @@
|
||||
|
||||
# Install the dependencies for "experiments" via:
|
||||
# $ cpanm --installdeps .
|
||||
|
||||
requires 'Data::Dump';
|
||||
requires 'Graph';
|
||||
requires 'MetaCPAN::Client';
|
||||
requires 'Path::Class';
|
||||
@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use 5.026;
|
||||
use Getopt::Long qw/ HelpMessage :config posix_default gnu_compat
|
||||
bundling auto_version auto_help /;
|
||||
use Graph ();
|
||||
use Memoize 'memoize';
|
||||
use Memoize::Storable ();
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
depend.pl MODULE(s)
|
||||
OPTIONS:
|
||||
-v | --verbose - more output
|
||||
-t | --want-test - include modules needed for test phase
|
||||
-p | --perl-ver VER - Perl version for corelist (default: 5.026)
|
||||
-c | --cache-file FILE - cache file for MetaCPAN API requests
|
||||
(default: /tmp/.metacpan_deps_cache)
|
||||
-C | --clear-cache - clear cache before running
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A test of resolving module dependences, currently via the MetaCPAN API.
|
||||
(The list of dependencies that MetaCPAN knows about may not always be complete.)
|
||||
|
||||
Outputs a possible install order that should satisfy dependencies.
|
||||
Note this order can change across runs, but theoretically it should
|
||||
always be a valid install order.
|
||||
|
||||
Notes for WebPerl:
|
||||
Could be used in F<build.pl>.
|
||||
I don't really need C<is_installed>.
|
||||
Perhaps instead of C<is_core> I should check if the module exists
|
||||
in the Perl source tree and is enabled in F<config.sh>...
|
||||
|
||||
=cut
|
||||
|
||||
our $VERSION = '0.01-beta';
|
||||
|
||||
GetOptions(
|
||||
'v|verbose' => \(my $VERBOSE),
|
||||
't|want-test' => \(my $WANT_TEST),
|
||||
'p|perl-ver=s' => \(my $PERL_VER='5.026'),
|
||||
'c|cache-file=s' => \(my $CACHE_FILE='/tmp/.metacpan_deps_cache'),
|
||||
'C|clear-cache' => \(my $NO_CACHE),
|
||||
) or HelpMessage(-exitval=>255);
|
||||
HelpMessage(-msg=>'Not enough arguments',-exitval=>255) unless @ARGV;
|
||||
|
||||
|
||||
if ($NO_CACHE && -e $CACHE_FILE)
|
||||
{ unlink($CACHE_FILE)==1 or die "Failed to unlink $CACHE_FILE: $!" }
|
||||
tie my %get_deps_cache, 'Memoize::Storable', $CACHE_FILE;
|
||||
memoize 'get_deps', SCALAR_CACHE=>[HASH=>\%get_deps_cache], LIST_CACHE=>'FAULT';
|
||||
memoize 'is_core';
|
||||
memoize 'is_installed';
|
||||
|
||||
|
||||
my $dep_graph = Graph->new(directed => 1);
|
||||
resolve_deps($_, $dep_graph) for @ARGV;
|
||||
my @topo = $dep_graph->topological_sort;
|
||||
say for reverse @topo;
|
||||
warn "No (non-core) dependencies\n" unless @topo;
|
||||
|
||||
|
||||
use MetaCPAN::Client ();
|
||||
sub get_deps { # will be memoized (and persisted)
|
||||
my ($module) = @_;
|
||||
state $mcpan = MetaCPAN::Client->new();
|
||||
$VERBOSE and say STDERR "Fetching dependencies of $module from MetaCPAN API";
|
||||
return $mcpan->release($mcpan->module($module)->distribution)->dependency;
|
||||
}
|
||||
|
||||
use Module::CoreList ();
|
||||
sub is_core { # will be memoized
|
||||
my ($module,$version) = @_;
|
||||
return Module::CoreList::is_core($module,$version,$PERL_VER);
|
||||
}
|
||||
|
||||
use Module::Load::Conditional ();
|
||||
sub is_installed { # will be memoized
|
||||
my ($module,$version) = @_;
|
||||
return Module::Load::Conditional::check_install(module=>$module,version=>$version);
|
||||
}
|
||||
|
||||
sub resolve_deps {
|
||||
my $module = shift;
|
||||
my $graph = @_ ? shift : Graph->new(directed => 1);
|
||||
for my $dep ( get_deps($module)->@* ) {
|
||||
next if is_core( $dep->{module}, $dep->{version} ); # ignore core modules
|
||||
next if $dep->{module} eq 'perl'; # ignore perl dist itself
|
||||
next unless $dep->{relationship} eq 'requires'; # ignore 'recommends' and 'suggests'
|
||||
die "Unknown relationship '$dep->{relationship}'"
|
||||
unless $dep->{relationship}=~/\A(?:requires|recommends|suggests)\z/;
|
||||
next if $dep->{phase} eq 'develop'; # ignore phase 'develop'
|
||||
next if !$WANT_TEST && $dep->{phase} eq 'test'; # ignore phase 'test' unless user wants it
|
||||
next if $dep->{phase}=~/\Ax_/; # ignore e.g. "x_Dist_Zilla"
|
||||
die "Unknown phase '$dep->{phase}'"
|
||||
unless $dep->{phase}=~/\A(?:configure|build|runtime|test)\z/;
|
||||
my $installed = is_installed( $dep->{module}, $dep->{version} ); # just for info
|
||||
$VERBOSE and say STDERR "$module requires $dep->{module}",
|
||||
$dep->{version} ? " (version $dep->{version})" : " (any version)",
|
||||
" for $dep->{phase}",
|
||||
$installed ? " (installed)" : " (not installed)";
|
||||
$graph->add_edge($module, $dep->{module});
|
||||
die "Fatal: Circular dependency detected (just added $module->$dep->{module})"
|
||||
if $graph->has_a_cycle;
|
||||
resolve_deps($dep->{module}, $graph)
|
||||
}
|
||||
return $graph;
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
/database.db
|
||||
/web/webperl.js
|
||||
/web/emperl.*
|
||||
/gui_basic
|
||||
/gui_basic.exe
|
||||
@ -0,0 +1,50 @@
|
||||
|
||||
WebPerl Basic GUI Example
|
||||
=========================
|
||||
|
||||
This is a demo of a very basic GUI using WebPerl. It consists of a
|
||||
local web server, which includes code to access an SQLite database,
|
||||
and this web server also serves up WebPerl code to a browser, where
|
||||
the GUI is implemented as HTML with Perl.
|
||||
|
||||
To get this to work, you will need to copy the `webperl.js` and the
|
||||
three `emperl.*` files from the main `web` directory to the `web`
|
||||
subdirectory in this project.
|
||||
|
||||
Note that this should not be considered production-ready, as there
|
||||
are several key features missing, such as HTTPS or access control.
|
||||
|
||||
Also, a limitation is that the server does not know when the browser
|
||||
window is closed, so it must be stopped manually.
|
||||
|
||||
You can pack this application into a single executable using:
|
||||
|
||||
DOING_PAR_PACKER=1 pp -o gui_basic -z 9 -x -a gui_basic_app.psgi -a web gui_basic.pl
|
||||
|
||||
Note: I'm not yet sure why, but sometimes this fails with errors such
|
||||
as *"error extracting info from -c/-x file"*, in that case just try
|
||||
the above command again.
|
||||
|
||||
|
||||
Author, Copyright, and License
|
||||
==============================
|
||||
|
||||
**WebPerl - <http://webperl.zero-g.net>**
|
||||
|
||||
Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, <http://www.igb-berlin.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl 5 itself: either the GNU General Public
|
||||
License as published by the Free Software Foundation (either version 1,
|
||||
or, at your option, any later version), or the "Artistic License" which
|
||||
comes with Perl 5.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
**WITHOUT ANY WARRANTY**; without even the implied warranty of
|
||||
**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
|
||||
See the licenses for details.
|
||||
|
||||
You should have received a copy of the licenses along with this program.
|
||||
If not, see <http://perldoc.perl.org/index-licence.html>.
|
||||
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use 5.018;
|
||||
use FindBin;
|
||||
use File::Spec::Functions qw/catdir/;
|
||||
use Plack::Runner ();
|
||||
use Starman ();
|
||||
use Browser::Open qw/open_browser/;
|
||||
|
||||
# This just serves up gui_basic_app.psgi in the Starman web server.
|
||||
# You can also say "plackup gui_basic_app.psgi" instead.
|
||||
|
||||
BEGIN {
|
||||
my $dir = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
|
||||
chdir $dir or die "chdir $dir: $!";
|
||||
}
|
||||
|
||||
my $SERV_PORT = 5000;
|
||||
my $THE_APP = 'gui_basic_app.psgi';
|
||||
|
||||
# AFAICT, both Plack::Runner->new(@args) and ->parse_options(@argv) set
|
||||
# options, and these options are shared between "Starman::Server"
|
||||
# (documented in "starman") and "Plack::Runner" (documented in "plackup").
|
||||
my @args = (
|
||||
server => 'Starman', loader => 'Delayed', env => 'development',
|
||||
version_cb => sub { print "Starman $Starman::VERSION\n" } );
|
||||
my @argv = ( '--listen', "localhost:$SERV_PORT", $THE_APP );
|
||||
my $runner = Plack::Runner->new(@args);
|
||||
$runner->parse_options(@argv);
|
||||
$runner->set_options(argv => \@argv);
|
||||
die "loader shouldn't be Restarter" if $runner->{loader} eq 'Restarter';
|
||||
|
||||
if ($ENV{DOING_PAR_PACKER}) {
|
||||
require Plack::Util;
|
||||
Plack::Util::load_psgi($THE_APP); # for dependency resolution
|
||||
# arrange to have the server shut down in a few moments
|
||||
my $procpid = $$;
|
||||
my $pid = fork();
|
||||
if (!defined $pid) { die "fork failed" }
|
||||
elsif ($pid==0) { sleep 5; kill 'INT', $procpid; exit; } # child
|
||||
print "====> Please wait a few seconds...\n";
|
||||
}
|
||||
else {
|
||||
# There's a small chance here that the browser could open before the server
|
||||
# starts up. In that case, a reload of the browser window is needed.
|
||||
print "Attempting to open in browser: http://localhost:$SERV_PORT/\n";
|
||||
open_browser("http://localhost:$SERV_PORT/");
|
||||
}
|
||||
|
||||
$runner->run;
|
||||
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use 5.018;
|
||||
use Plack::MIME;
|
||||
use Plack::Builder qw/builder enable mount/;
|
||||
use Plack::Request ();
|
||||
use Plack::Response (); # declare compile-time dependency
|
||||
use Cpanel::JSON::XS qw/decode_json encode_json/;
|
||||
use DBI ();
|
||||
use DBD::SQLite (); # declare compile-time dependency
|
||||
use HTML::Tiny ();
|
||||
|
||||
# This is the server-side code.
|
||||
|
||||
# note we rely on gui_basic.pl to set the working directory correctly
|
||||
my $SERV_ROOT = 'web';
|
||||
my $DB_FILE = 'database.db';
|
||||
|
||||
my $dbh = DBI->connect("DBI:SQLite:dbname=$DB_FILE",
|
||||
undef, undef, { RaiseError=>1, AutoCommit=>1 });
|
||||
|
||||
$dbh->do(q{ CREATE TABLE IF NOT EXISTS FooBar (
|
||||
foo VARCHAR(255), bar VARCHAR(255) ) });
|
||||
|
||||
# This sends HTML to the browser, but we could also send JSON
|
||||
# and build the HTML table dynamically in the browser.
|
||||
my $app_select = sub {
|
||||
state $html = HTML::Tiny->new;
|
||||
state $sth_select = $dbh->prepare(q{ SELECT rowid,foo,bar FROM FooBar });
|
||||
$sth_select->execute;
|
||||
my $data = $sth_select->fetchall_arrayref;
|
||||
my $out = $html->table(
|
||||
[ \'tr',
|
||||
[ \'th', 'rowid', 'foo', 'bar' ],
|
||||
map { [ \'td', @$_ ] } @$data
|
||||
] );
|
||||
return [ 200, [ "Content-Type"=>"text/html" ], [ $out ] ];
|
||||
};
|
||||
|
||||
# This is an example of one way to communicate with JSON.
|
||||
my $app_insert = sub {
|
||||
my $req = Plack::Request->new(shift);
|
||||
state $sth_insert = $dbh->prepare(q{ INSERT INTO FooBar (foo,bar) VALUES (?,?) });
|
||||
my $rv = eval { # catch errors and return as 500 Server Error
|
||||
my $content = decode_json( $req->content );
|
||||
$sth_insert->execute($content->{foo}, $content->{bar});
|
||||
{ ok=>1 }; # return value from eval, sent to client as JSON
|
||||
}; my $e = $@||'unknown error';
|
||||
my $res = $req->new_response($rv ? 200 : 500);
|
||||
$res->content_type($rv ? 'application/json' : 'text/plain');
|
||||
$res->body($rv ? encode_json($rv) : 'Server Error: '.$e);
|
||||
return $res->finalize;
|
||||
};
|
||||
|
||||
Plack::MIME->add_type(".js" => "application/javascript");
|
||||
Plack::MIME->add_type(".data" => "application/octet-stream");
|
||||
Plack::MIME->add_type(".mem" => "application/octet-stream");
|
||||
Plack::MIME->add_type(".wasm" => "application/wasm");
|
||||
|
||||
builder {
|
||||
enable 'SimpleLogger';
|
||||
enable 'Static',
|
||||
path => sub { s#\A/\z#/index.html#; /\.(?:html?|js|css|data|mem|wasm|pl)\z/i },
|
||||
root => $SERV_ROOT;
|
||||
mount '/select' => $app_select;
|
||||
mount '/insert' => $app_insert;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl GUI Demo</title>
|
||||
<script src="webperl.js"></script>
|
||||
<script type="text/perl" src="web.pl"></script>
|
||||
</head>
|
||||
<body style="font-family:sans-serif;">
|
||||
<h1>WebPerl GUI Demo</h1>
|
||||
|
||||
<div id="datatable"><i>No data loaded yet...</i></div>
|
||||
<div><button id="reload_data">Reload Data</button></div>
|
||||
|
||||
<div style="margin-top:1em">
|
||||
<div>
|
||||
<label for="input_foo">foo</label>
|
||||
<input type="text" id="input_foo">
|
||||
</div>
|
||||
<div>
|
||||
<label for="input_bar">bar</label>
|
||||
<input type="text" id="input_bar">
|
||||
</div>
|
||||
<div>
|
||||
<button id="do_insert">Insert Data</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Powered by <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a> (beta)</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,69 @@
|
||||
#!perl
|
||||
use warnings;
|
||||
use 5.028;
|
||||
use WebPerl qw/js js_new sub1 encode_json/;
|
||||
|
||||
# This is the code that WebPerl runs in the browser. It is loaded by index.html.
|
||||
|
||||
sub do_xhr {
|
||||
my %args = @_;
|
||||
die "must specify a url" unless $args{url};
|
||||
$args{fail} ||= sub { js('window')->alert(shift) };
|
||||
my $xhr = js_new('XMLHttpRequest');
|
||||
$xhr->addEventListener("error", sub1 {
|
||||
$args{fail}->("XHR Error on $args{url}: ".(shift->{textContent}||"unknown"));
|
||||
return;
|
||||
});
|
||||
$xhr->addEventListener("load", sub1 {
|
||||
if ($xhr->{status}==200) {
|
||||
$args{done}->($xhr->{response}) if $args{done};
|
||||
}
|
||||
else {
|
||||
$args{fail}->("XHR Error on $args{url}: ".$xhr->{status}." ".$xhr->{statusText});
|
||||
}
|
||||
return;
|
||||
});
|
||||
$xhr->addEventListener("loadend", sub1 {
|
||||
$args{always}->() if $args{always};
|
||||
return;
|
||||
});
|
||||
# when given data, default to POST (JSON), otherwise GET
|
||||
if ($args{data}) {
|
||||
$xhr->open($args{method}||'POST', $args{url});
|
||||
$xhr->setRequestHeader('Content-Type', 'application/json');
|
||||
$xhr->send(encode_json($args{data}));
|
||||
}
|
||||
else {
|
||||
$xhr->open($args{method}||'GET', $args{url});
|
||||
$xhr->send();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
my $document = js('document');
|
||||
|
||||
my $btn_reload = $document->getElementById('reload_data');
|
||||
sub do_reload {
|
||||
state $dtbl = $document->getElementById('datatable');
|
||||
$btn_reload->{disabled} = 1;
|
||||
do_xhr(url => 'select',
|
||||
done => sub { $dtbl->{innerHTML} = shift; },
|
||||
always => sub { $btn_reload->{disabled} = 0; } );
|
||||
return;
|
||||
}
|
||||
$btn_reload->addEventListener("click", \&do_reload);
|
||||
|
||||
my $btn_insert = $document->getElementById('do_insert');
|
||||
sub do_insert {
|
||||
state $txt_foo = $document->getElementById('input_foo');
|
||||
state $txt_bar = $document->getElementById('input_bar');
|
||||
$btn_insert->{disabled} = 1;
|
||||
do_xhr(url => 'insert',
|
||||
data => { foo=>$txt_foo->{value}, bar=>$txt_bar->{value} },
|
||||
always => sub { $btn_insert->{disabled} = 0; do_reload; } );
|
||||
return;
|
||||
}
|
||||
$btn_insert->addEventListener("click", \&do_insert);
|
||||
|
||||
do_reload; # initial load
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
/public/webperl.js
|
||||
/public/emperl.*
|
||||
/gui_sweet
|
||||
/gui_sweet.exe
|
||||
@ -0,0 +1,44 @@
|
||||
|
||||
WebPerl Advanced GUI Example
|
||||
============================
|
||||
|
||||
Similar to the "WebPerl Basic GUI Example", this is a demo of a GUI
|
||||
using WebPerl, but using [Bootstrap](https://getbootstrap.com/)
|
||||
and [jQuery](https://jquery.com/) instead of plain JavaScript,
|
||||
and [Mojolicious](https://mojolicious.org/) instead of plain Plack.
|
||||
|
||||
To get this to work, you will need to copy the `webperl.js` and the
|
||||
three `emperl.*` files from the main `web` directory to the `public`
|
||||
subdirectory in this project.
|
||||
|
||||
Also, a limitation is that the server does not know when the browser
|
||||
window is closed, so it must be stopped manually.
|
||||
|
||||
You can pack this application into a single executable using `do_pp.pl`.
|
||||
Note: I'm not yet sure why, but sometimes this fails with errors such
|
||||
as *"error extracting info from -c/-x file"*, in that case just try
|
||||
the command again.
|
||||
|
||||
|
||||
Author, Copyright, and License
|
||||
==============================
|
||||
|
||||
**WebPerl - <http://webperl.zero-g.net>**
|
||||
|
||||
Copyright (c) 2019 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, <http://www.igb-berlin.de>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl 5 itself: either the GNU General Public
|
||||
License as published by the Free Software Foundation (either version 1,
|
||||
or, at your option, any later version), or the "Artistic License" which
|
||||
comes with Perl 5.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
**WITHOUT ANY WARRANTY**; without even the implied warranty of
|
||||
**MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE**.
|
||||
See the licenses for details.
|
||||
|
||||
You should have received a copy of the licenses along with this program.
|
||||
If not, see <http://perldoc.perl.org/index-licence.html>.
|
||||
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use strict;
|
||||
use File::Basename qw/fileparse/;
|
||||
use File::Spec::Functions qw/catfile/;
|
||||
use File::Temp qw/tempfile/;
|
||||
|
||||
# this attempts to locate Mojo's default server.crt/server.key files
|
||||
chomp( my $dir = `perldoc -l Mojo::IOLoop::Server` );
|
||||
die "perldoc -l failed, \$?=$?" if $? || !-e $dir;
|
||||
(undef, $dir) = fileparse($dir);
|
||||
|
||||
# set up a file for pp's -A switch
|
||||
my ($tfh, $tfn) = tempfile(UNLINK=>1);
|
||||
print {$tfh} catfile($dir,'resources','server.crt'),";server.crt\n";
|
||||
print {$tfh} catfile($dir,'resources','server.key'),";server.key\n";
|
||||
close $tfh;
|
||||
|
||||
my @args = (qw/ -a public -a templates -A /, $tfn);
|
||||
|
||||
local $ENV{DOING_PAR_PACKER}=1;
|
||||
system(qw/ pp -o gui_sweet -z 9 -x /,@args,'gui_sweet.pl')==0
|
||||
or die "pp failed, \$?=$?";
|
||||
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env perl
|
||||
use Mojolicious::Lite;
|
||||
use Mojo::Util qw/md5_sum/;
|
||||
use FindBin;
|
||||
use File::Spec::Functions qw/catdir/;
|
||||
use Browser::Open qw/open_browser/;
|
||||
|
||||
# This is the server-side code.
|
||||
|
||||
my $SERV_PORT = 3000;
|
||||
|
||||
my ($SSLCERTS,$HOMEDIR);
|
||||
BEGIN {
|
||||
$HOMEDIR = $ENV{PAR_TEMP} ? catdir($ENV{PAR_TEMP},'inc') : $FindBin::Bin;
|
||||
chdir $HOMEDIR or die "chdir $HOMEDIR: $!";
|
||||
# do_pp.pl pulls the default Mojo SSL certs into the archive for us
|
||||
$SSLCERTS = $ENV{PAR_TEMP} ? '?cert=./server.crt&key=./server.key' : '';
|
||||
}
|
||||
|
||||
app->static->paths([catdir($HOMEDIR,'public')]);
|
||||
app->renderer->paths([catdir($HOMEDIR,'templates')]);
|
||||
app->secrets(['Hello, Perl World!']);
|
||||
app->types->type(js => "application/javascript");
|
||||
app->types->type(data => "application/octet-stream");
|
||||
app->types->type(mem => "application/octet-stream");
|
||||
app->types->type(wasm => "application/wasm");
|
||||
|
||||
# Authentication and browser-launching stuff (optional)
|
||||
my $TOKEN = md5_sum(rand(1e15).time);
|
||||
hook before_server_start => sub {
|
||||
my ($server, $app) = @_;
|
||||
my @urls = map {Mojo::URL->new($_)->query(token=>$TOKEN)} @{$server->listen};
|
||||
my $url = shift @urls or die "No urls?";
|
||||
if ($ENV{DOING_PAR_PACKER}) {
|
||||
# arrange to have the server shut down in a few moments
|
||||
my $procpid = $$;
|
||||
my $pid = fork();
|
||||
if (!defined $pid) { die "fork failed" }
|
||||
elsif ($pid==0) { sleep 5; kill 'USR1', $procpid; exit; } # child
|
||||
print "====> Please wait a few seconds...\n";
|
||||
$SIG{USR1} = sub { $server->stop; exit };
|
||||
}
|
||||
else {
|
||||
print "Attempting to open in browser: $url\n";
|
||||
open_browser($url);
|
||||
}
|
||||
};
|
||||
under sub {
|
||||
my $c = shift;
|
||||
return 1 if ($c->param('token')//'') eq $TOKEN;
|
||||
$c->render(text => 'Bad token!', status => 403);
|
||||
return undef;
|
||||
};
|
||||
|
||||
get '/' => sub { shift->render } => 'index';
|
||||
|
||||
post '/example' => sub {
|
||||
my $c = shift;
|
||||
my $data = $c->req->json;
|
||||
# can do anything here, this is just an example
|
||||
$data->{string} = reverse $data->{string};
|
||||
$c->render(json => $data);
|
||||
};
|
||||
|
||||
app->start('daemon', '-l', "https://localhost:$SERV_PORT$SSLCERTS");
|
||||
|
||||
__DATA__
|
||||
|
||||
@@ index.html.ep
|
||||
% layout 'main', title => 'WebPerl GUI Demo';
|
||||
<main role="main" class="container">
|
||||
<div>
|
||||
<h1>WebPerl Advanced GUI Demo</h1>
|
||||
<p class="lead">Hello, Perl World!</p>
|
||||
<div id="buttons"></div>
|
||||
</div>
|
||||
</main>
|
||||
@ -0,0 +1,44 @@
|
||||
#!perl
|
||||
use warnings;
|
||||
use 5.028;
|
||||
use WebPerl qw/js sub1 encode_json/;
|
||||
|
||||
# This is the code that WebPerl runs in the browser. It is loaded by index.html.
|
||||
|
||||
my $window = js('window');
|
||||
my $document = js('document');
|
||||
my $jq = js('jQuery');
|
||||
|
||||
sub do_ajax {
|
||||
my %args = @_;
|
||||
die "must specify a url" unless $args{url};
|
||||
$args{fail} ||= sub { $window->alert(shift) };
|
||||
$jq->ajax( $args{url}, {
|
||||
$args{data} # when given data, default to POST (JSON), otherwise GET
|
||||
? ( method=>$args{method}||'POST',
|
||||
data=>encode_json($args{data}) )
|
||||
: ( method=>$args{method}||'GET' ),
|
||||
} )->done( sub1 {
|
||||
$args{done}->(shift) if $args{done};
|
||||
} )->fail( sub1 {
|
||||
my ($jqXHR, $textStatus, $errorThrown) = @_;
|
||||
$args{fail}->("AJAX Failed! ($errorThrown)");
|
||||
} )->always( sub1 {
|
||||
$args{always}->() if $args{always};
|
||||
} );
|
||||
return;
|
||||
}
|
||||
|
||||
# slightly hacky way to get the access token, but it works fine
|
||||
my ($token) = $window->{location}{search}=~/\btoken=([a-fA-F0-9]+)\b/;
|
||||
|
||||
my $btn = $jq->('<button>', { text=>"Click me!" } );
|
||||
$btn->click(sub {
|
||||
$btn->prop('disabled',1);
|
||||
do_ajax( url=>"/example?token=$token",
|
||||
data => { string=>"rekcaH lreP rehtonA tsuJ" },
|
||||
done => sub { $window->alert("The server says: ".shift->{string}) },
|
||||
always => sub { $btn->prop('disabled',0); } );
|
||||
} );
|
||||
$btn->appendTo( $jq->('#buttons') );
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><%= title %></title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<style>
|
||||
body { padding-top: 5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
||||
<a class="navbar-brand" href="#"><%= title %></a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="dropdown01" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
|
||||
<div class="dropdown-menu" aria-labelledby="dropdown01">
|
||||
<a class="dropdown-item" href="#">Action</a>
|
||||
<a class="dropdown-item" href="#">Another action</a>
|
||||
<a class="dropdown-item" href="#">Something else here</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<%= content %>
|
||||
|
||||
<!-- Bootstrap wants its script tags at the end of the body element, so we'll put everything here: -->
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||
<script src="webperl.js"></script>
|
||||
<script type="text/perl" src="web.pl"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,50 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl Perl 6 Demos (Experimental)</title>
|
||||
|
||||
<script src="webperl.js"></script>
|
||||
|
||||
<!-- Please see the documentation at http://webperl.zero-g.net/perl6.html -->
|
||||
|
||||
<!-- Example 1: A really basic script -->
|
||||
<script type="text/perl6">
|
||||
print "Hello, Perl 6 World!\n";
|
||||
</script>
|
||||
|
||||
<!-- Example 2: Accessing JavaScript -->
|
||||
<script type="text/perl6">
|
||||
my $document = EVAL(:lang<JavaScript>, 'return document');
|
||||
$document.getElementById('my_button')
|
||||
.addEventListener("click", -> $event {
|
||||
print "You clicked 'Testing!'\n";
|
||||
} );
|
||||
</script>
|
||||
|
||||
<!-- Optional STDOUT/STDERR text area (if you don't use this, output goes to Javascript console) -->
|
||||
<script>
|
||||
window.addEventListener("load", function () {
|
||||
document.getElementById('output')
|
||||
.appendChild( Raku.makeOutputTextarea() );
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>This is a demo of the
|
||||
<a href="http://webperl.zero-g.net/perl6.html" target="_blank">experimental
|
||||
Perl 6 support</a> in
|
||||
<a href="http://webperl.zero-g.net" target="_blank">WebPerl</a>!</p>
|
||||
|
||||
<p><em>Currently only works in Chrome (needs BigInt support) and
|
||||
may take a few seconds to load.</em></p>
|
||||
|
||||
<div id="output"></div>
|
||||
<div id="buttons">
|
||||
<button id="my_button">Testing!</button>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use strict;
|
||||
use FindBin;
|
||||
use Path::Class qw/dir/;
|
||||
use HTTP::Tiny;
|
||||
use File::Copy qw/copy/;
|
||||
$|++;
|
||||
|
||||
# Quick & dirty script to patch P6 into the "web" dir
|
||||
|
||||
# Note: To restore webperl.js to the original version:
|
||||
# $ git checkout web/webperl.js
|
||||
|
||||
my $p6url = 'https://perl6.github.io/6pad/gen/eval_code.js';
|
||||
|
||||
my $mydir = dir($FindBin::Bin);
|
||||
my $webdir = $mydir->parent->parent->subdir('web');
|
||||
|
||||
print "Patching experimental Perl 6 support into ",$webdir->relative,"...\n";
|
||||
|
||||
my $wpfile = $webdir->file('webperl.js');
|
||||
die "File structure not as I expected" unless -e $wpfile;
|
||||
|
||||
my $http = HTTP::Tiny->new();
|
||||
my $jsfile = $webdir->file('perl6.js');
|
||||
print "$p6url: ";
|
||||
my $resp = $http->mirror($p6url, "$jsfile");
|
||||
print "$resp->{status} $resp->{reason}\n";
|
||||
die unless $resp->{success};
|
||||
print "-> mirrored to ",$jsfile->relative,"\n";
|
||||
|
||||
my $wp = $wpfile->slurp(iomode=>'<:raw:encoding(UTF-8)');
|
||||
$wp =~ s{
|
||||
^ \N* \bbegin_webperl6_patch\b \N* $
|
||||
.*
|
||||
^ \N* \bend_webperl6_patch\b \N* $
|
||||
}{}msxi;
|
||||
die "I thought I clobbered the webperl6.js patch, why is there still a reference to Raku?"
|
||||
if $wp=~/\bRaku\./;
|
||||
my $wp6file = $mydir->file('webperl6.js');
|
||||
my $wp6 = $wp6file->slurp(iomode=>'<:raw:encoding(UTF-8)');
|
||||
1 while chomp($wp6);
|
||||
$wpfile->spew(iomode=>'>:raw:encoding(UTF-8)', $wp.$wp6);
|
||||
print "Patched ",$wp6file->relative," into ",$wpfile->relative,"\n";
|
||||
|
||||
for my $f ($mydir->children) {
|
||||
next unless $f->basename=~/(?:html?|css)\z/i;
|
||||
link_or_copy($f, $webdir);
|
||||
}
|
||||
|
||||
|
||||
sub link_or_copy {
|
||||
my ($src,$dest) = @_;
|
||||
die "Not a dir: $dest" unless -d $dest;
|
||||
$dest = $dest->file( $src->basename );
|
||||
if ( eval { symlink("",""); 1 } ) { # we have symlink support
|
||||
if (!-l $dest) {
|
||||
$dest->remove or die "$dest: $!" if -e $dest;
|
||||
my $targ = $src->relative( $dest->dir );
|
||||
symlink($targ,$dest) or die "symlink: $!";
|
||||
print "Linked ",$dest->relative," to $targ\n";
|
||||
}
|
||||
else { print "Link ",$dest->relative," exists\n"; }
|
||||
}
|
||||
else {
|
||||
$dest->remove or die "$dest: $!" if -e $dest;
|
||||
copy($src,$dest) or die "copy: $!";
|
||||
print "Copied ",$src->relative," to ",$dest->relative,"\n";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl Perl 6 Experiments</title>
|
||||
|
||||
<script src="webperl.js"></script>
|
||||
|
||||
<!--
|
||||
The following is a demo of Perl 5 and Perl 6 calling each other via JavaScript.
|
||||
-->
|
||||
|
||||
<script>
|
||||
window.Foo = {
|
||||
set: function (x,y) { window.Foo[x]=y }, // workaround, see P6 below
|
||||
};
|
||||
</script>
|
||||
|
||||
<script type="text/perl">
|
||||
use warnings;
|
||||
use 5.028;
|
||||
|
||||
sub hello {
|
||||
my $x = shift;
|
||||
say "Hello from Perl 5! You said '$x'";
|
||||
}
|
||||
|
||||
my $Foo = js('window.Foo');
|
||||
$Foo->{p5} = \&hello;
|
||||
|
||||
js('document')->getElementById('btn_p5')
|
||||
->addEventListener("click", sub {
|
||||
say "This is Perl 5, attempting to call Perl 6...";
|
||||
$Foo->p6("I am Perl 5!");
|
||||
} );
|
||||
|
||||
say "Perl 5 is ready.";
|
||||
</script>
|
||||
|
||||
<script type="text/raku">
|
||||
|
||||
sub hello ($x) {
|
||||
say "Hello from Perl 6! You said '$x'"
|
||||
}
|
||||
|
||||
my $Foo = EVAL(:lang<JavaScript>, 'return window.Foo');
|
||||
# I'm not yet sure why the following doesn't work, Foo.set is a workaround
|
||||
#$Foo<p6> = &hello;
|
||||
$Foo.set("p6", &hello);
|
||||
|
||||
my $document = EVAL(:lang<JavaScript>, 'return document');
|
||||
$document.getElementById('btn_p6')
|
||||
.addEventListener("click", -> $event {
|
||||
say "This is Perl 6, attempting to call Perl 5...";
|
||||
$Foo.p5("I am Perl 6!");
|
||||
} );
|
||||
|
||||
say "Perl 6 is ready.";
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>See the JS console! Don't click the buttons until both languages are ready.</p>
|
||||
|
||||
<div id="buttons">
|
||||
<button id="btn_p5">Perl 5</button>
|
||||
<button id="btn_p6">Perl 6</button>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,148 @@
|
||||
"use strict"; /* DO NOT EDIT THIS LINE! begin_webperl6_patch */
|
||||
|
||||
/***** NOTICE: This is part of the experimental WebPerl Perl 6 support.
|
||||
* This file (webperl6.js) is currently patched into webperl.js by 6init.pl.
|
||||
* There is currently a fair amount of duplication between the following code
|
||||
* and webperl.js that should probably be reduced.
|
||||
* This file should eventually be merged permanently into webperl.js.
|
||||
*/
|
||||
|
||||
/** ***** WebPerl - http://webperl.zero-g.net *****
|
||||
*
|
||||
* Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
* at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
* Berlin, Germany, http://www.igb-berlin.de
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the same terms as Perl 5 itself: either the GNU General Public
|
||||
* License as published by the Free Software Foundation (either version 1,
|
||||
* or, at your option, any later version), or the "Artistic License" which
|
||||
* comes with Perl 5.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the licenses for details.
|
||||
*
|
||||
* You should have received a copy of the licenses along with this program.
|
||||
* If not, see http://perldoc.perl.org/index-licence.html
|
||||
**/
|
||||
|
||||
// I'm using "Raku" because the Hamming distance from Perl <-> Perl6 is too small for me,
|
||||
// it's too much of a risk for typos since webperl.js also provides the "Perl" object.
|
||||
// But the following functions are currently available on both the Raku.* and Perl6.* objects:
|
||||
// .init(), .eval(), .addStateChangeListener(), .makeOutputTextarea()
|
||||
// but everything else, such as Raku.state or Raku.output, needs to go via the Raku object.
|
||||
var Raku = {
|
||||
state: "Uninitialized", // user may read (only!) this
|
||||
// internal variables:
|
||||
stdout_buf: "", stderr_buf: "", // for our default Raku.output implementation
|
||||
};
|
||||
var Perl6 = {};
|
||||
|
||||
Raku.changeState = function (newState) {
|
||||
if (Raku.state==newState) return;
|
||||
var oldState = Raku.state;
|
||||
Raku.state = newState;
|
||||
for( var i=0 ; i<Raku.stateChangeListeners.length ; i++ )
|
||||
Raku.stateChangeListeners[i](oldState,newState);
|
||||
};
|
||||
Raku.stateChangeListeners = [ function (from,to) {
|
||||
console.debug("Raku: state changed from "+from+" to "+to);
|
||||
} ];
|
||||
Raku.addStateChangeListener = Perl6.addStateChangeListener = function (handler) {
|
||||
Raku.stateChangeListeners.push(handler);
|
||||
};
|
||||
|
||||
// chan: 1=STDOUT, 2=STDERR
|
||||
// implementations are free to ignore the "chan" argument if they want to merge the two streams
|
||||
Raku.output = function (str,chan) { // can be overridden by the user
|
||||
var buf = chan==2 ? 'stderr_buf' : 'stdout_buf';
|
||||
Raku[buf] += str;
|
||||
var pos = Raku[buf].indexOf("\n");
|
||||
while (pos>-1) {
|
||||
console.log( chan==2?"STDERR":"STDOUT", Raku[buf].slice(0,pos) );
|
||||
Raku[buf] = Raku[buf].slice(pos+1);
|
||||
pos = Raku[buf].indexOf("\n");
|
||||
}
|
||||
};
|
||||
|
||||
Raku.makeOutputTextarea = Perl6.makeOutputTextarea = function (id) {
|
||||
var ta = document.createElement('textarea');
|
||||
if (id) ta.id = id;
|
||||
ta.rows = 24; ta.cols = 80;
|
||||
ta.setAttribute("readonly",true);
|
||||
Raku.output = function (str) {
|
||||
ta.value = ta.value + str;
|
||||
ta.scrollTop = ta.scrollHeight;
|
||||
};
|
||||
return ta;
|
||||
};
|
||||
|
||||
Raku.init = Perl6.init = function (readyCallback) {
|
||||
if (Raku.state != "Uninitialized")
|
||||
throw "Raku: can't call init in state "+Raku.state;
|
||||
Raku.changeState("Initializing");
|
||||
var baseurl = Perl.Util.baseurl(getScriptURL()); // from webperl.js
|
||||
|
||||
// NOTE that NQP_STDOUT currently gets handed HTML,
|
||||
// so we jump through some hoops to decode it here:
|
||||
var decode_div = document.createElement('div');
|
||||
window.NQP_STDOUT = function (str) {
|
||||
str = str.replace(/[\<\>]/g,''); // declaw unexpected tags
|
||||
decode_div.innerHTML = str;
|
||||
str = decode_div.textContent;
|
||||
decode_div.textContent = '';
|
||||
Raku.output(str,1);
|
||||
};
|
||||
|
||||
console.debug("Raku: Fetching Perl6...");
|
||||
var script = document.createElement('script');
|
||||
script.async = true; script.defer = true;
|
||||
// Order is important here: 1. Add to DOM, 2. set onload, 3. set src
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
script.onload = function () {
|
||||
Raku.eval = Perl6.eval = window.evalP6;
|
||||
Raku.changeState("Ready");
|
||||
if (readyCallback) readyCallback();
|
||||
};
|
||||
script.src = baseurl+"/perl6.js";
|
||||
}
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
var scripts = [];
|
||||
var script_src;
|
||||
document.querySelectorAll("script[type='text/perl6'],script[type='text/raku']")
|
||||
.forEach(function (el) {
|
||||
if (el.src) {
|
||||
if (script_src || scripts.length)
|
||||
console.error('Only a single Perl6 script may be loaded via "script src=", ignoring others');
|
||||
else
|
||||
script_src = el.src;
|
||||
}
|
||||
else {
|
||||
if (script_src)
|
||||
console.error('Only a single Perl6 script may be loaded via "script src=", ignoring others');
|
||||
else
|
||||
scripts.push(el.innerHTML);
|
||||
}
|
||||
});
|
||||
if (script_src) {
|
||||
console.debug("Raku: Found a script with src, fetching and running...", script_src);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function () {
|
||||
var code = this.responseText;
|
||||
Raku.init(function () { Raku.eval(code); });
|
||||
});
|
||||
xhr.open("GET", script_src);
|
||||
xhr.send();
|
||||
}
|
||||
else if (scripts.length) {
|
||||
console.debug("Raku: Found",scripts.length,"embedded script(s), autorunning...");
|
||||
var code = scripts.join(";\n");
|
||||
Raku.init(function () { Raku.eval(code); });
|
||||
}
|
||||
else console.debug("Raku: No embedded scripts");
|
||||
});
|
||||
|
||||
/* DO NOT EDIT THIS LINE! end_webperl6_patch */
|
||||
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use strict;
|
||||
use Data::Dump;
|
||||
use IO::Socket;
|
||||
|
||||
# $ git clone https://github.com/novnc/websockify
|
||||
# $ cd websockify
|
||||
# $ ./run 2345 localhost:2346
|
||||
|
||||
my $serv = IO::Socket::INET->new(
|
||||
LocalAddr => 'localhost',
|
||||
LocalPort => 2346,
|
||||
Proto => 'tcp',
|
||||
Listen => 5,
|
||||
Reuse => 1 ) or die $@;
|
||||
|
||||
# really dumb server
|
||||
print "Listening...\n";
|
||||
while (my $client = $serv->accept()) {
|
||||
print "Got a client...\n";
|
||||
print $client "Hello, Perl!\n";
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
use warnings;
|
||||
use 5.028;
|
||||
use Socket;
|
||||
use Fcntl qw/F_GETFL F_SETFL O_NONBLOCK/;
|
||||
use IO::Select;
|
||||
use Data::Dumper;
|
||||
$Data::Dumper::Useqq=1;
|
||||
|
||||
my $port = 2345;
|
||||
my $iaddr = inet_aton("localhost") || die "host not found";
|
||||
my $paddr = sockaddr_in($port, $iaddr);
|
||||
|
||||
# Note: Emscripten apparently doesn't like NONBLOCK being passed to socket(),
|
||||
# and I couldn't get setsockopt to work yet - but the following works.
|
||||
# https://github.com/kripken/emscripten/blob/d08bf13/tests/sockets/test_sockets_echo_client.c#L166
|
||||
# everything is async - need "our $sock" here so it doesn't go out of scope at end of file
|
||||
socket(our $sock, PF_INET, SOCK_STREAM, getprotobyname("tcp")) or die "socket: $!";
|
||||
my $flags = fcntl($sock, F_GETFL, 0) or die "get flags: $!";
|
||||
fcntl($sock, F_SETFL, $flags | O_NONBLOCK) or die "set flags: $!";
|
||||
connect $sock, $paddr or !$!{EINPROGRESS} && die "connect: $!";
|
||||
|
||||
# so far so good... but probably should just use something like IO::Async instead
|
||||
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl XTerm.js Test</title>
|
||||
|
||||
<!--cacheable--><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@3.7.0/dist/xterm.css" integrity="sha256-OSfRj4jMeYVFSwgcvVvKj4V0+mwqSP9YJjyEJe7dmK0=" crossorigin="anonymous" />
|
||||
<!--cacheable--><script src="https://cdn.jsdelivr.net/npm/xterm@3.7.0/dist/xterm.js" integrity="sha256-gIILiZzLBFrmY1dzcKJC2Nmw4o9ISITTNsro2rf8svM=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="webperl.js"></script>
|
||||
|
||||
<script>
|
||||
"use strict";
|
||||
window.addEventListener('load', function () {
|
||||
var term = new Terminal();
|
||||
term.open(document.getElementById('terminal'));
|
||||
Perl.output = function (str) { term.write(str) };
|
||||
Module.preRun.push(function () { ENV.TERM = "xterm" });
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/perl">
|
||||
use warnings;
|
||||
use strict;
|
||||
use Term::ANSIColor qw/colored/;
|
||||
|
||||
print colored("Hello, Color World!\n", 'black on_yellow');
|
||||
|
||||
# Possible To-Do for Later: can we accept input from XTerm?
|
||||
# might not be so easy: https://github.com/xtermjs/xterm.js/issues/1546#issuecomment-402547923
|
||||
# (keypresses are events, but reading from STDIN is normally blocking...)
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="terminal"></div>
|
||||
<p><a href="http://xtermjs.org/" target="_blank">xterm.js</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,90 +0,0 @@
|
||||
|
||||
\[ [Using](using.html) -
|
||||
[Building](building.html) -
|
||||
[🦋](perl6.html) -
|
||||
[Notes](notes.html) -
|
||||
[Legal](legal.html) -
|
||||
[Wiki](https://github.com/haukex/webperl/wiki) \]
|
||||
|
||||
Welcome to WebPerl!
|
||||
===================
|
||||
|
||||
|
||||
WebPerl uses the power of [WebAssembly](https://webassembly.org/) and
|
||||
[Emscripten](http://emscripten.org/) to let you run Perl in the browser!
|
||||
|
||||
WebPerl does not translate your Perl code to JavaScript, instead, it is
|
||||
a port of the `perl` binary to WebAssembly, so that you have the full
|
||||
power of Perl at your disposal!
|
||||
|
||||
**Notice: WebPerl is very much in beta.**
|
||||
Some things may not work yet, and parts of the API may still change.
|
||||
Your feedback is always appreciated!
|
||||
|
||||
[**New: Experimental Perl 6 Support**](perl6.html)
|
||||
|
||||
```html
|
||||
<script src="webperl.js"></script>
|
||||
<script type="text/perl">
|
||||
|
||||
print "Hello, Perl World!\n"; # goes to JavaScript console by default
|
||||
|
||||
js('document')->getElementById('my_button')
|
||||
->addEventListener('click', sub {
|
||||
js('window')->alert("You clicked the button!");
|
||||
} );
|
||||
</script>
|
||||
```
|
||||
|
||||
- [**Download `webperl_prebuilt_v0.09-beta.zip`**](https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip)
|
||||
- [**Get the sources on GitHub**](https://github.com/haukex/webperl)
|
||||
|
||||
For web applications written with WebPerl, see:
|
||||
|
||||
- [**WebPerl Code Demo Editor** (beta)](democode/index.html)
|
||||
- [**WebPerl Regex Tester** (beta)](regex.html)
|
||||
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
- Prerequisites: `perl` (a recent version is recommended, e.g. v5.26 and up),
|
||||
and [`cpanm`](https://metacpan.org/pod/App::cpanminus) to easily install
|
||||
dependencies (otherwise, see the file `cpanfile` for the dependencies and
|
||||
use the module installer of your choce).
|
||||
|
||||
- In a shell:
|
||||
|
||||
$ wget https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip
|
||||
$ unzip webperl_prebuilt_v0.09-beta.zip
|
||||
$ cd webperl_prebuilt_v0.09-beta
|
||||
$ cpanm --installdeps .
|
||||
$ plackup webperl.psgi
|
||||
HTTP::Server::PSGI: Accepting connections at http://0:5000/
|
||||
|
||||
- Then point your browser at <http://localhost:5000/webperl_demo.html>
|
||||
and have a look at its source. The ZIP archive also contains several
|
||||
other examples, which you can access at <http://localhost:5000/>.
|
||||
|
||||
You may also host the contents of the above ZIP archive on a webserver of your
|
||||
choice, or some browsers will support opening the files locally; both are
|
||||
described in [Serving WebPerl](using.html#serving-webperl).
|
||||
(Note: In `webperl_demo.html`, you'll likely see "AJAX Failed!", which is to be
|
||||
expected since your webserver won't know how to handle the example AJAX request.)
|
||||
|
||||
Have fun!
|
||||
|
||||
|
||||
***
|
||||
|
||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, <http://www.igb-berlin.de>
|
||||
|
||||
Please see the ["Legal" page](legal.html) for details.
|
||||
|
||||
***
|
||||
|
||||
You can find the source for this page at
|
||||
<https://github.com/haukex/webperl/blob/gh-pages/index.md>
|
||||
|
||||
@ -1,203 +0,0 @@
|
||||
|
||||
\[ [Using](using.html) -
|
||||
[Building](building.html) -
|
||||
[🦋](perl6.html) -
|
||||
Notes -
|
||||
[Legal](legal.html) -
|
||||
[Wiki](https://github.com/haukex/webperl/wiki) \]
|
||||
|
||||
Misc. Notes on WebPerl
|
||||
======================
|
||||
|
||||
|
||||
Links
|
||||
-----
|
||||
|
||||
Other places I've written about WebPerl:
|
||||
|
||||
- [Run Perl 5 in the Browser!](https://www.perlmonks.org/?node_id=1220426)
|
||||
- [WebPerl Regex Tester (beta)](https://www.perlmonks.org/?node_id=1221705)
|
||||
- [Embedding WebPerl Code Demos into Other Pages](https://www.perlmonks.org/?node_id=1223812)
|
||||
- [WebPerl now has Experimental Perl 6 Support!](https://www.perlmonks.org/?node_id=1225647)
|
||||
|
||||
### Talks
|
||||
|
||||
- YAPC::EU 2019 (PerlCon) in Rīga - [talk](https://perlcon.eu/talk/40), [slides](https://goo.gl/QPvUb9) ([PDF](HD_WebPerl_Riga_2019_Slides.pdf)), [Video Link](https://youtu.be/bT17TCMbsdc)
|
||||
- Bonus Lightning Talk - [Video Link](https://youtu.be/tenl6JEum5k?t=29642)
|
||||
- German Perl Workshop 2019 in Munich - [talk](http://act.yapc.eu/gpw2019/talk/7616), [slides in English](https://goo.gl/yM6dff) ([PDF](HD_WebPerl_GPW_2019_Slides.pdf)), talk in German 🇩🇪, [Video Link](https://www.youtube.com/watch?v=EI8js5zf_Pw)
|
||||
- YAPC::EU 2018 in Glasgow - [lightning talk](http://act.perlconference.org/tpc-2018-glasgow/talk/7475), [Video Link](https://youtu.be/KrGSg7uVZj0?t=29520)
|
||||
|
||||
[](https://doi.org/10.5281/zenodo.1400490)
|
||||
|
||||
### "In the Press"
|
||||
|
||||
- WebPerl gets a mention in Sawyer X's talk ["Guac!"](https://www.youtube.com/watch?v=sTEshbh2lYQ) about [`Guacamole`](https://metacpan.org/pod/Guacamole) at The Perl Conference in the Cloud 2020
|
||||
- WebPerl gets another mention in Will Braswell's talk ["The Perl Family Tree: Discover Your Heritage"](https://www.youtube.com/watch?v=ZM-KHZJpy84) at The Perl Conference 2019 in Pittsburgh
|
||||
- WebPerl gets a mention in Will Braswell's talk ["Perl 11 - The Future of Saint Larry's Language"](https://fosdem.org/2019/schedule/event/perl11/) at FOSDEM '19
|
||||
- [Perl Advent Calendar 2018, Day 2](http://www.perladvent.org/2018/2018-12-02.html) (by Mark Fowler)
|
||||
- [Reddit: Your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should (2018-10-22)](https://www.reddit.com/r/programmingcirclejerk/comments/9qerw5/your_scientists_were_so_preoccupied_with_whether/) 😉
|
||||
- [Hacker News (2018-10-21)](https://news.ycombinator.com/item?id=18269071)
|
||||
- [Reddit /r/programming (2018-10-21)](https://www.reddit.com/r/programming/comments/9q65tf/run_perl_in_the_browser_with_webperl/)
|
||||
- [Facebook Group "Perl Programmers" (2018-10-20)](https://www.facebook.com/groups/perlprogrammers/permalink/2141844605848316/)
|
||||
- [Reddit /r/perl: WebPerl Regex Tester (2018-09-05)](https://www.reddit.com/r/perl/comments/9d5n77/webperl_regex_tester/)
|
||||
|
||||
### Prior Art
|
||||
|
||||
Several people have built microperl with Emscripten:
|
||||
|
||||
- Harsha <https://github.com/moodyharsh/plu>
|
||||
- Shlomi Fish <https://github.com/shlomif/perl5-for-JavaScript--take2>
|
||||
- FUJI Goro <https://github.com/gfx/perl.js>
|
||||
|
||||
|
||||
TODOs
|
||||
-----
|
||||
|
||||
1. Testing
|
||||
|
||||
- How to best disable individual tests that we know won't work? (qx etc.)
|
||||
- How to handle the many tests that call an external Perl?
|
||||
- patching t/test.pl's runperl() seems easiest at the moment, and we can use the iframe method from the IDE
|
||||
- Continue work on `WebPerl.t`
|
||||
- More tests for Unicode support (Perl/JS interface, Perl.eval(), plus Emscripten's virtual FS)
|
||||
- Focus on getting the tests running in the browser instead of node.js
|
||||
- How to best package tests?
|
||||
- If possible, a separate bundle, so that it can be loaded optionally and we don't need to rebuild
|
||||
- How does `make test` find and handle all the various modules' `t`s?
|
||||
|
||||
2. Misc
|
||||
|
||||
- Write up a full RPC example
|
||||
- `Future`
|
||||
- Would `Future::AsyncAwait` work in WebPerl? (Or maybe with a JS backend?)
|
||||
- `Future::HTTP` (probably just as a frontend to XHR?)
|
||||
- Improve Perl 6 integration
|
||||
- Build a packaged Rakudo.js ourselves instead of borrowing from <https://perl6.github.io/6pad/>
|
||||
- Perhaps get an API change or two into Rakudo.js: `STDOUT` currently gets output with HTML escapes
|
||||
- See also notes in [Perl 6 Support 🦋](perl6.html)
|
||||
- Investigate Emscripten's main loop concept for handling nonblocking sockets?
|
||||
- Use Multiplicity for re-starting perl? (Thanks to Nick Clark for the idea)
|
||||
- Add Regex::Debugger into regex tester? (Thanks to Renee for the idea)
|
||||
- Turn some patches from emperl5 into patches for P5P
|
||||
- Submit some patches to Emscripten
|
||||
- <https://github.com/kripken/emscripten/pull/7005>
|
||||
- <https://github.com/kripken/emscripten/issues/7029>
|
||||
- Would we need to patch Perl's signal functions if Emscripten's stubs weren't noisy?
|
||||
- Add `Perl.Util` functions for making file uploads and downloads easier
|
||||
- Plus an example showing how to use it to run a "legacy" Perl script with inputs and output
|
||||
- There is some potential for restructuring:
|
||||
- `Perl.glue()` and `Perl.dispatch()` could go into `WebPerl.xs` (?)
|
||||
- Parts of `webperl.js` could go into `common_preamble.js` or `WebPerl.xs`,
|
||||
so that `emperl.js` is **runnable on its own in a Web Worker (?)**
|
||||
(see notes in `perlrunner.html` / `e12f1aa25a000`)
|
||||
→ this might be interesting for running tests?!
|
||||
- `nodeperl_dev_prerun.js` could probably be merged into that as well
|
||||
- Could put a `WebPerl` stub on CPAN
|
||||
- In theory could write a `WebPerl::Local` that contains the `WebPerl` API, and
|
||||
have various drivers such as `WebPerl::WebDriver` or `WebPerl::JSAny` that
|
||||
runs the code given to WebPerl's `js()`...
|
||||
- Regarding the funky syntax highlighting on GitHub: <https://github.com/atom/language-html/issues/88#issuecomment-431361414>
|
||||
|
||||
3. See Also
|
||||
|
||||
- <https://github.com/haukex/webperl/issues>
|
||||
- <https://github.com/haukex/webperl/pulls>
|
||||
- See also `TODO`s in the source tree by grepping for `TODO`
|
||||
or using the included `findtodo.sh`.
|
||||
|
||||
|
||||
SSL
|
||||
---
|
||||
|
||||
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout selfsigned.key -out selfsigned.crt
|
||||
...
|
||||
Common Name (e.g. server FQDN or YOUR name) []: localhost
|
||||
...
|
||||
$ plackup --enable-ssl --ssl-key-file=selfsigned.key --ssl-cert-file=selfsigned.crt web/webperl.psgi
|
||||
# then go to https://localhost:5000 and accept the certificate warning
|
||||
|
||||
Possible Improvements
|
||||
---------------------
|
||||
|
||||
- More efficient JS/C/Perl glue
|
||||
- Test/Support sockets/WebSockets
|
||||
- for example, can we compile a DBD:: module to connect to a DB on the server?
|
||||
- A RPC module for communicating between client and server Perls
|
||||
- I think it's probably best to not have WebPerl prescribe a specific RPC mechanism,
|
||||
since there's a big variety and many are pretty simple to implement using e.g. jQuery
|
||||
- Support some of the Emscripten C API (like wget?)
|
||||
- Try to shrink the download size more (exclude more modules, ...?)
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
- Only works in browsers with WebAssembly support
|
||||
(asm.js requires aligned memory access, and Perl apparently has quite a few places with unaligned access)
|
||||
- 32-bit ints
|
||||
- No `system`, `qx`, `fork`, `kill`, `wait`, `waitpid`, threads, etc.
|
||||
- Theoretically, we could link in BusyBox to get a shell and utilities (??)
|
||||
- (`system` and `qx` support could theoretically be added by patching `pp_system`/`pp_backtick` in `pp_sys.c`)
|
||||
- No signals (except `SIGALRM`)
|
||||
- In the current configuration, `exit` is not supported, and therefore `atexit` handlers aren't supported
|
||||
(see discussion in [Using WebPerl](using.html), and `NO_EXIT_RUNTIME` in the Emscripten documentation -
|
||||
currently it seems to make the most sense to build with `NO_EXIT_RUNTIME=1`)
|
||||
- Static linking, requires rebuild to add modules
|
||||
(Emscripten apparently only supports asm.js dynamic linking when dynamic memory growth is disabled, which is not very useful)
|
||||
|
||||
|
||||
Release Checklist
|
||||
-----------------
|
||||
|
||||
- Update `Changes.md` with all changes since last release
|
||||
|
||||
As an example, to list changes since a specific version, excluding the regex tester:
|
||||
|
||||
$ git log --stat v0.05-beta.. -- . ':!web/regex_tester.html' ':!.gitignore'
|
||||
|
||||
- Also make sure that the documentation in `using.md` etc. mentions when features were added/deprecated
|
||||
|
||||
- Update version numbers everywhere; use `grep` to find them, for example:
|
||||
|
||||
$ grep -Er --exclude-dir=hostperl --exclude-dir=.git --exclude-dir=emperl5 --exclude=emperl.* '0\.[01][0-9]' .
|
||||
$ ( cd emperl5; grep -Er '0\.[01][0-9]' `git diff --numstat --diff-filter=ACMRT v5.28.1 HEAD | cut -f3` )
|
||||
|
||||
At a minimum there is:
|
||||
- `web/webperl.js` - `Perl.WebPerlVersion`
|
||||
- `emperl5/ext/WebPerl/lib/WebPerl.pm` - `$VERSION`
|
||||
- `pages/index.md` and `pages/using.md` - download links
|
||||
|
||||
- Update [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) values as needed, e.g.:
|
||||
|
||||
$ perl -wMstrict -MDigest::SRI=sri -le 'print sri "SHA-256","web/webperl.js"'
|
||||
|
||||
- Build and create dist, e.g. `build/build.pl --reconfig --dist=webperl_prebuilt_v0.07-beta`
|
||||
|
||||
- Test all build results, both from `file://...` and `http://localhost`
|
||||
|
||||
- Add tags, the `webperl` repo gets an annotated tag such as `v0.07-beta`,
|
||||
and the `emperl5` repo gets an unannotated tag such as `webperl_v0.07-beta`,
|
||||
then `git push --tags`
|
||||
|
||||
- Create a release on GitHub and upload the `webperl_prebuilt_*.zip` as an asset
|
||||
|
||||
- Uploading to AWS S3:
|
||||
1. `for X in emperl.* webperl.js; do gzip -v -9 $X && mv -v $X.gz $X ; done`
|
||||
2. Upload them with the appropriate `Content-Type` (see e.g. `web/webperl.psgi`) and a `Content-Encoding` of `gzip`
|
||||
|
||||
- If there was a `pages_for_vX.XX` branch of `gh-pages`, don't forget to merge that
|
||||
|
||||
|
||||
***
|
||||
|
||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, <http://www.igb-berlin.de>
|
||||
|
||||
Please see the ["Legal" page](legal.html) for details.
|
||||
|
||||
***
|
||||
|
||||
You can find the source for this page at
|
||||
<https://github.com/haukex/webperl/blob/gh-pages/notes.md>
|
||||
|
||||
@ -1,139 +0,0 @@
|
||||
|
||||
\[ [Using](using.html) -
|
||||
[Building](building.html) -
|
||||
🦋 -
|
||||
[Notes](notes.html) -
|
||||
[Legal](legal.html) -
|
||||
[Wiki](https://github.com/haukex/webperl/wiki/Perl6) \]
|
||||
|
||||
WebPerl Experimental Perl 6 Support 🦋
|
||||
=====================================
|
||||
|
||||
|
||||
Thanks to [**Paweł Murias**](https://github.com/pmurias) and his
|
||||
amazing work on **`Rakudo.js`** and
|
||||
[**6Pad**](https://perl6.github.io/6pad/), I've been able to patch
|
||||
support for Perl 6 into WebPerl!
|
||||
|
||||
Currently **requires Google Chrome** (due to BigInt support), see the
|
||||
[Quick Start](#quick-start) below on how to get this up and running on
|
||||
your local machine!
|
||||
|
||||
```html
|
||||
<script src="webperl.js"></script>
|
||||
<script type="text/perl6">
|
||||
|
||||
say "Hello, Perl 6 World!"; # goes to JavaScript console by default
|
||||
|
||||
my $document = EVAL(:lang<JavaScript>, 'return document');
|
||||
my $window = EVAL(:lang<JavaScript>, 'return window');
|
||||
$document.getElementById('my_button')
|
||||
.addEventListener("click", -> $event {
|
||||
$window.alert("You clicked the button!");
|
||||
} );
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
- Prerequisites: `perl` (a recent version is recommended, e.g. v5.26 and up),
|
||||
and [`cpanm`](https://metacpan.org/pod/App::cpanminus) to easily install
|
||||
dependencies (otherwise, see the files `cpanfile` for the dependencies and
|
||||
use the module installer of your choce).
|
||||
|
||||
- In a shell (the following assumes Linux):
|
||||
|
||||
$ git clone https://github.com/haukex/webperl.git
|
||||
$ cd webperl
|
||||
$ wget https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip
|
||||
$ unzip -j webperl_prebuilt_v0.09-beta.zip '*/emperl.*' -d web
|
||||
$ cpanm --installdeps .
|
||||
$ cd experiments ; cpanm --installdeps . ; cd ..
|
||||
$ experiments/p6/6init.pl # this patches Perl 6 support in
|
||||
$ plackup web/webperl.psgi
|
||||
|
||||
- Then point your Chrome browser at <http://localhost:5000/6demo.html>,
|
||||
and have a look at its source.
|
||||
|
||||
Have fun!
|
||||
|
||||
|
||||
Experimental Status and Notes
|
||||
-----------------------------
|
||||
|
||||
- I don't have enough experience with `Rakudo.js`
|
||||
- <https://github.com/rakudo/rakudo/tree/master/src/vm/js>
|
||||
- <https://perl6.github.io/6pad/>
|
||||
- <http://blogs.perl.org/users/pawel_murias/>
|
||||
- <https://github.com/perl6/perl6-parcel-example>
|
||||
- <https://www.youtube.com/watch?v=LN0mKjmraVs>
|
||||
|
||||
- requires BigInt support, which is currently only available in Chrome
|
||||
- <https://developers.google.com/web/updates/2018/05/bigint>
|
||||
- <https://github.com/tc39/proposal-bigint>
|
||||
- <https://v8.dev/blog/bigint>
|
||||
|
||||
- Large download (10MB compressed, 74MB uncompressed) - can we
|
||||
repackage it to make it smaller, or is there a good way to
|
||||
distribute this?
|
||||
|
||||
- STDERR only goes to console, STDOUT gets output with HTML escapes
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
My code steal^H^H^H^H^Hborrows the prepackaged `Rakudo.js` build from
|
||||
[6Pad](https://perl6.github.io/6pad/) and caches it locally. The script
|
||||
`experiments/p6/6init.pl` also patches the experimental P6 support into
|
||||
`webperl.js` (see the [Quick Start](#quick-start) above).
|
||||
|
||||
Note that both Perl 5 and Perl 6 are only loaded on demand by
|
||||
`webperl.js`, so if you only use one or the other, you won't have the
|
||||
overhead of loading both.
|
||||
|
||||
For now, I've basically just patched `Rakudo.js`'s `evalP6()` into
|
||||
`Raku.eval()`, and `NQP_STDOUT` into `Raku.output`, to make things more
|
||||
like the Perl 5 WebPerl, and provided some of the same API for Perl 6
|
||||
as I provide for Perl 5.
|
||||
|
||||
The JS API provided by WebPerl for Perl 6 currently closely mirrors
|
||||
[the Perl 5 API](using.html#webperljs): There is a JS object `Raku`
|
||||
which provides the following functions / properties that do mostly the
|
||||
same as for Perl 5:
|
||||
|
||||
- `Raku.addStateChangeListener( function (from,to) {} )`
|
||||
- `Raku.state`
|
||||
- `Raku.output = function (str,chan) {}`
|
||||
- `Raku.makeOutputTextarea()`
|
||||
- `Raku.init( function () {} )`
|
||||
- `Raku.eval( code )`
|
||||
|
||||
You can add Perl 6 code to your HTML pages with `<script>` tags
|
||||
with `type="text/perl6"` or `type="text/raku"`.
|
||||
|
||||
For everything else, I defer to `Rakudo.js` for now! I will update this
|
||||
documentation as things evolve.
|
||||
|
||||
|
||||
***
|
||||
|
||||
Additional notes on WebPerl's experimental Perl 6 support may be found
|
||||
in the [GitHub Wiki](https://github.com/haukex/webperl/wiki/Perl6).
|
||||
|
||||
***
|
||||
|
||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, <http://www.igb-berlin.de>
|
||||
|
||||
Please see the ["Legal" page](legal.html) for details.
|
||||
|
||||
***
|
||||
|
||||
You can find the source for this page at
|
||||
<https://github.com/haukex/webperl/blob/gh-pages/perl6.md>
|
||||
|
||||
|
||||
@ -1,655 +0,0 @@
|
||||
|
||||
\[ Using -
|
||||
[Building](building.html) -
|
||||
[🦋](perl6.html) -
|
||||
[Notes](notes.html) -
|
||||
[Legal](legal.html) -
|
||||
[Wiki](https://github.com/haukex/webperl/wiki/Using-WebPerl) \]
|
||||
|
||||
Using WebPerl
|
||||
=============
|
||||
|
||||
|
||||
**Notice: WebPerl is very much in beta.**
|
||||
Some things may not work yet, and parts of the API may still change.
|
||||
Your feedback is always appreciated!
|
||||
|
||||
This page documents the Perl 5 support, for the *experimental*
|
||||
Perl 6 support, see [here](perl6.html).
|
||||
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Serving WebPerl](#serving-webperl)
|
||||
- [Including Perl code in your HTML](#including-perl-code-in-your-html)
|
||||
- [The Perl Interpreter and its Environment](#the-perl-interpreter-and-its-environment)
|
||||
- [Memory Management and Anonymous `sub`s](#memory-management-and-anonymous-subs)
|
||||
- [Virtual Filesystem](#virtual-filesystem)
|
||||
- [webperl.js](#webperljs)
|
||||
- The JS `Perl` object
|
||||
- [WebPerl.pm](#webperlpm)
|
||||
- Perl <-> JavaScript mappings
|
||||
- [The Mini IDE](#the-mini-ide)
|
||||
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
### Getting WebPerl
|
||||
|
||||
If you plan on building WebPerl, for example if you'd like to add more CPAN
|
||||
modules, then head on over to [Building WebPerl](building.html). Otherwise, if
|
||||
you'd just like to get started quickly and work with the prebuilt WebPerl
|
||||
(includes many of the Perl core modules plus a couple extras), then download
|
||||
[`webperl_prebuilt_v0.09-beta.zip`](https://github.com/haukex/webperl/releases/download/v0.09-beta/webperl_prebuilt_v0.09-beta.zip)
|
||||
and unpack it. This ZIP file includes the contents of the
|
||||
[`web`](https://github.com/haukex/webperl/tree/master/web) directory of the
|
||||
source code, as well as the build products `emperl.*` (currently three files).
|
||||
If you'd like to work with the source code as checked out from GitHub, then you
|
||||
can copy these `emperl.*` files into the `web` directory of the source tree.
|
||||
|
||||
### Serving WebPerl
|
||||
|
||||
You should serve WebPerl via a webserver of your choice, or you can
|
||||
use the included simple `webperl.psgi` for testing. You can run it using
|
||||
[`plackup` from Plack](https://metacpan.org/pod/distribution/Plack/script/plackup)
|
||||
by simply saying `plackup webperl.psgi`.
|
||||
|
||||
The following four files make up WebPerl:
|
||||
|
||||
- `webperl.js` - Contains the WebPerl JavaScript API and supporting code.
|
||||
- `emperl.js` - Emscripten-generated supporting JavaScript.
|
||||
- `emperl.wasm` - The `perl` binary and libraries compiled to WebAssembly.
|
||||
- `emperl.data` - The Emscripten virtual file system data (`.pm` files etc.).
|
||||
|
||||
I strongly recommend you add a MIME type of `application/wasm` for `.wasm` files,
|
||||
otherwise you may see warnings like
|
||||
"wasm streaming compile failed: TypeError: Response has unsupported MIME type" and
|
||||
"falling back to ArrayBuffer instantiation".
|
||||
For example, in an Apache `.htaccess` file, you can say: `AddType application/wasm .wasm`
|
||||
|
||||
Note that opening the files locally (via `file://`) may not work
|
||||
due to browsers' Same-Origin Policy. However, there are some workarounds:
|
||||
|
||||
* On Linux, the "wasm streaming compile failed: TypeError: Response has unsupported MIME type /
|
||||
falling back to ArrayBuffer instantiation" warnings can be worked around by
|
||||
adding the line `application/wasm wasm` to `~/.mime.types` or `/etc/mime.types`
|
||||
* In Firefox, if your files reside in different directories, the same-origin policy can be
|
||||
made more lax for `file://` URIs by disabling the
|
||||
[security.fileuri.strict_origin_policy](http://kb.mozillazine.org/Security.fileuri.strict_origin_policy)
|
||||
option. **But be aware** of the security implications of disabling this option!
|
||||
|
||||
See also the Emscripten deployment notes at
|
||||
<http://kripken.github.io/emscripten-site/docs/compiling/Deploying-Pages.html>,
|
||||
in particular I'd recommended using gzip encoding to serve the WebPerl files.
|
||||
|
||||
### Including Perl code in your HTML
|
||||
|
||||
In your HTML file, add the following (usually inside the `<head>` tags):
|
||||
|
||||
<script src="webperl.js"></script>
|
||||
|
||||
Then, you can add one or more `<script type="text/perl">` tags containing embedded Perl code,
|
||||
or a single `<script type="text/perl" src="foo.pl"></script>` tag which loads a
|
||||
Perl script from the server - but not both! The code from multiple
|
||||
`<script type="text/perl">` tags will be concatenated and run as a single script.
|
||||
|
||||
If you use embedded `<script type="text/perl">` tags, then the function `js` from
|
||||
`WebPerl.pm` will be imported automatically. If you want to customize the import
|
||||
list, then add `use WebPerl ...;` as one of the first five lines of your Perl code
|
||||
(to be exact, WebPerl will look for `/^\s*use\s+WebPerl(\s|;)/m`).
|
||||
|
||||
If you don't have any such script tags in the document, Perl won't be run
|
||||
automatically, and you can control Perl in detail via the JavaScript `Perl`
|
||||
object provided by [webperl.js](#webperljs).
|
||||
|
||||
Note that unlike JavaScript, which is run immediately, WebPerl will always be loaded
|
||||
and run asynchronously from the page load. If you use `<script type="text/perl">` tags,
|
||||
these will always be run after the document is ready, and if you use the `Perl` object
|
||||
as described below, you will have control over when Perl is initialized and run, but
|
||||
it will still be asynchronous because files need to be fetched from the server.
|
||||
|
||||
|
||||
The Perl Interpreter and its Environment
|
||||
----------------------------------------
|
||||
|
||||
The `perl` compiled for WebPerl is mostly a standard build of Perl, except
|
||||
for a few patches to make things compile properly, and the major differences
|
||||
described here.
|
||||
|
||||
[Emscripten](http://emscripten.org/) provides emulation for a number of system
|
||||
calls, most notably for WebPerl, it provides a
|
||||
[**virtual filesystem** (details below)](#virtual-filesystem) from which Perl can
|
||||
load its modules, since of course JavaScript in the browser is a sandboxed
|
||||
environment (no access to hardware, the local filesystem, etc.).
|
||||
However, because Perl is the *only* Emscripten process running in the browser,
|
||||
there are **several things that won't work** either because Emscripten doesn't
|
||||
support them (yet) or because they are simply not possible in this
|
||||
single-process environment:
|
||||
|
||||
- Running other programs via e.g. `system`, backticks (`qx`), piped `open`, etc.
|
||||
- No `fork` or multithreading, no `kill`, `wait`, `waitpid`, etc.
|
||||
- There is experimental support for pthreads, but this is not tested with WebPerl yet.
|
||||
See also <http://kripken.github.io/emscripten-site/docs/porting/pthreads.html>.
|
||||
- No signals (except `SIGALRM`)
|
||||
|
||||
Like many UI frameworks, scripting in the browser is usually **asynchronous and event-driven**.
|
||||
In addition, in Emscripten it is currently not easy to run a program multiple times.
|
||||
In order to better support these circumstances, WebPerl's C `main()` function has been
|
||||
patched to *not* end the runtime. This means that once the main Perl script is run,
|
||||
the interpreter is *not* shut down, meaning `END` blocks and global destruction are not run,
|
||||
and instead control is passed back to the browser.
|
||||
|
||||
This way, you can write Perl in an event-driven manner: in your main code, you can register
|
||||
callbacks as event handlers for events such as button clicks, network communication, etc., and
|
||||
then control is passed back to the browser. When the events occur, your Perl callbacks will be run.
|
||||
|
||||
In order to allow for this mode of execution, WebPerl is built with Emscripten's
|
||||
`NO_EXIT_RUNTIME` option enabled. When this option is enabled, `atexit` handlers are
|
||||
not supported, and calls to `exit` will result in a warning. For this reason, WebPerl
|
||||
is patched to not call `exit` when the exit code is zero. As a result of all this,
|
||||
in your scripts, I strongly recommend you **don't use Perl's `exit;`/`exit(0);`**,
|
||||
as it will not likely do what you want.
|
||||
|
||||
Remember that in the browser, the user may leave a page at any time, and there is little
|
||||
a script can do to prevent this. Although it's possible to ask Perl to end early as follows,
|
||||
I would still recommend that you **don't rely on `END` blocks or global destruction**.
|
||||
If your program is doing things like saving files (e.g. via an asynchronous network request),
|
||||
then you should provide some kind of feedback to your user to know that a process is still
|
||||
going on, and possibly install your own "beforeunload" handler.
|
||||
|
||||
WebPerl includes a C function `int emperl_end_perl()` which will perform the normal
|
||||
Perl interpreter shutdown (but as mentioned above, not call `exit` if the exit code is zero).
|
||||
This function is accessible in several ways:
|
||||
|
||||
- From JavaScript, set `Perl.endAfterMain` before calling `Perl.init()`
|
||||
(this enables a "hack" that calls `emperl_end_perl()` after `main()` returns)
|
||||
- From JavaScript, call `Perl.end()`
|
||||
- From Perl, call `WebPerl::end_perl()`
|
||||
|
||||
These options might be useful if you're porting an existing script to run in WebPerl.
|
||||
|
||||
(In addition, WebPerl currently registers an "beforeunload" handler that attempts to call
|
||||
the "end" function, but since this will be happening as the page is being unloaded,
|
||||
do *not* rely on this being able to do very much, or even being called at all!)
|
||||
|
||||
### Memory Management and Anonymous `sub`s
|
||||
|
||||
**Anonymous `sub`s passed from Perl to JavaScript must be explicitly freed**
|
||||
**when you are done using them, or else this is a memory leak.**
|
||||
Please read this section!
|
||||
|
||||
When JavaScript arrays, objects, and functions are passed to Perl, they are not
|
||||
copied, instead they are given an ID and placed in a table so that when Perl
|
||||
wants to access them, it only needs to remember the ID, and pass the ID and the
|
||||
corresponding operation to JavaScript. In JavaScript, these objects are kept alive
|
||||
because of the entry in the table. Once the object goes out of scope in Perl,
|
||||
its `DESTROY` method lets JavaScript know that it can free that entry from the
|
||||
table, so JavaScript is free to garbage collect it if there are no other references.
|
||||
|
||||
When Perl values are passed to JavaScript, they are generally copied, except
|
||||
for anonymous `sub`s, where a mechanism similar to the above is used, and a reference
|
||||
to the `sub`s is kept alive using a table in Perl. *However,* JavaScript has
|
||||
no equivalent of the `DESTROY` method, which means that even if you are done
|
||||
using a `sub` in JavaScript, Perl will not know when it can free the table
|
||||
entry, unless you explicitly tell it to!
|
||||
|
||||
WebPerl provides two mechanisms for freeing `sub`s:
|
||||
|
||||
- `WebPerl::unregister()` (can be exported), which takes a single argument that is
|
||||
a reference to an anonymous sub previously passed to JavaScript. If you
|
||||
`use 5.028;` or `use feature 'current_sub';`, anonymous `sub`s can refer to
|
||||
themselves using the special `__SUB__` identifier, so for example, you can say:
|
||||
|
||||
use 5.028;
|
||||
js( sub {
|
||||
print "I was called, now I am going away\n";
|
||||
WebPerl::unregister(__SUB__);
|
||||
} )->();
|
||||
|
||||
- `WebPerl::sub_once` aka `WebPerl::sub1` are wrappers for `sub`s that essentially
|
||||
call the `sub` once and then immediately `unregister` it. The above example can be
|
||||
written as:
|
||||
|
||||
use WebPerl qw/js sub1/;
|
||||
js( sub1 { print "Single-use sub called\n"; } )->();
|
||||
|
||||
`unregister` is still useful for anonymous `sub`s that need to be called multiple
|
||||
times before falling out of use.
|
||||
|
||||
Of course, it is often the case that anonymous `sub`s need to persist for the
|
||||
entire run of a program (like for example click handlers for buttons), or that
|
||||
you may only have a handful of anonymous `sub`s in your program overall.
|
||||
In such cases, you probably don't need to `unregister` them. However, there are
|
||||
cases where this is very important to keep in mind - for example anonymous
|
||||
`sub`s generated via stringy `eval`s.
|
||||
|
||||
If you want to check how many anonymous `sub`s are registered, you can say
|
||||
`print scalar(keys %WebPerl::CodeTable);` (*do not* modify this hash).
|
||||
|
||||
Note that the above only applies to *anonymous* `sub`s. `sub`s that exist
|
||||
in Perl's symbol table will persist in Perl's memory anyway, and no table entry
|
||||
is generated for them, because it is assumed you won't delete them from the
|
||||
symbol table - so please don't do that. Also, don't rename or redefine `sub`s
|
||||
after having passed them to JavaScript, as that will probably cause mysterious behavior.
|
||||
|
||||
### Virtual Filesystem
|
||||
|
||||
Emscripten provides a virtual file system that also provides a few "fake" files such
|
||||
as `/home/web_user`, `/dev`, and others, so that it resembles a normal *NIX file system.
|
||||
This filesystem resides entirely in memory in the browser.
|
||||
|
||||
Perl's libraries (`*.pm`) are installed into this virtual file system at `/opt/perl`.
|
||||
Note that because the `perl` binary is compiled to WebAssembly and XS libraries are
|
||||
statically linked into it, you won't find any `perl` binary or shared library files in the
|
||||
virtual file system, or for that matter any other binaries, since this is a
|
||||
single-process environment.
|
||||
|
||||
It is important to keep apart the different ways to access files:
|
||||
|
||||
- There's your local file system and the web server's file system, to which WebPerl does *not* have direct access.
|
||||
There are only a few ways to get files from a local file system into WebPerl:
|
||||
- The user can upload files from their local system using a file upload control and JavaScript.
|
||||
The "mini IDE" included with WebPerl includes a demo of this, see
|
||||
`file_upload` and `upload_file` in
|
||||
[`emscr_ide.js`](https://github.com/haukex/webperl/blob/master/web/mini_ide/emscr_ide.js).
|
||||
(Also, `file_download` shows a way to send files to the user's browser.)
|
||||
- An RPC service that you set up on your webserver would allow WebPerl to communicate
|
||||
with the server, and the service can provide access to the files on the server.
|
||||
For one example, see
|
||||
[`webperl.psgi`](https://github.com/haukex/webperl/blob/master/web/webperl.psgi)
|
||||
for the server side and
|
||||
[`webperl_demo.html`](https://github.com/haukex/webperl/blob/master/web/webperl_demo.html)
|
||||
for the client side.
|
||||
- For more permanent additions to the virtual filesystem, you could compile files
|
||||
into Emscripten's virtual file system (mentioned below).
|
||||
|
||||
- All that WebPerl can see directly is the virtual file system provided by Emscripten.
|
||||
More details are below.
|
||||
|
||||
- When you use the JavaScript `Perl` object provided by `webperl.js` to control Perl,
|
||||
the `argv` you supply to `Perl.start` references files in the virtual file system.
|
||||
|
||||
- A `<script type="text/perl" src="foo.pl"></script>` tag will cause `webperl.js`
|
||||
to fetch `foo.pl` from the *web server*, not the virtual filesystem!
|
||||
- *However*, that Perl script will *also* only see the virtual filesystem,
|
||||
not the web server, so it won't even be able to see "itself" (`webperl.js`
|
||||
may save files such as `/tmp/scripts.pl`, but that's *not* guaranteed).
|
||||
You can still fetch things from the webserver using e.g. AJAX requests.
|
||||
|
||||
While a WebPerl instance is running, you can modify files in the virtual file system
|
||||
as you might be used to from regular Perl. But the virtual filesystem is reloaded every
|
||||
time WebPerl is reloaded, so any changes are lost! The exception is the "`IDBFS`"
|
||||
provided by Emscripten, which stores files in an `IndexedDB`, so they typically persist
|
||||
in the browser's storage across sessions. WebPerl mounts an instance of this filesystem
|
||||
at `/mnt/idb`, which you are free to use. If you want your Perl script to write to files
|
||||
there, you **must** also use Emscripten's `FS.syncfs()` interface after writing files,
|
||||
for example:
|
||||
|
||||
js(q/ FS.syncfs(false, function (err) {
|
||||
if(err) alert("FS sync failed: "+err);
|
||||
else console.log("FS sync ok"); }); /);
|
||||
|
||||
Remember that users may clear this storage at any time, so it is not really a permanent storage
|
||||
either. If you need to safely store files, it's best to store them on the user's machine
|
||||
(or the web server, if they are different machines) using one of the methods described above.
|
||||
|
||||
In particular, even though you might make heavy use of `/mnt/idb` when testing with the "mini IDE",
|
||||
remember that this storage is *not* a way to distribute files to your users, and in fact, some
|
||||
users' browsers may automatically regularly clear the `IndexedDB`, or have it disabled altogether.
|
||||
It also may not work at all in a "sandboxed" `iframe`.
|
||||
For providing your application to your users, either use `<script type="text/perl">` tags,
|
||||
compile the script into the virtual file system, or use the JavaScript `Perl` object.
|
||||
|
||||
You might also put files into the virtual filesystem more permanently by modifying the "make install"
|
||||
step of [`build.pl`](https://github.com/haukex/webperl/blob/master/build/build.pl).
|
||||
Keep in mind that like the other files in the virtual file system,
|
||||
any modifications will be lost once WebPerl is reloaded, and the only way to modify them
|
||||
is re-running `build.pl`.
|
||||
|
||||
Additional information on the virtual file system may be found at:
|
||||
|
||||
- <http://kripken.github.io/emscripten-site/docs/porting/files/file_systems_overview.html>
|
||||
- <http://kripken.github.io/emscripten-site/docs/api_reference/Filesystem-API.html>
|
||||
- <http://kripken.github.io/emscripten-site/docs/api_reference/advanced-apis.html#advanced-file-system-api>
|
||||
|
||||
Note that WebPerl's build process strips any POD from the Perl libraries, to reduce download size.
|
||||
|
||||
By the way, I don't recommend relying on the initial working directory when WebPerl
|
||||
starts; either `chdir` to a known location, or always use absolute filenames.
|
||||
|
||||
|
||||
webperl.js
|
||||
----------
|
||||
|
||||
`webperl.js` provides a JavaScript object `Perl` that can be used to control
|
||||
the Perl interpreter. Many properties of this object are intended for internal
|
||||
use by WebPerl only, so please **only use the interface documented here**.
|
||||
|
||||
### Controlling Perl
|
||||
|
||||
As documented above, if your HTML file contains `<script type="text/perl">`
|
||||
tags, these will be run automatically, so you should *not* use `Perl.init()`
|
||||
and `Perl.start()` in this case.
|
||||
|
||||
#### `Perl.init(function)`
|
||||
|
||||
Initializes the Perl interpreter (asynchronously fetches the `emperl.*` files).
|
||||
You should pass this function a callback function, which is to be called when
|
||||
Perl is ready to be run - normally you would call `Perl.start()` from this callback.
|
||||
|
||||
#### `Perl.start(argv)`
|
||||
|
||||
Runs Perl with the given `argv` array. If `argv` is not provided, uses Emscripten's
|
||||
`Module.arguments`, which currently defaults to `['--version']`.
|
||||
|
||||
#### `Perl.eval(code)`
|
||||
|
||||
Evaluates the given Perl code. Currently always returns a string.
|
||||
|
||||
The functionality of this function *may* be expanded upon in the future
|
||||
to return more than just a string. See the discussion in
|
||||
[Mappings from Perl to JavaScript](#mappings-from-perl-to-javascript).
|
||||
|
||||
#### `Perl.end()`
|
||||
|
||||
Ends the Perl interpreter. See the discussion under
|
||||
["The Perl Interpreter and its Environment"](#the-perl-interpreter-and-its-environment)
|
||||
for details.
|
||||
|
||||
### Options
|
||||
|
||||
#### `Perl.output`
|
||||
|
||||
Set this to a `function (str,chan) {...}` to handle Perl writing to `STDOUT` or `STDERR`.
|
||||
`str` is the string to be written, which may consist of a single character, a whole
|
||||
line, or multiple lines. `chan` will be either 1 for `STDOUT` or 2 for `STDERR`.
|
||||
If you want to merge the two streams, you can simply ignore the `chan` argument.
|
||||
Defaults to an implementation that line-buffers and logs via `console.log()`,
|
||||
prefixing either `STDOUT` or `STDERR` depending on the channel.
|
||||
See also `Perl.makeOutputTextarea`, which installs a different output handler.
|
||||
|
||||
#### `Perl.endAfterMain`
|
||||
|
||||
If set to `true` before calling `Perl.init()`, then WebPerl will automatically
|
||||
end the Perl interpreter after it finishes running the main script. See the
|
||||
discussion under
|
||||
["The Perl Interpreter and its Environment"](#the-perl-interpreter-and-its-environment).
|
||||
Defaults to `false`.
|
||||
|
||||
#### `Perl.noMountIdbfs`
|
||||
|
||||
If set to `true` before calling `Perl.start()`, then WebPerl will not automatically
|
||||
mount the IDBFS filesystem (see ["Virtual File System"](#virtual-file-system).
|
||||
Defaults to `false`.
|
||||
|
||||
This option was added in `v0.05-beta`.
|
||||
|
||||
#### `Perl.trace`
|
||||
|
||||
Enable this option at any time to get additional trace-level output
|
||||
to `console.debug()`. Defaults to `false`.
|
||||
|
||||
#### `Perl.addStateChangeListener(function)`
|
||||
|
||||
Pass this function a `function (from,to) {...}` to register a new handler
|
||||
for state changes of the Perl interpreter.
|
||||
|
||||
The states currently are:
|
||||
|
||||
- `"Uninitialized"` - `Perl.init` has not been called yet.
|
||||
- `"Initializing"` - `Perl.init` is currently operating.
|
||||
- `"Ready"` - `Perl.init` is finished and `Perl.start` can be called.
|
||||
- `"Running"` - The Perl interpreter is running, `Perl.eval` and `Perl.end` may be called
|
||||
- `"Ended"` - The Perl interpreter has ended.
|
||||
~~You might receive several state change notifications for this state.~~
|
||||
This is no longer the case as of WebPerl `v0.09-beta`:
|
||||
you should only receive one event per state change.
|
||||
|
||||
This function was added in WebPerl `v0.05-beta`.
|
||||
|
||||
#### `Perl.exitStatus`
|
||||
|
||||
This property should be **read only**! After Perl's state has changed
|
||||
to `Ended`, you can retrieve the exit code here.
|
||||
|
||||
This property was added in WebPerl `v0.09-beta`.
|
||||
|
||||
#### `Perl.stateChanged`
|
||||
|
||||
**Deprecated** in WebPerl `v0.05-beta`. Use `Perl.addStateChangeListener` instead.
|
||||
|
||||
Set this to a `function (from,to) {...}` to handle state changes of the Perl interpreter.
|
||||
Defaults to a simple implementation that logs via `console.debug()`.
|
||||
|
||||
### Utility Functions
|
||||
|
||||
#### `Perl.makeOutputTextarea(id)`
|
||||
|
||||
This function will create a new DOM `<textarea>` element, set up a
|
||||
`Perl.output` handler that redirects Perl's output (merged STDOUT and STDERR)
|
||||
into the `<textarea>`, and return the DOM element. You may optionally pass this
|
||||
function a string argument giving a DOM ID. You will need to add the
|
||||
`<textarea>` to your DOM yourself (see `webperl_demo.html` for an example).
|
||||
|
||||
|
||||
WebPerl.pm
|
||||
----------
|
||||
|
||||
`WebPerl.pm` provides the Perl side of the WebPerl API.
|
||||
Its central function is `js()`, documented below.
|
||||
It also provides the functions `unregister`, `sub_once`, and `sub1`
|
||||
(the latter two are aliases for each other), which are documented
|
||||
in ["Memory Management and Anonymous `sub`s"](#memory-management-and-anonymous-subs).
|
||||
For convenience, it can also re-export `encode_json`, so you can
|
||||
request it directly from `WebPerl` instead of needing to `use` another module.
|
||||
Additional functions, like `js_new()`, are documented below.
|
||||
All functions are exported only on request.
|
||||
|
||||
Note that WebPerl will also enable autoflush for `STDOUT`.
|
||||
|
||||
### `js()`
|
||||
|
||||
This function takes a single string argument consisting of JavaScript code to
|
||||
run, uses JavaScript's `eval` to run it, and returns the result, as follows.
|
||||
|
||||
You may also pass an arrayref, hashref, or coderef, and this data structure
|
||||
will be passed to JavaScript, and a corresponding `WebPerl::JSObject` returned.
|
||||
Other references, including objects, are currently not supported.
|
||||
|
||||
### Mappings from JavaScript to Perl
|
||||
|
||||
If the code given to `js()` throws a JavaScript error, `js()` will `die`.
|
||||
Otherwise, the `js()` function will return:
|
||||
|
||||
- JS `undefined` becomes Perl `undef`
|
||||
- JS booleans become Perl's "booleans" (`!0` and `!1`)
|
||||
- JS numbers and strings become Perl numbers and strings (values are copied)
|
||||
- JS "Symbol"s currently cause a warning and and `js()` returns `undef`
|
||||
- JS functions, objects (hashes), and arrays are returned as
|
||||
Perl objects of the class `WebPerl::JSObject`.
|
||||
|
||||
### `WebPerl::JSObject`
|
||||
|
||||
A `WebPerl::JSObject` is a thin wrapper around a JavaScript object.
|
||||
The contents of the JavaScript object are not copied to Perl, they are kept in
|
||||
JavaScript and accessed only when requested from Perl.
|
||||
|
||||
`JSObject`s support overload array, hash, and code dereferencing, plus
|
||||
autoloaded method calls. This means that if you have a `WebPerl::JSObject`
|
||||
stored in a Perl scalar `$foo` pointing to a JavaScript object `foo`:
|
||||
|
||||
- Perl `$foo->{bar}` is the equivalent of JavaScript `foo["bar"]`
|
||||
- Perl `$foo->[42]` is the equivalent of JavaScript `foo[42]`
|
||||
- Perl `$foo->("arg")` is the equivalent of JavaScript `foo("arg")`
|
||||
- Perl `$foo->bar("arg")` is the equivalent of JavaScript `foo.bar("arg")`
|
||||
|
||||
`JSObject`s provide the following methods:
|
||||
|
||||
- `hashref` is the method behind hashref overloading. It returns a reference
|
||||
to a tied hash which accesses the underlying JavaScript object. The tied
|
||||
hash should behave like a normal Perl hash, except that all operations
|
||||
on it are passed to JavaScript.
|
||||
- `arrayref` is the method behind arrayref overloading. It returns a reference
|
||||
to a tied array which accesses the underlying JavaScript array. The tied
|
||||
array should behave like a normal Perl array, except that all operations
|
||||
on it are passed to JavaScript.
|
||||
- `coderef` is the method behind coderef overloading. It returns a reference
|
||||
to a `sub` that, when called, calls the underlying JavaScript function.
|
||||
- `methodcall` is the method behind method autoloading. Its first argument is
|
||||
the name of the method, and the further arguments are arguments to the method.
|
||||
- `toperl` is a method that translates the object from a `JSObject` into a
|
||||
regular Perl data structure (deep copy). Note that JavaScript functions are
|
||||
kept wrapped inside anonymous Perl `sub`s.
|
||||
- `jscode` returns a string of JavaScript code that represents a reference
|
||||
to the JavaScript object. **Warning:** Treat this value as read-only in
|
||||
JavaScript! *Do not* assign to this value, call JavaScript's `delete` on
|
||||
this value, etc. (calling methods that may mutate the object is ok, though).
|
||||
You should treat the string as an opaque value, no guarantees are made about
|
||||
its format and whether it may change in future releases.
|
||||
This is an advanced function that should not normally be needed,
|
||||
unless you are building strings of JavaScript to run. In that case, you
|
||||
may need to wrap the value in parentheses for it to evaluate correctly in
|
||||
JavaScript. Example: `js( "console.log(".$jsobject->jscode.")" )`
|
||||
(`jscode` was added in WebPerl `v0.07-beta`.)
|
||||
|
||||
Method autoloading will of course not work for JavaScript methods that have
|
||||
the same name as existing Perl methods - these are the above methods,
|
||||
plus methods named `AUTOLOAD`, `DESTROY`, plus any methods inherited from Perl's
|
||||
[`UNIVERSAL`](http://perldoc.perl.org/UNIVERSAL.html) class, such as `can` or `isa`.
|
||||
If you need to call JavaScript methods with any of these names,
|
||||
use `methodcall`. For example, `$jsobject->methodcall("can", "arg1")` will call
|
||||
the JavaScript method `can` instead of the Perl method `can`.
|
||||
|
||||
Arguments from Perl to JavaScript function or method calls are mapped as follows.
|
||||
|
||||
### Mappings from Perl to JavaScript
|
||||
|
||||
Unlike the JavaScript to Perl mappings, values are (currently¹) generally *copied* from
|
||||
Perl to JavaScript, instead of being *referenced*.
|
||||
The exceptions are Perl `sub`s and `WebPerl::JSObject`s.
|
||||
|
||||
- Perl arrayrefs become JavaScript arrays (deep copy)
|
||||
- Perl hashrefs become JavaScript objects (deep copy)
|
||||
- Perl coderefs become JavaScript functions² -
|
||||
**Warning:** please see the discussion in
|
||||
["Memory Management and Anonymous `sub`s"](#memory-management-and-anonymous-subs)!
|
||||
- Perl `WebPerl::JSObject`s become references to the wrapped JavaScript objects
|
||||
(i.e. the underlying JS object is passed back to JS transparently)
|
||||
- Perl numbers/strings are copied to JavaScript via `Cpanel::JSON::XS::encode_json`
|
||||
(with its `allow_nonref` option enabled). This means that the choice
|
||||
for whether to encode a Perl scalar as a JavaScript number or string is
|
||||
left up to the module, and is subject to the usual ambiguities when
|
||||
serializing Perl scalars. See
|
||||
[the `Cpanel::JSON::XS` documentation](https://metacpan.org/pod/Cpanel::JSON::XS).
|
||||
- Other references, including Perl objects, are currently not supported.
|
||||
|
||||
¹ So far, the focus of WebPerl has been to replace JavaScript with Perl, and
|
||||
therefore on accessing JavaScript from Perl, and not as much the other
|
||||
way around, that is, doing complex things with Perl from JavaScript code.
|
||||
For example, currently, `Perl.eval()` always returns a string, but could in the
|
||||
future be extended to return more than that, similar to `WebPerl::js()`,
|
||||
and then the passing of Perl values to JavaScript could be accomplished
|
||||
differently as well.
|
||||
|
||||
² **Remember** that Perl `sub`s without an explicit `return` statement will
|
||||
implicitly return the value of the last statement (if it is an expression).
|
||||
This value will in turn be passed to JavaScript, which may be inefficient
|
||||
if this value is not needed, and it may cause errors if the return value cannot
|
||||
be encoded to JavaScript. Therefore it is recommended to get into the
|
||||
habit of adding an explicit `return;` at the end of `sub`s passed to JS.
|
||||
|
||||
### `js_new()`
|
||||
|
||||
This function is a convenience function for calling JavaScript's `new`.
|
||||
The first argument is the name of the class, the following arguments are
|
||||
passed to the constructor. It returns the same thing as the `js()` function,
|
||||
in this case that would be the new object. For example:
|
||||
|
||||
js_new('Blob', ["<html></html>"], {type=>"text/html;charset=utf-8"})
|
||||
|
||||
is the same as calling this in JavaScript:
|
||||
|
||||
new Blob(["<html></html>"], {type:"text/html;charset=utf-8"})
|
||||
|
||||
This function was added in WebPerl `v0.05-beta`.
|
||||
|
||||
|
||||
The Mini IDE
|
||||
------------
|
||||
|
||||
**Warning:** The "mini IDE" included with WebPerl is currently *not* meant to
|
||||
be a full-featured IDE in which you're supposed to develop your WebPerl scripts.
|
||||
It started out as a way to simply inspect Emscripten's
|
||||
[virtual filesystem](#virtual-filesystem), and quickly test some features.
|
||||
Please consider it more of a *demo* of some of the things that are possible,
|
||||
and don't be surprised that it doesn't have many of the features you might
|
||||
expect from an IDE, and it has a few "quirks" (you are of course free to provide
|
||||
patches, if you like `;-)` ).
|
||||
|
||||
Note the default encoding for files is assumed to be UTF-8.
|
||||
|
||||
### The Editor
|
||||
|
||||
- 📄 "New File" - Clears the editor and starts a new file.
|
||||
|
||||
- ⬆ "File from Disk" - Allows you to select a file from your local computer
|
||||
for upload *into the editor*, i.e. it does not save anything into the
|
||||
virtual file system; you need to use "Save File" to do that.
|
||||
|
||||
- ⬇ "Download editor contents" - Allows you to download the text as currently
|
||||
shown in the editor as a file to your local disk.
|
||||
|
||||
- 📁 "Open File" - Lets you browse the virtual file system and select a file
|
||||
to open in the editor. Click the "Open File" button again to cancel the open.
|
||||
|
||||
- 💾 "Save File" - Saves the text currently shown in the editor into the
|
||||
file named in the "File Name" text box, so enter the file name there **first**.
|
||||
Remember that to persist something in the [virtual filesystem](#virtual-filesystem)
|
||||
longer than the current page, you need to save the file in a location like `/mnt/idb`.
|
||||
|
||||
### "Run & Persist"
|
||||
|
||||
This controls the Perl interpreter that is part of the current page.
|
||||
[Remember](#the-perl-interpreter-and-its-environment) that there can be only
|
||||
one Perl interpreter in a page at once, and it can only run once, which means
|
||||
that if you edit the code in the editor, you'll need to save the file to
|
||||
a location like `/mnt/idb` and re-load the entire page for the changes
|
||||
to be able to take effect.
|
||||
|
||||
The advantage of this mode is that it most resembles the way WebPerl is
|
||||
intended to be run as part of a web page - it starts once when the page
|
||||
is loaded (or in this case, when you click "Run"), and is then suspended
|
||||
after `main()` finishes, so that control passes back to the browser, and
|
||||
JavaScript can then call any handlers you've installed for things like click events.
|
||||
|
||||
### "Run Multi (via IFrame)"
|
||||
|
||||
This provides a "hacked" way to run multiple Perl interpreters without reloading
|
||||
the entire page, by loading a new page in an `<iframe>`. The script is run there,
|
||||
and when `main()` exits, Perl is ended, and its output fetched into the "output"
|
||||
textarea. The advantage of this mode is that it allows quicker edit-and-run cycles,
|
||||
which is good for testing, the disadvantage is that you can't really interact with
|
||||
Perl from the browser while it is running.
|
||||
|
||||
Remember to first save your script in a location like `/mnt/idb`, because
|
||||
otherwise the Perl instance in the `iframe` won't be able to see it
|
||||
(since it gets a fresh copy of the virtual file system).
|
||||
|
||||
|
||||
***
|
||||
|
||||
Additional notes on using WebPerl may be found in the
|
||||
[GitHub Wiki](https://github.com/haukex/webperl/wiki/Using-WebPerl).
|
||||
|
||||
***
|
||||
|
||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, <http://www.igb-berlin.de>
|
||||
|
||||
Please see the ["Legal" page](legal.html) for details.
|
||||
|
||||
***
|
||||
|
||||
You can find the source for this page at
|
||||
<https://github.com/haukex/webperl/blob/gh-pages/using.md>
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
|
||||
/** CSS file for emscr_ide.js **/
|
||||
|
||||
.emide {
|
||||
font-family: Calibri, Ubuntu, "Droid Sans", Tahoma, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.CodeMirror,.code {
|
||||
font-family: Consolas, "Ubuntu Mono", "Droid Sans Mono", "Lucida Console", "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
.toolbar button {
|
||||
margin: 0 0.2em;
|
||||
}
|
||||
|
||||
.statusbar {
|
||||
padding: 0.2em;
|
||||
height: 1em;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.filebrowser {
|
||||
border: 1px solid black;
|
||||
padding: 0.2em 0.5em;
|
||||
overflow: auto;
|
||||
height: 20em;
|
||||
}
|
||||
.fb-link { cursor:pointer; }
|
||||
.fb-link:hover { color:#000090; background:rgba(255,255,102,0.4); }
|
||||
.fb-link:active { color:#FF0000; }
|
||||
|
||||
.cm-resize-frame {
|
||||
overflow: hidden;
|
||||
resize: vertical;
|
||||
height: 24em;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
@ -0,0 +1,259 @@
|
||||
"use strict";
|
||||
|
||||
/** ***** WebPerl - http://webperl.zero-g.net *****
|
||||
*
|
||||
* Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
* at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
* Berlin, Germany, http://www.igb-berlin.de
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the same terms as Perl 5 itself: either the GNU General Public
|
||||
* License as published by the Free Software Foundation (either version 1,
|
||||
* or, at your option, any later version), or the "Artistic License" which
|
||||
* comes with Perl 5.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the licenses for details.
|
||||
*
|
||||
* You should have received a copy of the licenses along with this program.
|
||||
* If not, see http://perldoc.perl.org/index-licence.html
|
||||
**/
|
||||
|
||||
function make_emscr_ide (textarea, callbacks) {
|
||||
|
||||
var defaulttext = "use warnings;\nuse 5.028;\nuse WebPerl qw/js/;\n\n";
|
||||
var div = $('<div/>').addClass("emide");
|
||||
var ide = { elem: div, cleanval: '' };
|
||||
|
||||
var tb = $('<div/>').addClass("toolbar").appendTo(div);
|
||||
var file_new = $('<button/>',{title:"New File",html:"📄"}).appendTo(tb);
|
||||
var file_upload = $('<button/>',{title:"File from Disk",html:"⬆"}).appendTo(tb);
|
||||
var file_download = $('<button/>',{title:"Download editor contents",html:"⬇"}).appendTo(tb);
|
||||
var file_open = $('<button/>',{title:"Open File",html:"📁"}).appendTo(tb);
|
||||
var file_save = $('<button/>',{title:"Save File",html:"💾"}).appendTo(tb);
|
||||
var file_name = $('<input/>',{title:"File Name",type:"text",size:60}).addClass("code").appendTo(tb);
|
||||
//TODO Later: support for deleting files?
|
||||
|
||||
var upload_file = $('<input/>',{type:"file"});
|
||||
var file_up_form = $('<div/>').append(
|
||||
$('<form/>').append( upload_file ) ).appendTo(div);
|
||||
file_up_form.hide();
|
||||
|
||||
var fbrowser = make_emscr_ide_filebrowser();
|
||||
fbrowser.elem.hide();
|
||||
fbrowser.elem.appendTo(div);
|
||||
|
||||
var resize_frame = $('<div/>').addClass("cm-resize-frame").appendTo(div);
|
||||
textarea.replaceWith(div);
|
||||
resize_frame.append(textarea);
|
||||
|
||||
var statusbar = $('<div/>').addClass("statusbar").appendTo(div);
|
||||
|
||||
var cm = CodeMirror.fromTextArea( textarea[0], {
|
||||
lineNumbers: true, indentWithTabs: true,
|
||||
tabSize: 4, indentUnit: 4 });
|
||||
ide.cm = cm;
|
||||
/* With thanks to https://codepen.io/sakifargo/pen/KodNyR */
|
||||
var fr = resize_frame[0];
|
||||
var cm_resize = function() { cm.setSize(fr.clientWidth + 2, fr.clientHeight - 10); };
|
||||
cm_resize();
|
||||
if (window.ResizeObserver)
|
||||
new ResizeObserver(cm_resize).observe(fr);
|
||||
else if (window.MutationObserver)
|
||||
new MutationObserver(cm_resize).observe(fr, {attributes: true});
|
||||
|
||||
ide.isDirty = function () { return cm.getValue()!=ide.cleanval; };
|
||||
// returns true if the user chose to abort
|
||||
// returns false if the buffer is not dirty or the user chose to continue anyway
|
||||
ide.dirtyCheck = function () {
|
||||
if (ide.isDirty()) {
|
||||
// confirm() returns true if the user clicked "OK", and false otherwise.
|
||||
if (confirm("Unsaved changes in editor!\nContinue anyway?"))
|
||||
return false;
|
||||
else return true; // buffer is dirty and user chose to abort
|
||||
}
|
||||
else return false;
|
||||
};
|
||||
|
||||
file_new.click(function () {
|
||||
if (ide.dirtyCheck()) return;
|
||||
console.debug("IDE: New File");
|
||||
file_name.val("");
|
||||
ide.cleanval = defaulttext;
|
||||
cm.setValue(defaulttext);
|
||||
sessionStorage.removeItem('file_mru');
|
||||
statusbar.text("New File");
|
||||
});
|
||||
|
||||
file_upload.click(function () {
|
||||
if(!window.FileReader) {
|
||||
alert("Sorry, your browser does not support file uploads.");
|
||||
return; }
|
||||
if (file_up_form.is(":visible")) file_up_form.hide(); else file_up_form.show();
|
||||
});
|
||||
upload_file.on('change', function (chgEvt) {
|
||||
statusbar.text("");
|
||||
file_up_form.hide();
|
||||
if (ide.dirtyCheck()) return;
|
||||
file_name.val("");
|
||||
sessionStorage.removeItem('file_mru');
|
||||
console.debug("IDE: Reading file from local disk...");
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(loadEvt) {
|
||||
if(loadEvt.target.readyState != 2) return;
|
||||
if(loadEvt.target.error) {
|
||||
alert('Error while reading file');
|
||||
return; }
|
||||
console.debug("IDE: File read!");
|
||||
statusbar.text("File opened from local disk");
|
||||
cm.setValue(loadEvt.target.result);
|
||||
};
|
||||
reader.readAsText(chgEvt.target.files[0]);
|
||||
});
|
||||
|
||||
file_download.click(function () {
|
||||
var blob = new Blob([cm.getValue()],
|
||||
{type: "text/plain;charset=utf-8"});
|
||||
var link = document.createElement("a");
|
||||
link.download = 'script.pl';
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.target = '_blank';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
|
||||
file_open.click(function () {
|
||||
statusbar.text("");
|
||||
if (!fbrowser) return;
|
||||
if (fbrowser.isVisible()) {
|
||||
file_open.html("📁");
|
||||
fbrowser.cancel();
|
||||
}
|
||||
else {
|
||||
file_open.html("📂⃠");
|
||||
fbrowser.show(function (file) {
|
||||
file_open.html("📁");
|
||||
if (ide.dirtyCheck()) return;
|
||||
console.debug("IDE: Opening "+file);
|
||||
file_name.val(file);
|
||||
var data = FS.readFile(file,{encoding:"utf8"});
|
||||
ide.cleanval = data;
|
||||
cm.setValue(data);
|
||||
sessionStorage.setItem('file_mru',file);
|
||||
statusbar.text("Opened "+file);
|
||||
console.debug("IDE: Opened "+file);
|
||||
if (callbacks && callbacks.open) callbacks.open(file);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
file_save.click(function () {
|
||||
statusbar.text("");
|
||||
var file = file_name.val();
|
||||
if (file.length<1) {
|
||||
alert("Invalid File Name");
|
||||
return; }
|
||||
try {
|
||||
console.debug("IDE: Trying to save "+file);
|
||||
var data = cm.getValue();
|
||||
FS.writeFile( file, data );
|
||||
/* Note: The user may be saving to some location outside of the IDBFS, in which
|
||||
* case this syncfs call isn't really needed. But it doesn't really hurt either,
|
||||
* so we always do it. */
|
||||
FS.syncfs(false, function (err) {
|
||||
if(err) { console.error(err); alert("Saving IDBFS failed: "+err); return; }
|
||||
statusbar.text("Saved to "+file);
|
||||
console.debug("IDE: Saved "+file);
|
||||
sessionStorage.setItem('file_mru',file);
|
||||
ide.cleanval = data;
|
||||
if (callbacks && callbacks.save) callbacks.save(file);
|
||||
});
|
||||
}
|
||||
catch (err) { console.error(err); alert("Save Failed: "+err); }
|
||||
});
|
||||
|
||||
var mru_file = sessionStorage.getItem('file_mru');
|
||||
try {
|
||||
if (!mru_file) throw "No MRU file";
|
||||
var data = FS.readFile( mru_file, {encoding:"utf8"} );
|
||||
ide.cleanval = data;
|
||||
cm.setValue(data);
|
||||
file_name.val(mru_file);
|
||||
console.debug("IDE: Loaded "+mru_file);
|
||||
statusbar.text("Opened "+mru_file);
|
||||
if (callbacks && callbacks.open) callbacks.open(mru_file);
|
||||
}
|
||||
catch (e) {
|
||||
console.debug("IDE: Loading MRU failed:",e,"- falling back...");
|
||||
file_name.val('');
|
||||
ide.cleanval = defaulttext;
|
||||
cm.setValue(defaulttext);
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", function (evt) {
|
||||
if (ide.isDirty()) {
|
||||
var dialogText = "Unsaved changes in editor!";
|
||||
evt.returnValue = dialogText;
|
||||
return dialogText;
|
||||
}
|
||||
});
|
||||
|
||||
return ide;
|
||||
}
|
||||
|
||||
function make_emscr_ide_filebrowser() {
|
||||
var fb = {
|
||||
elem: $('<div/>').addClass("filebrowser"),
|
||||
curpath: (ENV && ENV.HOME) ? ENV.HOME : FS.cwd(),
|
||||
};
|
||||
fb.update = function () {
|
||||
fb.elem.empty();
|
||||
$('<div>📂 </div>').addClass("fb-curdir")
|
||||
.append(fb.curpath).appendTo(fb.elem);
|
||||
var files = FS.readdir(fb.curpath);
|
||||
$.each( files.sort(), function (idx,file) {
|
||||
if (file=='.') return;
|
||||
if (file=='..' && fb.curpath=='/') return;
|
||||
var fullfile = FS.joinPath([fb.curpath,file]);
|
||||
var stat = FS.stat(fullfile);
|
||||
var icon = "📜";
|
||||
var click;
|
||||
if (FS.isFile(stat.mode)) {
|
||||
icon = "📄";
|
||||
click = function (evt) {
|
||||
fb.elem.hide();
|
||||
if(fb.callback) fb.callback(fullfile);
|
||||
fb.callback = null;
|
||||
};
|
||||
}
|
||||
else if (FS.isDir(stat.mode)) {
|
||||
icon = file=='..'?"📁⃖":"📁";
|
||||
click = function (evt) {
|
||||
fb.curpath = fullfile;
|
||||
fb.update();
|
||||
};
|
||||
}
|
||||
else if (FS.isLink(stat.mode)) {
|
||||
icon = "📄⃪";
|
||||
}
|
||||
var el = $('<div>'+icon+' </div>').addClass("fb-link")
|
||||
.append(file).appendTo(fb.elem);
|
||||
if (click) el.click(click);
|
||||
});
|
||||
};
|
||||
fb.show = function (callback) {
|
||||
fb.callback = callback;
|
||||
fb.elem.show();
|
||||
fb.curpath = FS.cwd();
|
||||
fb.update();
|
||||
};
|
||||
fb.cancel = function () {
|
||||
fb.elem.hide();
|
||||
fb.callback = null;
|
||||
}
|
||||
fb.isVisible = function () { return fb.elem.is(":visible") }
|
||||
return fb;
|
||||
}
|
||||
@ -0,0 +1,246 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl (IDE)</title>
|
||||
|
||||
<!-- ##### WebPerl - http://webperl.zero-g.net #####
|
||||
|
||||
Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
Berlin, Germany, http://www.igb-berlin.de
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl 5 itself: either the GNU General Public
|
||||
License as published by the Free Software Foundation (either version 1,
|
||||
or, at your option, any later version), or the "Artistic License" which
|
||||
comes with Perl 5.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the licenses for details.
|
||||
|
||||
You should have received a copy of the licenses along with this program.
|
||||
If not, see http://perldoc.perl.org/index-licence.html
|
||||
-->
|
||||
|
||||
<!--cacheable--><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.css" integrity="sha256-I8NyGs4wjbMuBSUE40o55W6k6P7tu/7G28/JGUUYCIs=" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" type="text/css" href="emscr_ide.css" />
|
||||
<style>
|
||||
button,.text {
|
||||
font-family: Calibri, Ubuntu, "Droid Sans", Tahoma, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
pre,.code,textarea {
|
||||
font-family: Consolas, "Ubuntu Mono", "Droid Sans Mono", "Lucida Console", "Courier New", Courier, monospace;
|
||||
}
|
||||
.fakelink {
|
||||
color: darkblue;
|
||||
cursor: pointer;
|
||||
}
|
||||
.border {
|
||||
border: 1px solid black;
|
||||
margin: 0.5em;
|
||||
padding: 0.2em;
|
||||
}
|
||||
#evalcode_container .CodeMirror {
|
||||
height: 4em;
|
||||
border: 1px solid grey;
|
||||
margin: 0.2em 0;
|
||||
}
|
||||
.output {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.output textarea {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/codemirror.min.js" integrity="sha256-uRIJ6Wfou5cTtmcCvQNA9lvsYl8sUbZXxnfG+P79ssY=" crossorigin="anonymous"></script>
|
||||
<!--cacheable--><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.39.2/mode/perl/perl.min.js" integrity="sha256-Uu9QBfi8gU6J/MzQunal8ewmY+i/BbCkBrcAXA5bcCM=" crossorigin="anonymous"></script>
|
||||
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
||||
<script src="emscr_ide.js"></script>
|
||||
<script src="../webperl.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
console.debug("Running our Javascript...");
|
||||
|
||||
window.onerror = function(event) {
|
||||
alert('Exception thrown, see JavaScript console'); };
|
||||
|
||||
// This is a workaround for Emscripten only being able to call main() once per page load.
|
||||
// I wouldn't recommend this for "production" use.
|
||||
var baseurl = Perl.Util.baseurl(window.location);
|
||||
function run_perl_iframe (argv, state_callback, done_callback) {
|
||||
var html = '<html><head><base href="'+baseurl+'"><script src="webperl.js"></scr'+'ipt></head><body></body></html>';
|
||||
var blob = new Blob([html], {type: "text/html;charset=utf-8"});
|
||||
var src = URL.createObjectURL(blob);
|
||||
var iframe = $('<iframe/>',{id:'Perl_IFrame',src:src}).hide().appendTo('body');
|
||||
iframe.on('load', function() {
|
||||
var IFramePerl = iframe[0].contentWindow['Perl'];
|
||||
var outbuf = '';
|
||||
IFramePerl.output = function (str) { outbuf+=str }; //TODO Later: maybe dynamic output updating is possible?
|
||||
IFramePerl.endAfterMain = true;
|
||||
IFramePerl.addStateChangeListener( function (from,to) {
|
||||
if (state_callback) state_callback(to);
|
||||
if (from!='Ended' && to=='Ended') {
|
||||
iframe.remove();
|
||||
if (done_callback) done_callback(outbuf);
|
||||
URL.revokeObjectURL(src);
|
||||
}
|
||||
} );
|
||||
IFramePerl.init(function () {
|
||||
window.setTimeout(function () { IFramePerl.start(argv); }, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var eval_cm;
|
||||
$( function() {
|
||||
console.debug("Document is ready, setting up...");
|
||||
$('#runperl').prop("disabled",true);
|
||||
$('#evalperl').prop("disabled",true);
|
||||
$('#endperl').prop("disabled",true);
|
||||
|
||||
var once_out_ta = Perl.makeOutputTextarea();
|
||||
$("#once_output").append(once_out_ta);
|
||||
$("#once_out_clear").click(function () { $(once_out_ta).val("") });
|
||||
|
||||
{
|
||||
var multirun = $('#multi_run');
|
||||
var multirun_toggle = $('<div/>',{text:"Hide Run Multi (via IFrame)"});
|
||||
multirun_toggle.addClass('fakelink');
|
||||
multirun_toggle.click(function () {
|
||||
if (multirun.is(":visible")) {
|
||||
multirun.hide();
|
||||
multirun_toggle.text("Show Run Multi (via IFrame)");
|
||||
}
|
||||
else {
|
||||
multirun.show();
|
||||
multirun_toggle.text("Hide Run Multi (via IFrame)");
|
||||
}
|
||||
});
|
||||
multirun.before( multirun_toggle );
|
||||
}
|
||||
|
||||
// need to set up CodeMirror while #runonce is still visible (!)
|
||||
eval_cm = CodeMirror.fromTextArea( $('#evalcode')[0], {
|
||||
lineNumbers: true, indentWithTabs: true,
|
||||
tabSize: 4, indentUnit: 4 });
|
||||
{
|
||||
var runonce = $('#runonce');
|
||||
var runonce_toggle = $('<div/>',{text:"Show Run & Persist"});
|
||||
runonce_toggle.addClass('fakelink');
|
||||
runonce_toggle.click(function () {
|
||||
if (runonce.is(":visible")) {
|
||||
runonce.hide();
|
||||
runonce_toggle.text("Show Run & Persist");
|
||||
}
|
||||
else {
|
||||
runonce.show();
|
||||
runonce_toggle.text("Hide Run & Persist");
|
||||
}
|
||||
});
|
||||
runonce.before( runonce_toggle );
|
||||
runonce.hide();
|
||||
}
|
||||
Perl.addStateChangeListener( function (from,to) {
|
||||
$('#runstate').text("State: "+to);
|
||||
$('#runperl' ).prop("disabled", to!="Ready");
|
||||
$('#argv' ).prop("disabled", to!="Ready");
|
||||
$('#evalperl').prop("disabled", to!="Running");
|
||||
$('#endperl' ).prop("disabled", to!="Running");
|
||||
} );
|
||||
Perl.init(final_init);
|
||||
});
|
||||
|
||||
function final_init () { // Called when Perl goes to Ready state
|
||||
console.debug("Final initialization steps...");
|
||||
|
||||
// set up the IDE now that the Emscripten FS object is all ready to go
|
||||
var ide = make_emscr_ide( $('textarea#ide'), {
|
||||
open: function(file) { $('#argv,#multi_argv').val( JSON.stringify([file]) ) },
|
||||
save: function(file) { $('#argv,#multi_argv').val( JSON.stringify([file]) ) },
|
||||
} );
|
||||
|
||||
$('#multi_runperl').click(function () {
|
||||
if (ide.dirtyCheck()) return;
|
||||
$('#multi_runperl').prop("disabled",true); // we *could* run multiple Perls in parallel, I just don't recommend it (memory & performance issues)
|
||||
var argv = JSON.parse( $('#multi_argv').val() );
|
||||
$('#multi_out_ta').text("");
|
||||
run_perl_iframe(argv,
|
||||
//TODO Later: the user never gets to see the "Running" state
|
||||
function (newstate) { $('#multi_state').text("State: "+newstate) },
|
||||
function (output) {
|
||||
var ta = $('#multi_out_ta');
|
||||
ta.text(output);
|
||||
ta[0].scrollTop = ta[0].scrollHeight;
|
||||
$('#multi_runperl').prop("disabled",false);
|
||||
});
|
||||
});
|
||||
$('#runperl').click(function () {
|
||||
if (ide.dirtyCheck()) return;
|
||||
var argv = JSON.parse( $('#argv').val() );
|
||||
// run Perl async so that the window has a chance to refresh
|
||||
window.setTimeout(function () { Perl.start(argv); }, 1);
|
||||
});
|
||||
$('#evalperl').click(function () {
|
||||
var rv = Perl.eval( eval_cm.getValue() );
|
||||
console.debug('eval returned',rv);
|
||||
});
|
||||
$('#endperl').click(function () {
|
||||
if (confirm("Are you sure you want to end Perl?\nYou'll have to reload the page to restart Perl."))
|
||||
Perl.end();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="border text" style="text-align:center"><small>
|
||||
Really Simple Mini IDE as a Demo for <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a>
|
||||
- <a href="http://webperl.zero-g.net/using.html#the-mini-ide" target="_blank"><b>Documentation</b></a>
|
||||
</small></div>
|
||||
|
||||
<div class="border">
|
||||
<textarea id="ide" rows="24" cols="80"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="text border">
|
||||
<div id="runonce">
|
||||
<div>
|
||||
<span class="code">ARGV:</span> <small>(JSON)</small>
|
||||
<input type="text" id="argv" class="code" value='["--version"]' size="60" title="argv for perl (JSON)"/>
|
||||
<button id="runperl" title="Run perl"><span class="code">perl</span> ►</button>
|
||||
<button id="endperl" title="End perl">end ■</button>
|
||||
</div>
|
||||
<div class="statusbar" id="runstate">State: None</div>
|
||||
<div>
|
||||
<div id="evalcode_container"><textarea id="evalcode" rows="3" cols="80"></textarea></div>
|
||||
<div><button id="evalperl" title="eval Perl code"><span class="code">eval</span> ►</button></div>
|
||||
</div>
|
||||
<div class="output" id="once_output">
|
||||
<span class="code">STDOUT and STDERR:</span> <button id="once_out_clear">Clear</button><br/>
|
||||
<!-- output textarea will be inserted here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text border">
|
||||
<div id="multi_run">
|
||||
<div>
|
||||
<span class="code">ARGV:</span> <small>(JSON)</small>
|
||||
<input type="text" id="multi_argv" class="code" value='["--version"]' size="60" title="argv for perl (JSON)"/>
|
||||
<button id="multi_runperl" title="Run perl"><span class="code">perl</span> ►■</button><br/>
|
||||
<small><i>(Remember to store code in a persistent storage like <span class="code">/mnt/idb</span>!)</i></small>
|
||||
</div>
|
||||
<div class="statusbar" id="multi_state">State: None</div>
|
||||
<div class="output">
|
||||
<span class="code">STDOUT and STDERR:</span><br/>
|
||||
<textarea id="multi_out_ta" rows="24" cols="80" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl Tests</title>
|
||||
|
||||
<script src="webperl.js"></script>
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
window.onerror = function(event) {
|
||||
alert('Exception thrown, see JavaScript console'); };
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
|
||||
document.getElementById('output')
|
||||
.appendChild( Perl.makeOutputTextarea() );
|
||||
|
||||
var status = document.getElementById("status");
|
||||
Perl.endAfterMain = true;
|
||||
Perl.addStateChangeListener( function (from,to) {
|
||||
if (from!="Ended" && to=="Ended")
|
||||
status.textContent = "Tests finished, see output:";
|
||||
} );
|
||||
Perl.init(function () {
|
||||
status.textContent = "Running tests...";
|
||||
window.setTimeout(function () {
|
||||
Perl.start(['/opt/perl/dev/WebPerl.t']);
|
||||
}, 1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p id="status">Loading tests...</p>
|
||||
<div id="output"></div>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,388 @@
|
||||
"use strict";
|
||||
|
||||
/** ***** WebPerl - http://webperl.zero-g.net *****
|
||||
*
|
||||
* Copyright (c) 2018 Hauke Daempfling (haukex@zero-g.net)
|
||||
* at the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),
|
||||
* Berlin, Germany, http://www.igb-berlin.de
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the same terms as Perl 5 itself: either the GNU General Public
|
||||
* License as published by the Free Software Foundation (either version 1,
|
||||
* or, at your option, any later version), or the "Artistic License" which
|
||||
* comes with Perl 5.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the licenses for details.
|
||||
*
|
||||
* You should have received a copy of the licenses along with this program.
|
||||
* If not, see http://perldoc.perl.org/index-licence.html
|
||||
**/
|
||||
|
||||
/* -- Please see the documentation at http://webperl.zero-g.net/using.html -- */
|
||||
|
||||
var Module;
|
||||
var Perl = {
|
||||
trace: false, // user may enable this
|
||||
endAfterMain: false, // user may enable this (before Perl.init)
|
||||
noMountIdbfs: false, // user may enable this (before Perl.start)
|
||||
WebPerlVersion: 'v0.11-beta', // user may read (only!) this
|
||||
state: "Uninitialized", // user may read (only!) this
|
||||
exitStatus: undefined, // user may read (only!) this
|
||||
Util: {},
|
||||
// internal variables:
|
||||
initStepsLeft: 2, // Must match number of Perl.initStepFinished() calls!
|
||||
readyCallback: null,
|
||||
stdout_buf: "", stderr_buf: "", // for our default Perl.output implementation
|
||||
dispatch: function (perl) {
|
||||
Perl._call_code_args = Array.prototype.slice.call(arguments, 1);
|
||||
Perl.eval(perl);
|
||||
if (Perl._call_code_error) {
|
||||
var err = Perl._call_code_error;
|
||||
delete Perl._call_code_error;
|
||||
throw err;
|
||||
}
|
||||
else {
|
||||
var rv = Perl._call_code_rv;
|
||||
delete Perl._call_code_rv;
|
||||
return rv;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/* TODO: Embedded script should be able to influence the running of Perl,
|
||||
* the cleanest would probably be to set properties on the Perl object,
|
||||
* such as Perl.autorun = false or Perl.argv = [...]. It should be possible
|
||||
* for the user to do this for embedded scripts also! Will probably need
|
||||
* to change the initialization of Perl so that the user can set its properties
|
||||
* *before* loading webperl.js. */
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
// Note: to get the content of script tags with jQuery: $('script[type="text/perl"]').html()
|
||||
var scripts = [];
|
||||
var script_src;
|
||||
document.querySelectorAll("script[type='text/perl']")
|
||||
.forEach(function (el) {
|
||||
if (el.src) {
|
||||
if (script_src || scripts.length)
|
||||
console.error('Only a single Perl script may be loaded via "script src=", ignoring others');
|
||||
else
|
||||
script_src = el.src;
|
||||
}
|
||||
else {
|
||||
if (script_src)
|
||||
console.error('Only a single Perl script may be loaded via "script src=", ignoring others');
|
||||
else
|
||||
scripts.push(el.innerHTML);
|
||||
}
|
||||
});
|
||||
if (script_src) {
|
||||
console.debug("Perl: Found a script with src, fetching and running...", script_src);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function () {
|
||||
//TODO Later: Might be nice to name the script in the virtual FS after the URL instead of a generic name
|
||||
Perl._saveAndRun( this.responseText );
|
||||
});
|
||||
xhr.open("GET", script_src);
|
||||
xhr.send();
|
||||
}
|
||||
else if (scripts.length) {
|
||||
console.debug("Perl: Found",scripts.length,"embedded script(s), autorunning...");
|
||||
var code = scripts.join(";\n");
|
||||
|
||||
// get the first five lines of code
|
||||
var n = 5 + 1; // the contents of the <script> tag will usually begin with a newline
|
||||
var i = -1;
|
||||
while (n-- && i++ < code.length) {
|
||||
i = code.indexOf("\n", i);
|
||||
if (i < 0) break;
|
||||
}
|
||||
var head = i<0 ? code : code.substring(0,i);
|
||||
// look for a "use WebPerl"
|
||||
const regex = /^\s*use\s+WebPerl(\s|;)/m;
|
||||
if (!regex.exec(head)) { // load WebPerl unless the user loaded it
|
||||
console.debug("Perl: Autoloading WebPerl");
|
||||
code = "use WebPerl 'js';\n" + code;
|
||||
}
|
||||
|
||||
Perl._saveAndRun(code);
|
||||
}
|
||||
else console.debug("Perl: No embedded scripts");
|
||||
});
|
||||
Perl._saveAndRun = function (script) {
|
||||
Perl.init(function () {
|
||||
var file = "/tmp/scripts.pl";
|
||||
try {
|
||||
FS.writeFile( file, script );
|
||||
console.debug("Perl: Saved script(s) to ",file,", now running");
|
||||
window.addEventListener("beforeunload", function () {
|
||||
// not really needed because we're unloading anyway, but for good measure, end Perl...
|
||||
console.debug("Perl: beforeunload, ending...");
|
||||
Perl.end();
|
||||
});
|
||||
// run Perl async so that the window has a chance to refresh
|
||||
window.setTimeout(function () { Perl.start( [ file ] ); }, 1);
|
||||
}
|
||||
catch (err) { console.error("Perl:",err); alert("Save to "+file+" failed: "+err); }
|
||||
});
|
||||
};
|
||||
|
||||
Perl.changeState = function (newState) {
|
||||
if (Perl.state==newState) return;
|
||||
var oldState = Perl.state;
|
||||
Perl.state = newState;
|
||||
if (Perl.stateChanged) {
|
||||
console.info("Perl.stateChanged is deprecated, please use Perl.addStateChangeListener instead");
|
||||
Perl.stateChanged(oldState,newState);
|
||||
}
|
||||
for( var i=0 ; i<Perl.stateChangeListeners.length ; i++ )
|
||||
Perl.stateChangeListeners[i](oldState,newState);
|
||||
};
|
||||
Perl.stateChangeListeners = [ function (from,to) {
|
||||
console.debug("Perl: state changed from "+from+" to "+to);
|
||||
} ];
|
||||
Perl.addStateChangeListener = function (handler) {
|
||||
Perl.stateChangeListeners.push(handler);
|
||||
};
|
||||
|
||||
// chan: 1=STDOUT, 2=STDERR
|
||||
// implementations are free to ignore the "chan" argument if they want to merge the two streams
|
||||
Perl.output = function (str,chan) { // can be overridden by the user
|
||||
var buf = chan==2 ? 'stderr_buf' : 'stdout_buf';
|
||||
Perl[buf] += str;
|
||||
var pos = Perl[buf].indexOf("\n");
|
||||
while (pos>-1) {
|
||||
console.log( chan==2?"STDERR":"STDOUT", Perl[buf].slice(0,pos) );
|
||||
Perl[buf] = Perl[buf].slice(pos+1);
|
||||
pos = Perl[buf].indexOf("\n");
|
||||
}
|
||||
};
|
||||
Perl.outputLine = function (chan,text) { // internal function
|
||||
if (arguments.length > 2) text = Array.prototype.slice.call(arguments,1).join(' ');
|
||||
Perl.output(text,chan);
|
||||
Perl.output("\n",chan);
|
||||
};
|
||||
Perl.outputChar = function (chan,c) { // internal function
|
||||
Perl.output(String.fromCharCode(c),chan);
|
||||
};
|
||||
|
||||
Perl.makeOutputTextarea = function (id) {
|
||||
var ta = document.createElement('textarea');
|
||||
if (id) ta.id = id;
|
||||
ta.rows = 24; ta.cols = 80;
|
||||
ta.setAttribute("readonly",true);
|
||||
Perl.output = function (str) {
|
||||
ta.value = ta.value + str;
|
||||
ta.scrollTop = ta.scrollHeight;
|
||||
};
|
||||
return ta;
|
||||
};
|
||||
|
||||
var getScriptURL = (function() { // with thanks to https://stackoverflow.com/a/2976714
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var index = scripts.length - 1;
|
||||
var myScript = scripts[index];
|
||||
return function() { return myScript.src; };
|
||||
})();
|
||||
|
||||
Perl.Util.baseurl = function (urlstr) {
|
||||
var url = new URL(urlstr);
|
||||
if (url.protocol=='file:')
|
||||
return url.href.substring(0, url.href.lastIndexOf('/'));
|
||||
else
|
||||
return url.origin + url.pathname.substring(0, url.pathname.lastIndexOf('/'));
|
||||
};
|
||||
|
||||
Perl.init = function (readyCallback) {
|
||||
if (Perl.state != "Uninitialized")
|
||||
throw "Perl: can't call init in state "+Perl.state;
|
||||
Perl.changeState("Initializing");
|
||||
// Note that a lot of things still won't work for file:// URLs because of the Same-Origin Policy.
|
||||
// see e.g. https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSRequestNotHttp
|
||||
var baseurl = Perl.Util.baseurl(getScriptURL());
|
||||
Perl.readyCallback = readyCallback;
|
||||
Module = {
|
||||
noInitialRun: true,
|
||||
noExitRuntime: true,
|
||||
print: Perl.outputLine.bind(null,1), printErr: Perl.outputLine.bind(null,2),
|
||||
stdout: Perl.outputChar.bind(null,1), stderr: Perl.outputChar.bind(null,2),
|
||||
stdin: function () { return null },
|
||||
arguments: ['--version'],
|
||||
onAbort: function () {
|
||||
console.error("Perl: Aborted (state",Perl.state,")");
|
||||
alert("Perl aborted in state "+Perl.state);
|
||||
Perl.exitStatus = -1;
|
||||
Perl.changeState("Ended");
|
||||
},
|
||||
onExit: function (status) { // note this may be called multiple times
|
||||
if (status==0)
|
||||
console.debug( "Perl: Exit status",status,"(state",Perl.state,")");
|
||||
else {
|
||||
console.error("Perl: Exit status",status,"(state",Perl.state,")");
|
||||
alert("Perl exited with exit status "+status+" in state "+Perl.state);
|
||||
}
|
||||
Perl.exitStatus = status;
|
||||
Perl.changeState("Ended");
|
||||
},
|
||||
onRuntimeInitialized: function () {
|
||||
console.debug("Perl: Module.onRuntimeInitialized");
|
||||
Perl.initStepFinished();
|
||||
},
|
||||
preRun: [
|
||||
function () {
|
||||
if (Perl.noMountIdbfs) {
|
||||
console.debug("Perl: doing preRun, skipping IndexDB filesystem");
|
||||
Perl.initStepFinished();
|
||||
return;
|
||||
}
|
||||
console.debug("Perl: doing preRun, fetching IndexDB filesystem...");
|
||||
try { FS.mkdir('/mnt'); } catch(e) {}
|
||||
try { FS.mkdir('/mnt/idb'); } catch(e) {}
|
||||
FS.mount(IDBFS, {} ,'/mnt/idb');
|
||||
FS.syncfs(true, function (err) {
|
||||
if (err) { console.error("Perl:",err); alert("Perl: Loading IDBFS failed: "+err); return; }
|
||||
console.debug("Perl: IndexDB filesystem ready");
|
||||
Perl.initStepFinished();
|
||||
});
|
||||
}
|
||||
],
|
||||
locateFile: function (file) {
|
||||
var wasmRe = /\.(wast|wasm|asm\.js|data)$/;
|
||||
if (wasmRe.exec(file))
|
||||
return baseurl+"/"+file;
|
||||
return file;
|
||||
},
|
||||
};
|
||||
if (Perl.endAfterMain) {
|
||||
Module.preRun.push(function () {
|
||||
// patch _main so that afterwards we call emperl_end_perl
|
||||
var origMain = Module._main;
|
||||
Module._main = function() {
|
||||
origMain.apply(this, arguments);
|
||||
console.debug("Perl: main() has ended, ending perl...");
|
||||
return Perl.end();
|
||||
};
|
||||
});
|
||||
}
|
||||
console.debug("Perl: Fetching Emscripten/Perl...");
|
||||
var script = document.createElement('script');
|
||||
script.async = true; script.defer = true;
|
||||
script.src = baseurl+"/emperl.js";
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
};
|
||||
|
||||
Perl.initStepFinished = function () {
|
||||
if (Perl.state!="Initializing" || Perl.initStepsLeft<1)
|
||||
throw "Perl: internal error: can't call initStepFinished in state "+Perl.state+" ("+Perl.initStepsLeft+")";
|
||||
if (--Perl.initStepsLeft) {
|
||||
console.debug("Perl: One init step done, but "+Perl.initStepsLeft+" steps left, waiting...");
|
||||
return;
|
||||
} else console.debug("Perl: All init steps done, doing final initialization...");
|
||||
|
||||
/* NOTE: Apparently, when NO_EXIT_RUNTIME is set, and exit() is called from the main program
|
||||
* (from Module.callMain), Emscripten doesn't report this back to us via an ExitStatus exception
|
||||
* like it does from ccall - it doesn't even call the addOnExit/ATEXIT or addOnPostRun/ATPOSTRUN
|
||||
* handlers! So at the moment, the only reliable way I've found to catch the program exit()ing
|
||||
* is to patch into Emscripten's (undocumented!) Module.quit... */
|
||||
var origQuit = Module.quit;
|
||||
Module.quit = function (status, exception) {
|
||||
console.debug("Perl: quit with",exception);
|
||||
Module.onExit(status);
|
||||
origQuit(status,exception);
|
||||
}
|
||||
|
||||
Perl.changeState("Ready");
|
||||
if (Perl.readyCallback) Perl.readyCallback();
|
||||
Perl.readyCallback = null;
|
||||
};
|
||||
|
||||
Perl.start = function (argv) {
|
||||
if (Perl.state!="Ready")
|
||||
throw "Perl: can't call start in state "+Perl.state;
|
||||
Perl.changeState("Running");
|
||||
try {
|
||||
// Note: currently callMain doesn't seem to throw ExitStatus exceptions, see discussion in Perl.initStepFinished
|
||||
Module.callMain(argv ? argv : Module.arguments);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof ExitStatus) {
|
||||
console.debug("Perl: start:",e);
|
||||
Module.onExit(e.status);
|
||||
} else throw e;
|
||||
}
|
||||
};
|
||||
|
||||
Perl.eval = function (code) {
|
||||
if (Perl.state!="Running")
|
||||
throw "Perl: can't call eval in state "+Perl.state;
|
||||
if (Perl.trace) console.debug('Perl: ccall webperl_eval_perl',code);
|
||||
try {
|
||||
return ccall("webperl_eval_perl","string",["string"],[code]);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof ExitStatus) {
|
||||
// the code caused perl to (try to) exit - now we need to call
|
||||
// Perl's global destruction via our emperl_end_perl() function
|
||||
Perl.end(); //TODO: Perl.end has already been called at this point (how?)
|
||||
} else throw e;
|
||||
}
|
||||
};
|
||||
|
||||
/* Note that Emscripten apparently doesn't support re-running the program once it exits (?).
|
||||
* So at the moment, once we end Perl, that's it. The only useful effect of ending Perl is
|
||||
* that global destruction is executed and END blocks are called. But since a user may leave
|
||||
* a webpage at any moment without warning, WebPerl scripts should not rely on normal termination! */
|
||||
Perl.end = function () {
|
||||
if (Perl.state!="Running") {
|
||||
if (Perl.state=="Ended") {
|
||||
console.debug("Perl: end called when already Ended");
|
||||
return;
|
||||
}
|
||||
else throw "Perl: can't call end in state "+Perl.state;
|
||||
}
|
||||
var status;
|
||||
try {
|
||||
status = ccall("emperl_end_perl","number",[],[]);
|
||||
// we know that emperl_end_perl only calls exit() on a nonzero exit code,
|
||||
// which means no ExitStatus exception gets thrown on a zero exit code,
|
||||
// so we *should* reach this point only with status==0
|
||||
if (status!=0) console.warn("emperl_end_perl returned with status",status);
|
||||
Module.onExit(status); // does Perl.changeState() for us
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof ExitStatus) {
|
||||
console.debug("Perl: end: ",e);
|
||||
status = e.status;
|
||||
Module.onExit(e.status); // does Perl.changeState() for us
|
||||
} else throw e;
|
||||
}
|
||||
return status;
|
||||
};
|
||||
|
||||
Perl.next_glue_id = 0;
|
||||
Perl.GlueTable = {};
|
||||
Perl._glue_free_ids = {};
|
||||
Perl.glue = function (ref) {
|
||||
var id;
|
||||
var free_ids = Object.keys(Perl._glue_free_ids);
|
||||
if (free_ids.length>0) {
|
||||
id = free_ids[0];
|
||||
delete Perl._glue_free_ids[id];
|
||||
if (Perl.trace) console.debug('Perl: Glue reused id',id,'to',ref);
|
||||
}
|
||||
else {
|
||||
if (Perl.next_glue_id>=Number.MAX_SAFE_INTEGER)
|
||||
throw "Perl.GlueTable is overflowing!"
|
||||
id = ''+(Perl.next_glue_id++);
|
||||
if (Perl.trace) console.debug('Perl: Glue id',id,'to',ref);
|
||||
}
|
||||
Perl.GlueTable[id] = ref;
|
||||
return id;
|
||||
}
|
||||
Perl.unglue = function (id) {
|
||||
if (Perl.trace) console.debug('Perl: Unglue id',id,'from',Perl.GlueTable[id]);
|
||||
delete Perl.GlueTable[id];
|
||||
Perl._glue_free_ids[id]=1;
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env perl
|
||||
use warnings;
|
||||
use strict;
|
||||
use FindBin;
|
||||
use Plack::MIME;
|
||||
use Plack::Builder qw/builder enable mount/;
|
||||
use Plack::App::Directory ();
|
||||
use Cpanel::JSON::XS qw/decode_json encode_json/;
|
||||
require Plack::Middleware::CrossOrigin;
|
||||
|
||||
# Demo Plack server for WebPerl
|
||||
# run me with "plackup webperl.psgi"
|
||||
|
||||
# in an Apache .htaccess file, one could say:
|
||||
#AddType application/javascript .js
|
||||
#AddType application/octet-stream .data .mem
|
||||
#AddType application/wasm .wasm
|
||||
|
||||
Plack::MIME->add_type(".js" => "application/javascript");
|
||||
Plack::MIME->add_type(".data" => "application/octet-stream");
|
||||
Plack::MIME->add_type(".mem" => "application/octet-stream");
|
||||
Plack::MIME->add_type(".wasm" => "application/wasm");
|
||||
|
||||
my $SERV_ROOT = $FindBin::Bin;
|
||||
|
||||
my $app_ajaxtest = sub {
|
||||
my $req = Plack::Request->new(shift);
|
||||
my $rv = eval {
|
||||
my $content = decode_json( $req->content );
|
||||
|
||||
# We can do anything we like here, like e.g. call Perl subs,
|
||||
# read/write files on the server, etc. - for this demo we're
|
||||
# just going to munge some data from the request.
|
||||
$content->{hello} .= "The server says hello!\n";
|
||||
|
||||
$content; # return value from eval (must be a true value)
|
||||
}; my $e = $@||'unknown error';
|
||||
my $res = $req->new_response($rv ? 200 : 500);
|
||||
$res->content_type($rv ? 'application/json' : 'text/plain');
|
||||
$res->body($rv ? encode_json($rv) : 'Server Error: '.$e);
|
||||
return $res->finalize;
|
||||
};
|
||||
|
||||
builder {
|
||||
enable 'SimpleLogger';
|
||||
enable 'CrossOrigin', origins => '*';
|
||||
enable 'Static',
|
||||
path => qr/\.(?:html?|js|css|data|mem|wasm|pl)\z/i,
|
||||
root => $SERV_ROOT;
|
||||
mount '/' => Plack::App::Directory->new({root=>$SERV_ROOT})->to_app;
|
||||
mount '/ajaxtest' => $app_ajaxtest;
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>WebPerl <script> Demos</title>
|
||||
|
||||
<script src="webperl.js"></script>
|
||||
|
||||
<!-- Please see the documentation at http://webperl.zero-g.net/using.html -->
|
||||
|
||||
<!-- Example 1: A really basic script -->
|
||||
<script type="text/perl">
|
||||
print "Hello, Perl World!\n";
|
||||
</script>
|
||||
|
||||
<!-- Example 2: Accessing JavaScript -->
|
||||
<script type="text/perl">
|
||||
use warnings;
|
||||
use strict;
|
||||
use WebPerl qw/js/;
|
||||
|
||||
js('document')->getElementById('my_button')
|
||||
->addEventListener("click", sub {
|
||||
print "You clicked 'Testing!'\n";
|
||||
} );
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Example 3: Using jQuery -->
|
||||
<!--cacheable--><script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
|
||||
<script type="text/perl">
|
||||
use warnings;
|
||||
use strict;
|
||||
use WebPerl qw/js/;
|
||||
|
||||
my $jq = js('jQuery');
|
||||
my $btn = $jq->('<button>', { text=>"Click me!" } );
|
||||
$btn->click(sub {
|
||||
print "You clicked the jQuery button!\n";
|
||||
} );
|
||||
$btn->appendTo( $jq->('#buttons') );
|
||||
|
||||
# And a demo of AJAX using jQuery:
|
||||
use Cpanel::JSON::XS qw/encode_json/;
|
||||
use Data::Dumper;
|
||||
my $data_out = { hello => "Hello, World!\n" };
|
||||
$jq->ajax( '/ajaxtest', {
|
||||
method => 'POST', # we're sending JSON in the POST body
|
||||
data => encode_json($data_out),
|
||||
} )->done( sub {
|
||||
my $data_in = shift;
|
||||
print "AJAX Success! Data: ", Dumper($data_in->toperl);
|
||||
} )->fail( sub {
|
||||
my ($jqXHR, $textStatus, $errorThrown) = @_;
|
||||
print "AJAX Failed! ($errorThrown)\n";
|
||||
} );
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Optional STDOUT/STDERR text area (if you don't use this, output goes to Javascript console) -->
|
||||
<script>
|
||||
window.addEventListener("load", function () {
|
||||
document.getElementById('output')
|
||||
.appendChild( Perl.makeOutputTextarea() );
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>This is a demo of <a href="http://webperl.zero-g.net" target="_blank">WebPerl</a>!</p>
|
||||
|
||||
<div id="output"></div>
|
||||
<div id="buttons">
|
||||
<button id="my_button">Testing!</button>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Loading…
Reference in New Issue