Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
PerlMonks Disillusionment

by Cody Pendant (Prior)
on Jun 05, 2003 at 00:11 UTC ( [id://263197]=perlmeditation: print w/replies, xml ) Need Help??

I spent a couple of hours last night trying to "respectablize" some old Perl scripts.

I have learned much at the feet of my fellow PerlMonks, and now feel rather embarassed at: scripts which can't be run under strict; clunky HTML solutions; non-module form parsing routines and so on.

So last night I went over a script which was a mishmash of HERE documents and print statements and reformed it using

It really did take a couple of hours, even for a simple script, because I was unfamiliar with the module. Apart from anything else, it seems inconsistent. I can pretty much make any HTML tag I want by just using it (BLOCKQUOTE isn't mentioned in the documentation, but can simply be used like all other tags), but form fields have names that don't relate directly to their HTML names at all, like "textfield".

Anyway, I went through the rather monkish procedure of editing code that already works just fine and removing from it any non-CGI code for rendering HTML/dealing with parameters. Mostly I was just wanting to use the "stickiness" of form fields, because I wanted a form which would re-display with values intact if one of them was missing.

So what does that leave me with:

  • A really complex data structure -- just for a form laid out in a table, I've got a AoHoAoA kind of thing six or seven levels deep. I can almost hear PerlTidy sighing when I ask it to lay it out for me.
  • I've got ugly-ass code -- even with CGI::Pretty. All tags are in uppercase. Not all tags are indented or lines broken up.
  • I've got oldfashioned code, or at least I can't figure out how to make CGI give me HTML 4.0 strict code. I'm guessing the version on my hosting company is a bit out of date because the latest one on my HD Perl installation gives me XHTML unless I over-ride it.
  • I've got code about the same length as the HTML would have been if I'd written it myself.
    isn't any different to <tag attribute="value"> in size after all.
  • I've got the stickiness I wanted, without having to come up with my own cludgy solutions, but I notice that when I do my end_form, is adding it's own hidden values to my form anyway in order to achieve the stickiness.
  • I've got stickiness I didn't want -- because one of my parameters has the same name as one of my form fields, it was overriding the value, and I had to search the documentation to find out how to force it to accept a value. Simply putting a different value doesn't work. You have to say that that value isn't the value but the "default", and you have to put override=>1 to force it.
  • I've got that large complex data structure partly because, I now discover, doesn't like me to print the start of my table, print the rows and print the end of my table. It doesn't have an end_table call unless I specifically request one in the "use" line. So it's "easier" for me to nest every one of my rows, with their nested tds, inside the table's brackets. If I don't do that, I could just write my own custom HTML, like print $q->'</table>' -- but that's just insane. I'm using so I don't have to write my oen HTML any more.

I'm not saying I hate, but I'm a bit disillusioned. Am I missing something? What have I gained for my two hours? Do I just need another coffee? And how can I get to produce valid HTML 4.01 Strict or XHTML 1.0 or whatever in future?

“Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”

Replies are listed 'Best First'.
Re: Disillusionment
by edoc (Chaplain) on Jun 05, 2003 at 00:30 UTC

    hmm.. probably not what you want to hear, but here goes..

    I don't actually understand why anyone would want to use the html generating tags in CGI.. never have, so I've never used it. 5 minutes with it this morning trying to solve a problem for another monk simply reminded me how uncomfortable I find it (they were already using it). I think I diagnosed the problem correctly and your post has just confirmed that for me.. You can't create the start of a table, then add the rows, then close off the table without doing it in one go.. that's just not right.. and definitely not modular.. all too hard for me.

    templates, templates, templates.. that's the way to go as far as I'm concerned. Template::Toolkit Rocks! or maybe HTML::Template. Write your app to collect together all the data it needs to fill in the blanks in a template, then pass the data to the template module. In the template you can use whatever form of html you want and just insert variable tags, loops and things where you want the data put in.

    Definitely worth the effort to get your head and app around this kinda thing.. IMHO..



      You can't create the start of a table, then add the rows, then close off the table without doing it in one go.. that's just not right.. and definitely not modular.. all too hard for me.<
      That's simply not true.
      use CGI qw/ *table :standard /; print start_table({-border=>1}); print Tr( td( [qw, a s d f ,])); print end_table; __END__ <table border="1"><tr><td>a</td> <td>s</td> <td>d</td> <td>f</td></tr> +</table>

      MJD says you can't just make shit up and expect the computer to know what you mean, retardo!
      I run a Win32 PPM repository for perl 5.6x+5.8x. I take requests.
      ** The Third rule of perl club is a statement of fact: pod is sexy.

        Just to add to what the other monks have said on the subject, this is from the docs:

        There will be some times when you want to produce the start and end tags yourself. In this case, you can use the form start_tag_name and end_tag_name, as in:

        print start_h1,'Level 1 Header',end_h1;

        With a few exceptions (described below), start_tag_name and end_tag_name functions are not generated automatically when you use CGI. However, you can specify the tags you want to generate start/end functions for by putting an asterisk in front of their name, or, alternatively, requesting either "start_tag_name" or "end_tag_name" in the import list. Example:

        use CGI qw/:standard *table start_ul/;

        In this example, the following functions are generated in addition to the standard ones:

        start_table() (generates a <TABLE> tag) end_table() (generates a </TABLE> tag) start_ul() (generates a <UL> tag) end_ul() (generates a </UL> tag)

        I haven't tested it with every HTML tag, mind... ;-)

        Update: here's a link to the relevant part of the docs (as suggested by PodMaster).

        Cool! ta!++ that solves b310's problem! 8)



      Just wanted to add my vote for templates.

      I don't think I've ever used the's HTML generation methods either, and I do find that Template::Toolkit indeed rocks.

      When talking about perl webapps, I usually also like to plug Class::DBI just because it plays so nicely with the Toolkit and makes form persistance a snap.

      Though I may agree about the ugliness of CGI's html generating subroutines, please have your facts straight. You can too start the table, add the content, then close it. I'll just take that as a misunderstanding on your part ;)

      #!/usr/bin/perl -w use strict; use CGI qw/:standard *table/; print header(), start_html('foo bar qux'), start_table(); print Tr( td('foo'), td('bar'), td('qux') ); print end_table(), end_html();

      If the above content is missing any vital points or you feel that any of the information is misleading, incorrect or irrelevant, please feel free to downvote the post. At the same time, please reply to this node or /msg me to inform me as to what is wrong with the post, so that I may update the node to the best of my ability.

      When I used to write web stuff, I frequently used's HTML-generators while prototyping my code. Since I used anyways, I could whip up something that looked butt ugly but worked really fast, as proof of concept or as a starting point. Then, when the basic functionality was there, I'd create (or get from a designer) a HTML template and used HTML::Template instead.

      I've heard lots of good about TT as well, but haven't used it myself. I like HTML::Template because it only does a few things, does it well and fast, and that helps keeping the logic in the code as much as possible.

      I sincerely agree that one should not use to generate the HTML for anything "real" though. That is still mixing markup and code in a bad way, though for some small things, a foreach around some $q->li's are definitely a fast way to get the job done. YAGNI sometimes applies to templates as well. :)

      You have moved into a dark place.
      It is pitch black. You are likely to be eaten by a grue.
Re: Disillusionment
by tilly (Archbishop) on Jun 05, 2003 at 04:38 UTC
    Rule #1 is to always think for yourself. Of course if other people disagree with what you think, it is good to listen and try to understand why their point of view might make sense. Experiment. Be willing to change your mind. But in the end, train your judgement as best you can, judge carefully, and then think about how it worked out so that you can judge more intelligently next time.

    In this case, your criticisms seem right on target. When people say use CGI or die;, they are talking about the fact that you don't want to roll your own parsing logic, because it is extremely likely to be broken. They aren't talking about the autogeneration, and most knowledgable people don't use that. Instead they are likely to suggest some kind of templating solution.

    Now to train your judgement, here is my thinking on templating, for more detailed background read this white paper on Template::Toolkit, and for a list of options to make a choice from read Choosing a Templating System by our own perrin.

      >Experiment. Be willing to change your mind.

      I think I did both those last night.

      I put in the work, realised I didn't like the code, now I can move on with my life.

      No seriously, now I have to get to grips with the fact that I've been rolling my own Templating System in another project. Sigh... off to read up on Toolkit.

      “Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
      M-J D
        No seriously, now I have to get to grips with the fact that I've been rolling my own Templating System in another project. Sigh... off to read up on Toolkit.

        Been there, done that.. what I really kick myself for is that I looked at TT, decided it was more than I needed and set out on my own.. I probably learned a few life lessons that I won't regret, but now I've humbly but happily returned to drink from the oasis that is TT.. 8)



Re: Disillusionment
by sauoq (Abbot) on Jun 05, 2003 at 00:33 UTC

    I never use CGI for generating output. Period.

    IMHO, it was a broken idea to add that functionality to in the first place. I think the module should do what is necessary to implement the specification. Nothing less, maybe a bit more, but not all sorts of junk for formatting HTML.

    "My two cents aren't worth a dime.";
Re: Disillusionment
by moxliukas (Curate) on Jun 05, 2003 at 00:33 UTC

    OK, I am coming from a PHP background (I can already hear mumblings around ;)) and find (or any other HTML done in a similar way for that matter) unintuitive and ugly. Whenever I am doing some web-work I use templates for HTML. That way you can have a very clean hand-crafted HTML and no ugly print statements. Most of the time only a few things on a page are generated dynamicly, so it is very easy to use things like this:

    use HTML::Template; # ... first send header, update counters, whatever ... # then comes HTML rendering phase: my $template = HTML::Template->new('template.html'); # set some parameter $template->param('counter', $counter); # print it all print $template->output();

    It really separates all the business logic from display, so you can concentrate on interface with databse or whatever while leaving the HTML for a template. Well, at least I like it ;)

(jeffa) Re: Disillusionment
by jeffa (Bishop) on Jun 05, 2003 at 15:42 UTC
    I must be the only one on this thread who gets a big kick out of using HTML generation methods. :D Having said that, i never use them for 'production code', unless it's just a tiny little 'add on' or something.

    So, why use for generating HTML? For small test examples, especially for this site.

    Is there any real gain in using for generating HTML? Yes. These methods come to mind: header(), param(), query(), upload(), cookie(), and start_html(). [oops, only two of those actually generate HTML, 3 if you include cookie ... i changed my message mid sentance :/ ] The rest are supplemental, IMHO (and i may have missed a few more 'important' ones).

    So, let's cover those questions you have now:
    • "A really complex data structure -- just for a form laid out in a table..." If you are saying what i think you are saying, this is where a templating solution shines - your form is just HTML with a few special tags. However, if you are not comfortable with complex data structure, you are going to be hurting when you start using templating solutions. Nesting loops can be tricky, but never fear ... you can always ask us for help when that time comes.
    • "I've got ugly-ass code -- even with CGI::Pretty..." Sounds like you are not using the most recent version of (2.93). Older versions of CGI did output HTML tags in all uppercase, but newer versions use all lower case tags. You can specify which HTML standard you want when you use CGI:
      use CGI(:html2);
      Other function sets are html3 and html4. There are more ... you'll have to experiment to see what they do. ;)
    • "I've got code about the same length as the HTML would have been if I'd written it myself." It's probably more code. The benefit of using to generate HTML is not to reduce code, but instead to hopefully have more readable code - that is, you shouldn't have to strain your eyes too hard trying to decipher what is Perl and what is HTML. But, the real winners here are templating solutions.
    • "I've got the stickiness I wanted ... I've got stickiness I didn't want." Yep. Welcome to CGI Form User Error Handling 101. There is no real easy way to do this, because everybody has different needs. I haven't tried HTML::FillInForm yet, but i'll bet it's nice and worth the time you spend to learn how to use it.
    • " doesn't like me to print the start of my table, print the rows and print the end of my table..." Others have already addressed this point, but i wanted to stress that table and list generation in is about as hard as it gets. You have to master map first:
      print table( {border => 1}, map Tr( th({align=>'right'},$_),td[param($_)] ), param() ),
      That code will print whatever params were submitted. But ... if you think that's tough, know that when you use a templating solution, you have to massage a data structure into another data structure suitable for the template. Using or a templating tool does not alleviate the need for you to learn advanced Perl code.
    • "I'm using so I don't have to write my own HTML any more." Nope, that's the wrong reason to use only changes the method of writing that HTML - you will always have to write your own HTML, unless you have someone else to do it. And that's why templating solutions are such a boon - they allow you, the programmer, to concentrate on the code, while allowing another person, (most likely a non-coder, HTML designer) to write the HTML. Again, using to gerenerate HTML is better for small examples / testing.
    • "What have I gained for my two hours?" Well, hopefully you learned something. Besides two hours is nothing, i've invested well over 20 hours in reading the CGI docs, fumbling with getting those darned maps just right, etc. Even though i use HTML::Template religiously, i still am glad that i invested that time. Remember, i actually like gererating HTML with -- it can be fun if you are twisted enough. ;)
    • "Do I just need another coffee?" If you aren't jittery enough yet, then yes ... grab another cup. ;)

    UPDATE: (Tue Jun 17 2003)
    Here is an example of using to generate HTML that makes sense, but it is an isolated example: (jeffa) Re: automagic-HTML regex

    Hope this helps,


    (the triplet paradiddle with high-hat)
Re: Disillusionment
by perrin (Chancellor) on Jun 05, 2003 at 00:35 UTC
    I think you'll find that the only thing about the HTML generation that most people like is the sticky widgets. You can easilly just use those and HEREDOC or template the rest. A much better solution in my eyes.
      >I think you'll find that the only thing about the HTML generation that most people like is the sticky widgets.

      Well exactly. That's what I wanted. Forms which can report an error/missing field then pump out the field contents which are OK.

      Is there another solution for this kind of thing? It's easy enough to do field contents but checkboxes, radiobuttons and select lists always require some kludge. if(<param exists that matches the name>){print "selected" } or whatever

      Kludges are what I do best, but I'm trying to get away from that...

      “Every bit of code is either naturally related to the problem at hand, or else it's an accidental side effect of the fact that you happened to solve the problem using a digital computer.”
      M-J D

        Check out HTML::FillInForm for populating form fields without needing to use to generate the HTML. I use it very successfully with CGI::Application, HTML::Template and Data::FormValidator. A typical C::A runmode for me looks like the following:

        sub edit_form { my $self = shift; my $q = $self->query(); # load the HTML::Template object my $template = $self->load_tmpl('FormTemplate.tmpl') || die "Can't find template FormTemplate.tmpl"; if ($q->param('first_name')) { # The user has filled in the form, so we # check the values and update the database # if everything is OK my %form_data = $q->Vars; my %form_profile = ( required => [qw(first_name last_name)], optional => [qw(email)], constraints => { email => 'email', }, filters => [ 'trim' ], ); my ($valid, $missing, $invalid, $unknown) = Data::FormValidator->v +alidate(\%form_data, \%form_profile); if (@$missing || @$invalid) { # There were problems with the form my @errors; push @errors, map { { 'missing_'.$_ => 1 } } @$missing; push @errors, map { { 'invalid_'.$_ => 1 } } @$invalid; $template->param(errors => \@errors); } else { # Everything looks good, so update the database eval { ### Do some database stuff here My::DB::User->create($valid); }; if ($@) { $template->param(errors => [ { failed_on_update => 1} ]); } else { # The database was updated successfully # so display a success page $template->param(database_updated => 1); } } } return $self->fillinform(\$template->output()); } sub fillinform { my $self = shift; my $html = shift; # ref to string of HTML my $fif = new HTML::FillInForm; return $fif->fill(scalarref => $html, fobject => $self->query); }

        And the template would have the following fields in it:

        <TMPL_INCLUDE NAME="../header.tmpl"> <h2>Registration Form</h2> <TMPL_IF errors> <h4 class="error_hdr">There was a problem processing your request</h4> <ul> <TMPL_LOOP errors> <li class="error"> <TMPL_IF invalid_email>The email address you entered does not look l +ike a valid email address</TMPL_IF> <TMPL_IF missing_firstname>You are required to provide your first na +me</TMPL_IF> <TMPL_IF missing_lastname>You are required to provide your last name +</TMPL_IF> <TMPL_IF failed_on_create>Error: Failed to create the new user in t +he database.</TMPL_IF> </li> </TMPL_LOOP> </ul> </TMPL_IF> First Name: <input type="text" name="first_name" size="32" maxlength=" +72"><br /> Last Name: <input type="text" name="last_name" size="32" maxlength=" +72"><br /> email: <input type="text" name="email" size="32" maxlength=" +255"><br /> <input type="submit" name="submit" value="Submit"><input type="reset" + name="reset" value="Reset"><br /> <TMPL_INCLUDE NAME="../footer.tmpl">

        CGI::Application keeps my code structured nicely, HTML::Template allows me to separate design from code (including the content of error messages intended for the end-user), Data::FormValidator does most of the input field checking, and HTML::FillInForm gives me sticky form fields. And I usually top it off with Class::DBI for the database interface.

        This turned out to be a bit longer than I intended, but hopefully someone finds it useful (or someone points out where I can improve things :).


        Well, I dunno about it's kludge-worthiness, but for checkboxes, radio buttons, and selects for a quick form I had to do recently I went with extra template tags in the respective tags then in my script I set the appropriate values to 'selected' or 'checked' so my template looks like:

        <p>[% error.message %]</p> <input type="checkbox" name="field1" value="1"[% checked.field1 %]> <select name="field2"> <option value="val1"[% selected.val1 %]>Value 1</option> <option value="val2"[% selected.val2 %]>Value 2</option> </select> <input type="radio" name="field3" value="a"[% checked.field3_a %]> <input type="radio" name="field3" value="b"[% checked.field3_b %]> <input type="radio" name="field4" value="a"[% checked.field4_a %]> <input type="radio" name="field4" value="b"[% checked.field4_b %]>

        And after processing:

        <p>Please select a value for Field 4</p> <input type="checkbox" name="field1" value="1" checked> <select name="field2"> <option value="val1">Value 1</option> <option value="val2" selected>Value 2</option> </select> <input type="radio" name="field3" value="a" checked> <input type="radio" name="field3" value="b"> <input type="radio" name="field4" value="a"> <input type="radio" name="field4" value="b">

        Of course you're still going to have to process the input and set the appropriate values, but for that I created a config type hash containing the names of the different inputs grouped by type. For each I also specified a human readable label. Then I created a 'required' hash of input fields.

        The script then checks that any fields in the 'required' hash have been specified and if not, uses their labels to create the error message.

        Then we go through the field hashes to set appropriate values for 'selected' and 'checked'.



Re: Disillusionment
by Aragorn (Curate) on Jun 05, 2003 at 14:40 UTC
    I don't use the HTML-generation functionality of the module for a simple reason: Separation of code and presentation of data. I try to design (web-)applications with the possibility that sometime in the future I may have to change the user-interface from a browser to, say, a Tk interface. Using a templating module (like Template Toolkit) which you only have to feed a reference to your data makes it much easier to replace the user-interface than grovelling through scripts and modules which have (possibly bad) HTML in them (and using the CGI HTML capabilities is the same as having plain HTML in your code, IMO).

    So, apart from that it's probably much easier to maintain an application which uses a templating system, it also makes the design better by separating out the data and the means to display that data.


      I too do not use nor Template::Toolkit, for seperation of code and presentation of data..

      I like to do things like:
      my @select_box_list; foreach (@some_array_containing_a_hashref){ push(@select_box_list,$html_obj->HTML_Select_List_Option(name=>" +select_your_state",value=$_->{whatever_value},text=>$_->{whatever_des +cription}); }
      That sort of thing.. wherein all html formatting subroutines are in a seperate module called or somesuch. For some reason I can do this quicker than I can with Template::Toolkit or Reinventing the wheel or not.

      One4k4 - (
Re: Disillusionment
by Trimbach (Curate) on Jun 05, 2003 at 18:40 UTC
    When using the HTML generation in remember it's not an all or nothing thing. Yes, if you find yourself doing a lot of HERE docs or a whole bunch of print p(b("Hello")." how are you?"); stuff you really really should be using a templating system. I'll even go as far as saying all static output should be templated. But makes it stupid simple to do some things that to me are awkward in a template... combining's generation functions with a templating system gets you the best of both worlds.

    Suppose you've got a batch of data in an array that you want to display in an HTML list. To display it in a template you've got to create a suitable loop in your template, then assign your array data to a specially named loop variable that gets passed to your template engine. That's not that hard, but it's so much more easier for me to just my $list_var = ul(li(\@array)); and then plug $list_var into the appropriate part of my template.

    Ditto with tables. If my data lends itself to the special array handling in it's so much easier to construct the table in code ($table = table(Tr(td(\@AoA)));) and plug the result into my template. This is ESPECIALLY true in very dynamic tables where the number of columns is not known ahead of time.

    So, as always, use the parts of that make sense where they make sense, and leave the rest of the generation routines for when you want that quickie one-off and don't want to mess with a template.

    Gary Blackburn
    Trained Killer

Re: Disillusionment
by CountZero (Bishop) on Jun 05, 2003 at 21:31 UTC

    Indeed, has grown beyond the CGI-stuff it does so well and I am not sure that this evolution is all for the best.

    I had started a dynamic web-site with Perl-scripts which output the data as pure XML.

    XSLT-stylesheets transform this XML (originally client-side, but now server-side through AxKit and Sablotron) into HTML.

    I obtained thus a clean division between logic (the Perl scripts) and presentation (the XSLT and CSS stylesheets).However, then The Powers That Be wanted some form handling capacity into the website and They wanted it yesterday.

    Although it is not impossible to code your forms into XML and have them transformed into regular HTML through XSLT, this is not something you would like to do in a hurry.

    So I called to the rescue.

    The basic CGI-stuff works like a charm, but then I got tempted to use the HTML-generating stuff as well. Finally, I was able to combine the HTML-stuff of into XML and XSLT, but it is *ugly* and if it was not for lack of time, I would go back and rewrite the XSLT-stylesheets to handle all forms-related things and forget all about the HTML-generating functions.

    What we really need is a Pure::CGI::Lite module; nothing more, nothing less.


    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: Disillusionment
by trs80 (Priest) on Jun 06, 2003 at 03:18 UTC
    It's OK to not like CGI, as long as you respect it. is one of the reasons for Perl's popularity on the web and allowed for building web pages without having to know HTML inside and out when you needed to collect data from the visitor. It was made for Perl programmers not web designers. It's hard to remember life without WYSIWYG HTML editors and an HTML specification that didn't support tables or images, but its all true. It is great to see it has been extended as far as it has, but for the most part the state of art is beyond it.
Re: Disillusionment
by jonadab (Parson) on Jun 08, 2003 at 21:03 UTC

    If by stickiness you mean what I think you mean, you don't need for that. You presumably are already passing the form values selected by the user back to your script as input (either in a QUERY_STRING or via POST) because otherwise there'd be no point in having those fields in the first place, except maybe decor. So, when you print out your form elements (however you do it), just prefill them with the existing input. You can use ||= to set defaults, to cover the case where the user is getting the form for the first time. (If " " or zero is a legit value, you can use a defined ... or structure to do the same thing, e.g., defined $input{foo} or $input{foo}="blah";.)

    {my$c;$ x=sub{++$c}}map{$ \.=$_->()}map{my$a=$_->[1]; sub{$a++ }}sort{_($a->[0 ])<=>_( $b->[0])}map{my@x=(& $x( ),$ _) ;\ @x} split //, "rPcr t lhuJnhea o";print;sub _{ord(shift)*($=-++$^H)%(42-ord("\r"))};
Re: Disillusionment
by shotgunefx (Parson) on Jun 08, 2003 at 23:16 UTC
    In general templates are the way to go. I'll use CGI shortcuts for small scripts that may shift around a lot as it makes installing trivial. One place to look for problems. I've also used them on a few projects for validating data. Meaning I subclassed CGI to record what fields it outputs so I know what to expect automatically on the next submission or to add other hooks and behaviours.

    That being said, I don't actually type them. I have a HTML 2 CGI script based on a work by merlyn that takes any html page and translates it to syntax. That way changes are nice and easy.

    Use whatever makes sense for the job IMHO.


    "To be civilized is to deny one's nature."

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://263197]
Approved by broquaint
Front-paged by virtualsue
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (4)
As of 2024-06-18 21:17 GMT
Find Nodes?
    Voting Booth?

    No recent polls found

    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.