Bod's user image
User since: Nov 15, 2020 at 00:48 UTC (16 weeks ago)
Last here: Mar 06, 2021 at 23:00 UTC (27 minutes ago)
Experience: 1557
Level: Hermit (10)
Writeups: 324
Location:Coventry, UK
User's localtime: Mar 06, 2021 at 23:27 UTC
Scratchpad: View
For this user:Search nodes

Long time amateur coder since growing up with a ZX Spectrum and BBC Micro...

Introduced to Perl in the early 1990's which quickly became the language of choice. Built many websites and backend applications using Perl including the sites for my property business:
Lets Delight - company site
Lets Stay - booking site
Also a few simple TK based desktop apps to speed things up.

Guilty of only learning what I need to get the job done - a recipe for propagating bad practice and difficult to maintain code...difficult for me so good luck to anyone else!

Now (Nov 2020) decided to improve my coding skills although I'm not really sure what "improve" means in this context. It seems Perl and best practice have come along way since I last checked in and my programming is approach is stuck in the last decade.

Onwards and upwards...

Find me on LinkedIn

Posts by Bod
Preparing data for Template in Seekers of Perl Wisdom
1 direct reply — Read more / Contribute
by Bod
on Dec 30, 2020 at 17:38

    I have been following this tutorial for creating dynamic content with Template and trying to apply this to my own needs. But I've hit a problem in preparing the data for the template.

    Template file

    [% SET menuframe = ' menuselect' %] [% PROCESS %] [% FOREACH frame IN frames %] <hr> <p><b>[% %]</b><br> [% frame.colour %]</p> [% END %]
    This does not generate any output for anything within the [% FOREACH ... %] block

    Here is the code that extracts some data from the database and prepares it for passing to the Template display code

    #!/usr/bin/perl use Site::Common; use Site::HTML; use strict; my $site = Site::Common->new; my $html = Site::HTML->new; my $dbh = $site->db; if ($data{'command'} eq 'frames') { my $vars = { 'frames' => \&list_frames, }; warn "Displaying template"; # This gets called $html->display("admin_frames", $vars); } else { $html->display("admin_pictures"); } sub list_frames { warn "Building list of frames"; # This is not called my @frames; my $query = $dbh->prepare("SELECT idFrame, name, colour FROM Frame +"); $query->execute(); while (my ($id, $name, $colour) = $query->fetchrow_array()) { my $frame = { 'id' => $id, 'name' => $name, 'colour' => $colour, }; push @frames, $frame; } return \@frames; }
    There are a couple of warn statements in there so I can see something of what the code is doing.

    Here is the relevant part of Site::HTML...

    package Site::HTML; use Template; use Site::Variables; use strict; my $template = Template->new(INCLUDE_PATH => $Site::Variables::templat +e_path); sub display { my $self = shift; my $file = shift; my %vars = @_; $template->process("$", \%vars); } 1;

    I'm not sure when list_frames is supposed to get called but I'm guessing at the time the hash reference $vars is generated.

How long to PAUSE? in Seekers of Perl Wisdom
2 direct replies — Read more / Contribute
by Bod
on Dec 30, 2020 at 07:08

    How long does a PAUSE account generally take to be approved?
    I submitted an application at the start of the month and, other than the automated acknowledgement email, I've heard nothing this normal?

Debugging CPAN problem in Seekers of Perl Wisdom
6 direct replies — Read more / Contribute
by Bod
on Dec 22, 2020 at 13:08

    The Raspberry Pi based Curtain Controller project for my blind uncle is complete and almost ready to go to its new home. I have two RPi's - the one that is boxed up with the relay controller and one that is setup as a test environment so I can deal with any future problems or extra features.

    In setting up the controller unit I obviously made a mess somewhere as nothing would install from CPAN whereas this was not a problem on the development unit. The development unit also has an HTTP server which is accessible via an ngrok tunnel ready for the unit at be Alexa enabled once I get Device Discovery working. This server needs adding to the controler and requires HTTP::Server::Simple so CPAN is necessary.

    The development unit used an OS image with the network credentials added. This same image has now been used for the controller unit. So everything should be identical except that one has header pins soldered in and the other one doesn't!
    Once the OS had installed and an SSH connection made I have changed the password then updated with:

    sudo apt-get update sudo apt-get upgrade cpan install CPAN
    With the development unit, running CPAN and typing install HTTP::Server::Simple installed lots and lots of dependencies taking nearly 4 hours but eventually installed.

    On the unit that is to be shipped imminently, CPAN gets 'Killed' - this is the last block of what CPAN prints out:

    Configuring E/ET/ETHER/Try-Tiny-0.30.tar.gz with Makefile.PL Checking if your kit is complete... Looks good Generating a Unix-style Makefile Writing Makefile for Try::Tiny Writing MYMETA.yml and MYMETA.json ETHER/Try-Tiny-0.30.tar.gz /usr/bin/perl Makefile.PL INSTALLDIRS=site -- OK Running make for E/ET/ETHER/Try-Tiny-0.30.tar.gz cp lib/Try/ blib/lib/Try/ Manifying 1 pod document ETHER/Try-Tiny-0.30.tar.gz /usr/bin/make -- OK The current configuration of allow_installing_outdated_dists is 'ask/y +es', but for this option we would need 'CPAN::DistnameInfo' installed +. Please install 'CPAN::DistnameInfo' as soon as possible. As long as + we are not equipped with 'CPAN::DistnameInfo' this option does not t +ake effect Running make test for ETHER/Try-Tiny-0.30.tar.gz PERL_DL_NONLAZY=1 "/usr/bin/perl" "-MExtUtils::Command::MM" "-MTest::H +arness" "-e" "undef *Test::Harness::Switches; test_harness(0, 'blib/l +ib', 'blib/arch')" t/*.t t/00-report-prereqs.t .......... # # Versions for all modules listed in MYMETA.json (including optional o +nes): # # === Configure Requires === # # Module Want Have # ------------------- ---- ---- # ExtUtils::MakeMaker any 7.34 # # === Configure Suggests === # # Module Want Have # -------- ------- ------- # JSON::PP 2.27300 2.97001 # # === Build Requires === # # Module Want Have # ------------------- ---- ---- # ExtUtils::MakeMaker any 7.34 # # === Test Requires === # # Module Want Have # ------------------- ---- -------- # ExtUtils::MakeMaker any 7.34 # File::Spec any 3.74 # Test::More any 1.302133 # if any 0.0608 # # === Test Recommends === # # Module Want Have # ---------- -------- -------- # CPAN::Meta 2.120900 2.150010 # # === Test Suggests === # # Module Want Have # ------------------------ ----- ------- # CPAN::Meta::Check 0.011 missing # CPAN::Meta::Requirements any 2.140 # Capture::Tiny 0.12 missing # # === Runtime Requires === # # Module Want Have # -------- ---- ---- # Carp any 1.50 # Exporter 5.57 5.73 # constant any 1.33 # strict any 1.11 # warnings any 1.42 # # === Runtime Suggests === # # Module Want Have # --------- ---- ------- # Sub::Name 0.08 missing # Sub::Util any 1.50 # # === Other Modules === # # Module Have # ------------- ------- # JSON::PP 2.97001 # Pod::Coverage missing # Sub::Name missing # YAML missing # autodie 2.29 # t/00-report-prereqs.t .......... ok t/basic.t ...................... ok t/context.t .................... ok t/erroneous_usage.t ............ ok t/finally.t .................... ok t/given_when.t ................. skipped: Tests skipped on perl 5.27.7 ++, pending resolution of smartmatch changes t/global_destruction_forked.t .. ok t/global_destruction_load.t .... skipped: Capture::Tiny 0.12 required t/named.t ...................... ok t/when.t ....................... skipped: Tests skipped on perl 5.27.7 ++, pending resolution of smartmatch changes t/zzz-check-breaks.t ........... ok All tests successful. Files=11, Tests=97, 21 wallclock secs ( 0.78 usr 0.10 sys + 17.95 cus +r 1.02 csys = 19.85 CPU) Result: PASS Killed pi@eric:~ $
    I've tried using CPAN to install just Try::Tiny as this seems to be where things are failing. When I do this I get almost the same output with different wallclock values reported and Lockfile removed. displayed on the line before Killed and several minutes between them.

    Searching for an answer suggests that this is the OS killing CPAN and the most probable cause is lack of memory. The development unit has a 32Gb SD card whereas the controller has 16Gb. But, there is plenty of space on the card:

    pi@eric:~ $ df Filesystem 1K-blocks Used Available Use% Mounted on /dev/root 14989480 3552696 10774908 25% / devtmpfs 188088 0 188088 0% /dev tmpfs 221112 0 221112 0% /dev/shm tmpfs 221112 3212 217900 2% /run tmpfs 5120 0 5120 0% /run/lock tmpfs 221112 0 221112 0% /sys/fs/cgroup /dev/mmcblk0p1 258095 55052 203043 22% /boot tmpfs 44220 4 44216 1% /run/user/1000

    Two ostensibly identical units seem to be working differently.
    Can you suggest anything I can try to debug this problem?

Here documents in blocks in Seekers of Perl Wisdom
10 direct replies — Read more / Contribute
by Bod
on Dec 19, 2020 at 11:25

    When I created an account here some five weeks ago, little did I realise just how much varied learning I would receive in such a short I am asking for advice on an issue that has had me scratching my head many times over the years. How best to lay out code when quite a bit of text output is required, such as when dynamically creating a webpage, inside an indented block.

    In the main body of the code I usually use an interpolating heredoc with any runtime variations defined in variables ahead of printing it all out.

    my $login_text = $user_number?'logout':'login'; print<<"END_HTML"; <div> ...part of webpage... <input type="button" name="log" value="$login_text" onClick="doSomethi +ng();"> ...more of webpage... </div> END_HTML
    That works and looks fine for a block of procedural code but I run into difficulties when I want to put something similar into an indented block for any reason. It could be a subroutine that is called to display a largely different page based on the query string or a significant block of content that is only shown under some conditions.
    if (isAdmin($user_number)) { print ...some extra content... }
    Heredocs don't work so well in these circumstances. I am using Perl 5.16 so don't get to use the print<<~"END_HTML"; syntax introduced in Perl 5.26.

    This leaves a few option.
    The one that most of my legacy code has is to simply put every line in a separate print statement

    if (isAdmin($user_number)) { print "<table>\n"; print "<tr>\n"; print "<td class=\"someclass\" style=\"text-align:center\">Some Co +ntent</td>\n"; print "</tr><tr>\n<td class=\"someClass\">Restricted</td>\n" if $u +ser_number == 20; print "</tr>\n"; print "</table>"; }
    Not very pretty and quite difficult to follow as it becomes more involved, especially as more and more HTML gets added over time. So a slight improvement that I used for a short time is with qq to save having to escape the quotation marks.
    print qq[<td class="someclass" style="text-align:center">Some Conte +nt</td>\n]; print qq[</tr><tr>\n<td class="someClass">Restricted</td>\n] if $us +er_number == 20;
    Slightly better - but still not very nice...

    I have tried having a subroutine to strip out leading spaces but this has the disadvantage of always stripping leading spaces even when they are wanted! In this format it also strips out blank lines although this is not too tricky to solve.

    #!/usr/bin/perl use strict; print "Content-type: text/plain\n\n"; print "Test\n\n"; sub indent { my $text = shift; $text =~ s/^\s+//gm; return $text; } if (1) { print indent(<<"END_TEXT"); Here is some test text with plenty of space at the start END_TEXT } exit 0;
    This still requires END_TEXT to be written without an indent.

    Many times I have searched for a solution and found several references to the issue but nothing offering a 'proper' solution. The topic of indentation in some form or another crops up periodically in all sorts of forms including Mandatory indenting which was interesting despite not being directly relevant.

    Other than upgrading to Perl 5.26 or later, is there an elegant solution to laying out code to print a lot of text in an indented block?

Preventing multiple instances in Seekers of Perl Wisdom
10 direct replies — Read more / Contribute
by Bod
on Dec 16, 2020 at 16:36

    Because the Raspberry Pi does not have an onboard time clock (thanks Marshall for pointing this out), the first thing my script does is to check that it has a valid time that has been obtained from the network. It does this by comparing the current year as given by localtime to 2020. I've written some code which is probably unnecessary in this application but I'm asking about this as a wider learning point.

    How can I prevent more than one instance of a script from running?
    I have at times used a lockfile but there is always the risk that the power could go off or some other catastrophe could occur between the lockfile being created and being removed. So is there a better way to do it?

    This is the code to check is the RPi has a valid date and to wait ever increasing intervals before checking again and eventually to reboot as a possible cause after this amount of time is that the WiFi 'card' hasn't properly initialised.

    my @time = localtime; $control->log("Starting Curtain Controller") if $DEBUG > 1; # Check Pi has valid time my $wait = 1; while ($time[5] + 1900 < 2020) { if ($wait > 30) { $control->log("Still no valid time. Aborting!"); sleep(2); system("sudo reboot"); exit 0; } my $plural = $wait == 1?'':'s'; $control->log("No valid time. Waiting $wait minute$plural"); print "Waiting for $wait minutes$plural\n" if $DEBUG; sleep ($wait * 60); $wait = int(0.5 + $wait * 1.5); @time = localtime; }

    At present I am unable to install any modules from CPAN although this will be solved in time - it is more important to get this project working, built in a nice box and set up in its new home before Christmas.

    As this Raspberry Pi script is running from CRON every 2 minutes, the delay code is probably not necessary. Instead I will just make the check and if the time is invalid, log that and exit. 2 minutes later it will try again anyway. But I would be interested to learn of other ways of preventing two instances of the same script running because not all scripts run so regularly from cron as this one does.

LWP::Simple vs HTTP::Tiny in Seekers of Perl Wisdom
6 direct replies — Read more / Contribute
by Bod
on Dec 13, 2020 at 07:46

    In the past I've always used LWP::Simple when I've needed to fetch something from a web server or pass some information by way of the query string. Basically anytime I've not needed the full power of LWP::UserAgent.

    Only recently I learnt of HTTP::Tiny which appears to do more than LWP::Simple including being able to set the User Agent string and supporting mirrors. It is also core.

    This would make me think that I should drop using LWP::Simple and start using HTTP::Tiny instead. However, on my laptop which has both modules installed, CPAN uses LWP::Simple whereas on the Raspberry Pi, CPAN uses HTTP::Tiny. Given the choice CPAN makes, implies that LWP::Simple is somehow better...

    Are there any advantages for making simple web requests of using one over the other or does it come down to personal preference?

Continuous or timed? in Seekers of Perl Wisdom
10 direct replies — Read more / Contribute
by Bod
on Dec 11, 2020 at 11:13

    For the project that was talked about here -> Controlling USB on Raspberry Pi.
    The software needs to open and close curtains at specified times each day. 8:30am and sunset. So it really only needs to be active a couple of times each day but those times change. One always does and the other potentially will in the future.

    From a design viewpoint this seems to leave three options:

    1. Script runs a continuous loop
    2. CRON kicks it into life ever few (5?) minutes
    3. Script writes to CRON the next time it needs to fire up
    It seems to me that 1 is not such a good plan because, if for any reason the script were to fail, the system fails and doesn't restart. 2 would mean that the granularity of times the curtains could actually change state is quite big as set by CRON. Although this is not a big issue for this project I am trying to build something using sound design principles. Which leaves 3 which prevents the next event being changed. Again not a problem here but perhaps not the best design.

    Gut feeling is that 2 is the way to go but I'm failing to convince myself...

    Which one of these, combination of these or some other option I have not considered would you choose?

To Extend, to Use, to Create in Seekers of Perl Wisdom
4 direct replies — Read more / Contribute
by Bod
on Dec 06, 2020 at 12:06

    I need to create some configuration files on a server that will be pulled down by a client application - the remote config file. The client application will also have its own local config file.

    In the past I would have just hard coded the logic for reading/writing the config files into the program on both the the client and the server which could create a mismatch if this code is ever updated in the future. Being led in the ways of well-written, easy to maintain code by the esteemed Monks of the Monastery, I am going to write this code as a module that can be installed on both the client and the server. That way, provided both are using the same version of the module, no mismatch will be possible. The first step was to search CPAN for suitable modules. I didn't expect to find anything that fitted the task perfectly but I did find a few possible candidates of which the top choice would be Config::Simple.

    I don't need blocks so want what this module calls a SIMPLIFIED INI-FILE. There are two bits of functionality missing from this module that I need:

    • Creating Simplified ini-files
    • Fetching the config file from a server
    So this gives me three options: (or are there more?)
    1. Create a module that extends Config::Simple and add the required functionality
    2. Create a module which makes use of Config::Simple
    3. Write a module from scratch
    In this case I think 3 is most appropriate because Config::Simple does quite a bit of stuff that I don't want and of the the 3 things I want it to do (read/write/get), I have to write methods for 2 of them.

    But this is not a one off do you decide between extending a class by including that class in @ISA or bringing the class into your class with the use statement and having your module call its methods? Some cases seem clear cut but there is a lot of middle ground where the answer is not clear to me.

Controlling USB on Raspberry Pi in Seekers of Perl Wisdom
5 direct replies — Read more / Contribute
by Bod
on Dec 04, 2020 at 16:51

    I have a blind uncle for whom we installed electric curtains for Christmas a couple of years ago as he doesn't know when it goes dark. Currently these are on a rather unreliable timer. Even if the timer were reliable, it doesn't account for the changing sunset time or the change from BST to GMT and back. Generally this hasn't been a problem as we visit and reset the timer monthly. But the COVID lockdown restrictions here in the UK have shown the problems in this arrangement.

    Having looked at several other options I am considering building a controller using a Raspberry Pi Zero and a relay module. I have two choices for connecting the relay module to the RPI - USB or GPIO. My preference is USB but want to check what is involved in controlling the USB port with Perl. Is USB::LibUSB going to be sufficient to control two relays on a module like this one or are there pitfalls awaiting me with USB?

    My plan is that the RPI will get sunset time for each day, open the curtains at 8:30am local time (GMT or BST) and close them at sunset (perhaps with a fixed offest if necessary). I've not used a Raspberry Pi before - any advice or suggestions?

Perlbrew on shared hosting in Seekers of Perl Wisdom
6 direct replies — Read more / Contribute
by Bod
on Dec 03, 2020 at 14:19

    I am trying to upgrade from Perl v5.16.3 supplied by my shared hosting provider to Perl v5.32.0 by using Perlbrew. Perlbrew has installed fine by following the instruction \curl -L | bash from PuTTY.

    Perl v5.32.0 has downloaded fine but it will not install. I run the command:

    perlbrew install perl-5.32.0
    and get the message
    Installation process failed. To spot any issues, check /home/shoples1/perl5/perlbrew/build.perl-5.32.0.log
    Looking at the logfile everything makes sense. It shows the questions for a guided installation with the answers to the questions already filled in. Then right at the end is this:
    Use which C compiler? [cc] ./trygcc: line 10: /usr/bin/cc: Permission denied Uh-oh, the C compiler 'cc' doesn't seem to be working. ./trygcc: line 25: /usr/bin/gcc: Permission denied ./checkcc: line 10: /usr/bin/cc: Permission denied Uh-oh, the C compiler 'cc' doesn't seem to be working. You need to find a working C compiler. Either (purchase and) install the C compiler supplied by your OS vendo +r, or for a free C compiler try I cannot continue any further, aborting. ##### Brew Failed #####
    I have previously installed modules from CPAN using PuTTY and SSH thanks to help from the Monastery. I know that I do not have access to install modules if I attempt it through cPanel but it has worked recently from an SSH shell.

    Am I missing something obvious?

    Is it possible that installing , will installing Perlbrew will upset any of the scripts already running on the server using Perl v5.16.3?