How can I make a rich web application in Perl? - perl

I have a Perl command-line script that I want to convert to a rich. cross-platform desktop GUI application. What's the best method to do it. I want to the internal and the logic code in Perl but the GUI should be rich web application.

I have been working on the Perl module XUL::Gui on CPAN which uses Firefox as a host platform to render cross platform gui's from Perl. All you need is Firefox installed on the target platforms. It is currently in development, but may be stable enough for your needs. Here is a brief example of how the syntax looks:
use XUL::Gui;
display Window title => 'Foo Processor',
Hbox(
(map {
my $id = $_;
CheckBox id => $id,
label => "use $id",
option => sub {shift->checked eq 'true' ? " -$id" : ()}
} qw/foo bar baz/
),
TextBox( id => 'num',
type => 'number',
option => sub {' -num ' . shift->value}
),
Button( label => 'run', oncommand => sub {
my #opts = map {$ID{$_}->option} qw/foo bar baz num/;
$ID{txt}->value = `fooproc #opts`;
}),
),
TextBox( FILL, id => 'txt' );
Since it is under development, if you have any feature requests (or find any bugs) let me know.
Also, since you are inside of Firefox, any web technologies that Firefox supports (canvas, iframes, flash...) are fully usable from Perl. For gui components, you can use any combination of HTML and XUL tags.

Mojolicious is a light but yet powerful Web framework which is particularly useful to wrap scripts into quick and well done Web apps.
We are using it extensively on the local network to let colleagues make use of the scripts we develop on our Unix boxes, whatever their platform. For simple tasks, you can even pack everything (templates and routing) in one file: check Mojolicous::Lite.

Does it need to be a rich, native-OS style GUI? Or just any old user interface? You could get something up and running very quickly with CGI::Application - it's probably the best balance between clean, maintainable code and a short learning curve. For more heavily duty work, Catalyst seems to be the weapon of choice.
If you want to make a full-featured GUI with menus and draggable dialog boxes etc. Perl is probably not the best candidate.

If by the gui should be web for cross platfrom you mean it should be written using HTML / CSS /JavaScript, one solution is to use HTTP::Server::Simple::CGI in conjunction with CGI::Application and HTML::Template. Browser::Open completes the required minimal functionality.

Related

Extract and Format Information from TAP Archive

What I like to do:
I am using Rex to remotely call tests at servers. I remotely execute the tests with a call the the local prove. I want to gather all the information about the testruns at the different servers at one place. To achieve this I run the tests with prove -a (and maybe also with --merge for capturing STDERR) to create an archive (.tgz). I then download this archive again with Rex to the controlling server. I think this is quite a good plan so far...
My problem now is that I find a lot of hints on creating such a TAP-archive, but none of how I can actually read this archive. Sure, I could open and process it somehow with Archive::Tar or parse it manually with TAP::Parser as suggested by Schwern. But knowing that there are formatters like TAP::Formatter::HTML or TAP::Formatter::JUnit (e.g. for Jenkins) I think there must be a way of using those tools directly on a TAP-archive? When I look up the docs I only find hints on how to use this stuff with prove to format tests while running them. But I need to use this formatters on the archive, I have been running prove already remotely...
So far about the context. My question in short is: How can I use the Perl-TAP-Tools to format TAP coming from a TAP-archive produced by prove?
I am thankful for any little hints. Also if you see a problem in my approach in general.
Renée provided a working solution here: http://www.perl-community.de/bat/poard/thread/18420 (German)
use strict;
use warnings;
use TAP::Harness::Archive;
use TAP::Harness;
use TAP::Formatter::HTML;
my $formatter = TAP::Formatter::HTML->new;
my $harness = TAP::Harness->new({ formatter => $formatter });
$formatter->really_quiet(1);
$formatter->prepare;
my $session;
my $aggregator = TAP::Harness::Archive->aggregator_from_archive({
archive => '/must/be/the/complete/path/to/test.tar.gz',
parser_callbacks => {
ALL => sub {
$session->result( $_[0] );
},
},
made_parser_callback => sub {
$session = $formatter->open_test( $_[1], $_[0] );
}
});
$aggregator->start;
$aggregator->stop;
$formatter->summary($aggregator);
Tanks a lot! I hope this will help some others too. It seems like this knowledge is not very wide spread yet.
I have made a module to pack this solution in a nice interface: https://metacpan.org/module/Convert::TAP::Archive
So from now on you can just type this:
use Convert::TAP::Archive qw(convert_from_taparchive);
my $html = convert_from_taparchive(
'/must/be/the/complete/path/to/test.tar.gz',
'TAP::Formatter::HTML',
);
The problem with the output is mentioned in the docs. Please provide patches or comments if you know how to fix this (minor) issue. E.g. here: https://github.com/borisdaeppen/Convert-TAP-Archive
Renee pointed me to how Tapper makes it: https://metacpan.org/source/TAPPER/Tapper-TAP-Harness-4.1.1/lib/Tapper/TAP/Harness.pm#L273
Quite some effort to read an archive though...

perl Template::Alloy with Catalyst not displaying properly

From my understanding, Template::Alloy::TT should be interchangable with Template Toolkit, however I am having some issues trying to swap one out with the other. Here is the config for my view file:
package maypp::View::HTML;
use strict;
use base 'Catalyst::View::TT';
__PACKAGE__->config({
INCLUDE_PATH => [
myapp->path_to( 'root', 'src' ),
myapp->path_to( 'root', 'lib' ),
],
PRE_PROCESS => 'config/main',
WRAPPER => 'site/wrapper',
ERROR => 'error.html',
TIMER => 0,
render_die => 1,
COMPILE_DIR => '/tmp/compiled_templates', #caches compiled templates
STAT_TTL => 1, #how long to cache templates before seeing if there are any changes
TEMPLATE_EXTENSION => '.html',
});
I thought that changing Catalyst::View::TT to Catalyst::View::TT::Alloy would be all I had to do to begin using Template::Alloy (this has been the procedure for me before). However, whenever I change this I do not get the correct output. Below is my wrapper file:
[% IF template.name.match('\.(css|js|txt)');
debug("Passing page through as text: $template.name");
content;
ELSE;
debug("Applying HTML page layout wrappers to $template.name\n");
content WRAPPER "$host/site/html" + "$host/site/layout";
END;
-%]
site/html will be processed, however site/layout does not go into site/html like it does when I just use regular Template Toolkit (typically site/layout goes into [% content %] in site/html). Is there something I'm doing wrong here? I'd like to use Template::Alloy for the speed increase, but that's only if I can get it working :) Thanks for the help!
Just taking a stab in the dark, I'd say TT::Alloy might not support the multiple WRAPPER + directive. In quite a few years of TT development, I've never used it, but that's not to say it isn't popular somewhere. My experience is an app might switch between wrapper A and wrapper B based on context, but wrapping B within A? Not so much.
If you're always going to need to do that though, why not put the second WRAPPER directive inside $host/site/html.tt ?

Can I dynamically detect the Zend framework in the include path?

I'm developing a plugin for WordPress that will add additional functionality if the Zend framework is available, but the functionality added is not great enough to justify the user installing the framework if it does not already exist.
My question is, is there any good way to detect if Zend exists? Obviously I can use get_include_path() to return whatever the include path is, but beyond that I'm not sure. I could use regexes to determine if the phrase zend appears in the paths, but that seems unreliable at best (more thinking false positives than false negatives, but I think both have a potential if people haven't used the default path).
If I have to resort to this regex, I can always trap the errors as they come and proceed from there, but if there's a better way then that would be useful to know.
Simplest way:
if (stream_resolve_include_path('Zend/Version.php')) {
// ZF found
}
but I would question why you need to do this. If your plugin needs to be coded to work without the framework, what do you gain by using it if it's there? Seems like this would just complicate your code.
Yes this is somewhat easy:
$zfPresent = (bool) stream_resolve_include_path("Zend/Application.php")
If the file could be found inside the include paths stream_resolve_include_path() will return it's absoulte path - if not it will return false.
So if it is found the framework is definatly there.
Only grain of salt for some people: It requires at least PHP 5.3.2
See: http://php.net/manual/de/function.stream-resolve-include-path.php
If the PHP version does not allow you to use the above solution:
Try something like this:
set_error_handler(function($code, $text, $file, $line) {
// Handle zend not present
/* So that internal error handling won't be executed */
return true;
});
include("Zend/Application.php");
restore_error_handler();
I don't think it's really elegant but it should be somewhat reliable in detecting Zend. Another way may be something like:
function checkForZf()
{
$includePaths = array_merge(explode(PATH_SEPARATOR, get_include_path(), array($myAppsLib));
foreach($includePaths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . 'Zend' . DIRECTORY_SEPARATOR . 'Application.php') {
return true;
}
}
return false;
}
This should be also somewhat reliable but file actions are performance expensive. You may test it out in regards to performance or store the state somewhere after first detection so it does not need to run on every request.

Clash Between Multiple Locales in a mod_perl Setting?

I'm currently working on internationalizing a large Perl/Mason web application (Perl 5.8.0, Mason 1.48, mod_perl & Apache). In choosing a localization module, I decided to go with Locale::TextDomain over Locale::Maketext, mostly because the latter's plural form support isn't as nice as I'd like.
The hang-up I'm having with Locale::TextDomain is that it resolves which catalog to use for translations based on the process' locale. When I realized this, I got worried about how this would affect my application if I wanted users to be able to use different locales -- would it be possible that a change in locale to suit one user's settings would affect another user's session? For example, could there be a situation in which an English user received a page in German because a German user's session changed the process' locale? I'm not very knowledgeable about how Apache's thread/process model works, though it seems that if multiple users can be served by the same thread, this could happen.
This email thread would indicate that this is possible; here the OP describes the situation I'm thinking about.
If this is true, is there a way I can prevent this scenario while still using Locale::TextDomain? I suppose I could always hack at the module to load the catalogs in a locale-independent (probably using DBD::PO), but hopefully I'm just missing something that will solve my problem...
You entirely avoid the setlocale problems by using web_set_locale instead.
(That message on the mailing list predates the addition of that function by about 4 years.)
Edit: You are correct that global behaviour persists in Apache children, leading to buggy behaviour.
I wrote up a test case:
app.psgi
use 5.010;
use strictures;
use Foo::Bar qw(run);
my $app = sub {
my ($env) = #_;
run($env);
};
Foo/Bar.pm
package Foo::Bar;
use 5.010;
use strictures;
use Encode qw(encode);
use File::Basename qw(basename);
use Locale::TextDomain __PACKAGE__, '/tmp/Foo-Bar/share/locale';
use Locale::Util qw(web_set_locale);
use Plack::Request qw();
use Sub::Exporter -setup => { exports => [ 'run' ] };
our $DEFAULT_LANGUAGE = 'en'; # untranslated source strings
sub run {
my ($env) = #_;
my $req = Plack::Request->new($env);
web_set_locale($env->{HTTP_ACCEPT_LANGUAGE}, undef, undef, [
map { basename $_ } grep { -d } glob '/tmp/Foo-Bar/share/locale/*'
]); # XXX here
return $req
->new_response(
200,
['Content-Type' => 'text/plain; charset=UTF-8'],
[encode('UTF-8', __ 'Hello, world!')],
)->finalize;
}
The app runs as a PerlResponseHandler. When the user request a language that cannot be set, the call fails silently and the language that was used last successfully is still enabled.
The trick to fix this is to always set to a language that exists with a fallback mechanism. At the spot marked XXX, add the code or web_set_locale($DEFAULT_LANGUAGE), so that despite using a global setting, the behaviour cannot persist because we guarantee that it is set/changed once per request.
Edit 2: Further testing reveals that it's not thread-safe, sorry. Use the prefork MPM only which isolates requests as processes; however worker and event are affected because they are thread-based.

Configuring form_path in Catalyst::Controller::Formbuilder

Using the Catalyst::Controller::FormBuilder module to handle forms in a Catalyst application.
The documentation says you can set the form_path like this:
form_path => File::Spec->catfile( $c->config->{home}, 'root', 'forms' ),
But the call to config() in my application is at the top level of the base module. Therefore, $c is undefined. So I can't call $c->config->{home}.
What is the proper way to configure form_path please?
You should be able to access configuration values that have already been set from your application's main module using the __PACKAGE__->config hash. Example: __PACKAGE__->config->{home} or __PACKAGE__->config->{'Controller::FormBuilder'}->{form_path}.
If you're trying to set the FormBuilder configuration in your applications main module, you should be able to use the code provided in the documentation and just replace $c->config->{home} with __PACKAGE__->config->{home}. I think they might have even made a mistake by not doing it this way, but I'm not sure.