Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Bod

by Bod (Priest)
on Nov 15, 2020 at 00:48 UTC ( #11123653=user: print w/replies, xml ) Need Help??

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...

20th October 2021 - added to Saint in our Book 😀
2nd October 2022 - promoted to Priest


Find me on LinkedIn


CPAN Release

Business::Stripe::WebCheckout


Posts by Bod
Searching for homophones and words that are similar in Seekers of Perl Wisdom
3 direct replies — Read more / Contribute
by Bod
on Mar 21, 2023 at 19:45

    Do you know of an existing module or a method of searching for similar words and phrases, especially homophones (words that are spelt differently but pronounced the same)?

    I am planning a tool to analyse blocks of text, for example the 'About' page of a website, and work out the ratio of words like "I", "we", "our" to words like "you" and "your". However, I want the user to be able to enter their company's name and have that included with the first person terms. But variations of the name may exist in the text.
    For example: "Google" could be "Google", "Google Inc", "Google LLC" or "Alphabet" (the last one is not really catchable programmatically)

    Plus, typos exist especially around homophones and I want to be able to catch those in a similar way to search engines say "did you mean"
    For example: "Perl is grate for programmes" should be suggested as "Perl is great for programs".

    For the current use case, only the first example needs to be solved but I'd be interested how you would approach both. Does a long list of homophones need to be referenced? Or perhaps there is already a module on CPAN that deals with this. I have searched but nothing obvious came up.

Create email tracking image in Seekers of Perl Wisdom
7 direct replies — Read more / Contribute
by Bod
on Mar 17, 2023 at 21:17

    I'm looking for a neat way to create a 1x1 transparent PNG to track opening emails. The ways I have been doing it seem overly clumsy.

    The code we use in our main CRM reads an image file and outputs it. This is c2012 code and my coding has improved considerably since then!

    $file='incl/1x1transparent.png'; print "Content-type: image/png\n"; print "Set-Cookie: abc=xxx; SameSite=none; Max-Age=315576000\n" if $us +er; print "\n"; open IMG, $file; binmode IMG; while (sysread(IMG, $buffer,640)) { print $buffer; } close IMG; exit 0;

    I wanted to avoid making an IO call to the filesystem to read in such a small file. My first attempt was to Base64 encode the image and serve that as text but that didn't work...

    So I have come up with this solution:

    print "Content-type: image/png\n\n"; binmode STDOUT; my $image = GD::Image->new(1, 1); my $white = $image->colorAllocate(255,255,255); $image->transparent($white); print $image->png; exit;

    This is better but it seems a bit of overkill to use GD to do this.

    Can you suggest a more elegant solution?

Module callbacks - to fork or not fork in Seekers of Perl Wisdom
3 direct replies — Read more / Contribute
by Bod
on Feb 28, 2023 at 18:10

    I am writing a module to handle Stripe webhook calls. The module does the necessary checking that the call has come from Stripe and provides the user with a means to give defined callbacks for each webhook. They simply pass in a parameter that matches the Stripe event so it is compatible when Stripe adds new events.

    The module is instantiated like this:

    my $stripe = Stripe::Webhook->new( 'signing_secret' => 'whsec_xxxxxxxxxx', 'invoice-paid' => \&paid, 'all-webhooks' => \&all, );
    Here we call &paid; when Stripe sends an invoice.paid event. Plus, we call &all; for every event sent.

    The person using the module could do things that take a significant time in those subs. But the documentation says "your endpoint must quickly return a successful status code (2xx) prior to any complex logic that could cause a timeout.".

    So should I fork to another process for the callback or should I warn the user of the module in the documentation? Something along the lines of: "If your logic might take some time to complete, fork a new process and perform your logic there. This will allow a timely reply to be sent back to Stripe."

    Or should I be dealing with this problem in some other way?

    This is the code that provides the callback:

    my $hook_type = $self->{'webhook'}->{'type'}; $hook_type =~ s/\./-/g; if (defined &{$self->{$hook_type}}) { $self->{'reply'}->{'status'} = 'success'; &{$self->{$hook_type}}($self->{'webhook'}); }

    In my own implementation of handling events from Stripe, I only make a couple of calls to the database and write to a text file. But of course, I have no idea what other people might want to do with the callbacks. They might decide to send an email which is usually not exactly quick...

Error handling in a module in Seekers of Perl Wisdom
6 direct replies — Read more / Contribute
by Bod
on Feb 17, 2023 at 13:52

    I'm writing a module to go with Business::Stripe::WebCheckout that will deal with Stripe subscriptions. (Yes - I have written tests before writing the code!)

    The way the previous module deals with errors follows the way DBI mostly does it. If there is an error, it keeps it quiet and carries on. My module relies on the code using it to call success to check the operations worked out and calling error to find out what went wrong.

    Currently, the new module uses the same approach. But I am wondering if I should use some other way to detect errors. I've looked back a decade to Re: Error handling and there is some good advice but it doesn't cover modules per see. Also, wisdom can change in a decade...

    Here's my reasoning for doing it the way I am. Can you pick fault with it so that together we can find a better way or be confident that the current methodology is fine?

    • As the module connects with Stripe, it is only ever going to be used in a CGI environment. Therefore, the end user will be a web visitor. My concern is not the user experience of the end user. It is that the person who creates the web experience has an easy time of making a good user experience for the end user.
    • Throwing an error to STDERR is not very helpful in a CGI environment
    • Throwing an error with die and stopping the web experience completely is not a very good solution because the error that will be displayed is probably meaningless to the end user.
    • Taking the end user through to the Stripe checkout with no information is not helpful to anyone. So it is better that my module doesn't stop the website from working, it allows the site creator to check for errors and get an idea of what the error is. But, if they choose not to check, it won't take the end user off to Stripe unless all the required information is present.

    Maintaining compatibility and consistency with Business::Stripe::WebCheckout is not an issue as there needs to be a new version of that soon. I've learnt a lot since publishing that, a couple of people have made suggestions for extra functionality and Stripe have released a newer version of their API. The new version will adopt the error handling of the new, unpublished as yet Business::Stripe::Subscription.

What to test in a new module in Seekers of Perl Wisdom
5 direct replies — Read more / Contribute
by Bod
on Jan 28, 2023 at 17:16

    I've created a helper function for my own purposes and thought it would be useful to others. So CPAN seems a sensible place to put it so others can use it if they want to...

    It's function is simple - to go to the homepage of a website and return an array of URI's within that site, being careful not to stray outside it, that use the http or https scheme. It ignores things that aren't plain text or that it cannot parse such as PDFs or CSS files but includes Javascript files as links (thing like window.open or document.location.href) might be lurking there. It deliberately doesn't try to follow the action attribute of a form as that is probably meaningless without the form data.

    As the Monastery has taught be that all published modules should have tests, I want to do it probably and provide those tests...

    But, given that there is only one function and it makes HTTP requests, what should I test?

    The obvious (to me) test is that it returns the right number of URIs from a website. But that number will likely change over time, so I cannot hardcode the 'right' answer into the tests. So beyond the necessary dependencies and their versions, I'd like some ideas of what should be in the tests, please.

    In case you're interested, this came about from wanting to automate producing and installing sitemap files.

ICS File not updating Google Calendar (probably not a Perl issue) in Seekers of Perl Wisdom
1 direct reply — Read more / Contribute
by Bod
on Jan 18, 2023 at 06:07

    A bit off topic but I am using Perl to generate the data and Template to format it...

    I have a calendar that is synchronised with a a few people's Google Calendar, mine included. The Perl script generates an ICS file showing all the entries. Generally, they don't change date but they do change content. They are slots which customers can book and the VEVENT in the ICS file needs to change SUMMARY from 'vacant' to show the person's name, and a LOCATION tag gets added.

    Google pulls the data in without issue when new slots are added. However, it will not update them. It won't change the time of the slot or show the person's name or location.

    I have checked the ICS file against the ICS Validator and it passes fine. I've also added logging to my script to check that Google is actually reading the file - it reads it twice each day.

    Here is my ICS showing just two entries - in the actual file there are around 25 entries

    BEGIN:VCALENDAR PRODID:-//EC Local Slot Calendar//EN VERSION:2.0 CALSCALE:GREGORIAN X-WR-CALNAME:EC Local Slot Calendar X-WR-TIMEZONE:Europe/London METHOD:PUBLISH BEGIN:VEVENT DTSTAMP:20230122T120000 DTSTART:20230122T120000 DTEND:20230122T133500 UID:EC27@businessgrowthcoventry.uk SUMMARY:EC90 - Ian Boddison DESCRIPTION:https://www.biz-cov.uk/admin#idEC27 LOCATION:Some test place END:VEVENT BEGIN:VEVENT DTSTAMP:20230125T120000 DTSTART:20230125T120000 DTEND:20230125T133000 UID:EC28@businessgrowthcoventry.uk SUMMARY:EC90 - vacant DESCRIPTION:https://www.biz-cov.uk/admin#idEC28 END:VEVENT END:VCALENDAR

    Can you suggest anything I can try to get this to update? It is rather difficult to debug as Google only pulls the data twice a day and it does it at unpredictable times!

    UPDATE

    Updated title to be more descriptive

Rogue character(s) at start of JSON file in Seekers of Perl Wisdom
3 direct replies — Read more / Contribute
by Bod
on Jan 16, 2023 at 17:01

    I'm processing some JSON files using JSON and getting this error:

    malformed JSON string, neither tag, array, object, number, string or a +tom, at character offset 0 (before "\x{feff}[{"registered...")
    So I printed out the JSON file from the Perl script and sure enough there are three rogue characters before the opening square bracket. These do not show up in my text editor TextPad.

    A search has found this explanation. However, the JSON files are being pulled from a UK Government data source and I have no control over how they are made. So I have to deal with the character(s) somehow.

    Here's my test code:

    use strict; use warnings; use JSON; use Data::Dumper; $/ = undef; open my $fh, '<', 'charity.json'; my $data = <$fh>; close $fh; print unpack("W", substr($data, 0, 1)) . ' - ' . unpack("W", substr($data, 1, 2)) . ' - ' . unpack("W", substr($data, 2, 3)) . "\n\n"; $data =~ s/.*?\[/\[/; # <-- fudge to clear character(s) my $json = decode_json $data; print Dumper $json; <code> Using this JSON file as a test... <code> [{"registered":true,"insolvent":false,"administration":true,"test":fal +se}]

    Unpacking the first three characters gives 239 - 187 - 191 and the substitution seems to have the desired effect but it seems to be a bit of a fudge!

    Can you suggest a "better" way to deal with this?

    The output from Data::Dumper is a bit strange:

    $VAR1 = [ { 'administration' => bless( do{\(my $o = 1)}, 'JSON::PP::Bo +olean' ), 'registered' => $VAR1->[0]{'administration'}, 'test' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ), 'insolvent' => $VAR1->[0]{'test'} } ];
    I've come across  bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ) before instead of false but not $VAR1->[0]{'test'}. I guess this is so Data::Dumper doesn't have to create an object for each boolean. It instead it represents them in terms of ones it has previously created. Is that about right?

    I have proved that this is just Data::Dumper and not the underlying data structure by this dereference:

    foreach my $key(keys %{@{$json}[0]}) { print "$key - "; print ${@{$json}[0]}{$key}; print "\n"; }
    Which produces zeros and ones for false and true...

Modules return value in Seekers of Perl Wisdom
9 direct replies — Read more / Contribute
by Bod
on Jan 13, 2023 at 19:59

    This may be better in Meditations - if you have the power to move it and think it should be moved...go ahead!

    I've lost count of how many times I've created a module and got this error...

    Radar/API.pm did not return a true value at index.pl line 14.
    I know exactly how to solve it - simply ensure that there is a true value, usually 1 as the last line of code in the module.

    I suspect that almost everyone who has written more than a couple of modules has forgotten this line and got this error.

    So why is it part of Perl?

    I've been doing some digging and found that it's not just me that finds it frustrating but I have not found an explanation as to why Perl was created like this. Surely there must be a reason for it...

    So it got me thinking...
    Is there ever a time when one might read the true value and see what it is - i.e 1, 6 or 10?
    Is there ever a time when one might want it to return false? I can't imagine this as it's a vague and drastic error. If for some reason, the module cannot be used on the architecture or in the environment then surely a nicer error would be more appropriate.

OT: MariaDB data modelling in Seekers of Perl Wisdom
1 direct reply — Read more / Contribute
by Bod
on Dec 18, 2022 at 16:56

    This is not exactly a Perl question. But I will be using DBI 😁

    I'm starting a new little project and I will be using MariaDB but using more of its power than I usually do.

    Normally I use MySQL Workbench to visually create the ERD. However, for this project, the limitations of using a MySQL product with a MariaDB database are becoming apparent despite their common ancestor. The biggest issues I have found so far are that MySQL and MariaDB use different syntax for disabled indexes, Workbench doesn't support the Aria storage engine and Workbench doesn't seem able to create temporal tables with System Versioning.

    I've looked at many of the graphical clients suggested for MariaDB but none of them seem to visually create ERDs and forward engineer them.

    Are you aware of an equivalent to MySQL Worklbench for MariaDB?

    It's beginning to look like I will need to brush up on my DLL SQL which I seldom use as Workbench is very good at dealing with that for simpler databases that are using MyISAM or InnoDB tables.

    Edited to correct typo

Multilingual design in Seekers of Perl Wisdom
4 direct replies — Read more / Contribute
by Bod
on Dec 10, 2022 at 17:40

    I was recently chatting about writing a web-based system, and the idea came up of making the interface multi-lingual. Just to be clear, this was idle chit-chat and this is not something that will be built (probably!)...but it got me thinking...

    If I were to build a web-based system, how would I make it multi-lingual? In other words, all the interfaces and other wording on the site could be set to display in another language instead of the one it was originally written in.

    Let's leave aside the problem of actually doing the translations as that is the same regardless of implementation. Let's also assume that we are going to use Template to build the system.

    The first option is to have the template files contain just structure and the language passages.

    <h1>[% heading %]</h1> <p>[% introduction %]</p> <form method="post"> <table> <tr> <td class="label">[% form_name %]</td> <td><input type="text" name="name"></td> </tr> </table> </form>
    Then to pass all the text parts to the template file from the Perl script.

    This seems to violate the concept of MVC and involves passing huge numbers of template variables. It also makes the template file very difficult to read as there is not much text, only the variables.

    The second way I came up with was to do much the same but to hold the template variables in a language template file and PROCESS that. There would be a different language template file for each language:

    [% PROCESS $language.tt %] <h1>[% heading %</h1> <p>[% introduction %]</p>
    Again this makes the template file very difficult to read.

    Lastly I considered having completely separate template files for each language. Arrange these in different directories by language and pass in language-specific variables where variables would be passed in for a mono-lingual system.

    This last approach has lots of advantages. The template files are easy to read as they have all the static text, Only the necessary variables are passed into Template and it is easy to add another language. But there is a big disadvantage - although it is easy to implement, it would be very difficult to maintain. A change made to one template file would need to be made to the corresponding one in every other language. It would probably need a script to keep them all in sync.

    Is there another way that I haven't thought of?
    I'm sure I'm not the first person to have thought this through!
    What is the "best" solution to this problem?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others browsing the Monastery: (4)
As of 2023-03-29 07:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Which type of climate do you prefer to live in?






    Results (70 votes). Check out past polls.

    Notices?