This week we had contrasting challenges.

Challenge 1 - Reverse Words

Take a string of words {with arbitrary white space around the words} and reverse the order of the words in the string and removing any redundant white space.

This is a classic example of a 1-liner....


   join q( ), reverse grep {$_} split m{\s+}, $_[0];

Challenge 2 - Edit Distance

I will provide 2 solutions here... one a less optimal solution which at the same time gives us a nice way of rendering the alignment - and then an more efficient "boiling down" of the first algorithm to just return the distance...

I'm just going to add "Another day job challenge!"

To be able to make "nicer" output - rather than just keeping track of the edit distance of substrings - we will actually keep the alignment of the two words as a string of "operations" whether they be Indels or SNPs.

One of my background is working with genomic data and this can be thought of as a simple alignment algorithm - and so I think of the three operations as Indels {inserts/deletes - remembering an insert into one sequence is just the same as a delete from the other} and SNPs - or single nucleotide polymorphisms.

The simple alignment string representation we will use consists of:


'|' - the letters are the same;
'v' - insert
'^' - delete
' ' - SNP/modify

We can convert this to an edit distance by counting all the non-"|"s In perl we do this with tr/^v /^v / which returns the number of matches in scalar form. See {_edit_dist - function}

Finally we include a nice way to render the alignment {edits} By showing the two words with appropriate inserts in each word and indicate where the letters match in each word via a the alignment string in the middle. See {render_alighnment function}

  kitten-    sunday    boat rid-ing
   ||| |v      ||||    |^||||||v|||
  sitting    monday    b-at ridding

Additional note - we "memoise" the alignment function - as it will be called with the same subseq of letters following different paths through the two sequences. This increases performance...

From a "genomic" point of view this is known as the basis of the Smith-Waterman local alignment algorithm. Although Smith-Waterman has other features - including variable "penalties" for each type of edit {inserts, deletes, modifications}. Even having different penalties for certain changes {this is also similar to how typing correction software works - with assuming adjacent key typos are more likely.

See:
* https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm

Solution

We solve the recursively (stripping letters from one or both words each time). We have a number of options.

* Either of the words has no-letters - so the alignment is either a set of inserts/deletes from the other string.
* If the first character of each word is the same - we continue to the next letters {and an alignment is marked between the two words}
* If they are not the same - we look to see which of the options insert, delete or snp makes has the lowest score...

The other two helper functions render this string (given the two sequences) showing the gaps and alignments; and work out the edit distance from the alignment.


sub alignment_string {
  my( $s, $t ) = @_;
  $calls++;
  my $key = "$s\t$t";
  return $cache{$key} if exists $cache{$key};
  $misses++;
  ## Both strings are empty so reached end!
  return $cache{$key}||=''              if $t eq q() && $s eq q();
  ## Exhausted t so all edits are now deletes...
  return $cache{$key}||='^' x length $s if $t eq q();
  ## Exhausted s so all edits are now inserts...
  return $cache{$key}||='v' x length $t if $s eq q();
  ## First letters are the same so we just prepend the
  ## match symbol (|) and continue...
  return $cache{$key}||='|'.alignment_string(substr($s,1),substr($t,1))
                                        if ord $s == ord $t;

## We now have three choices - "insert", "delete" or "SNP"
my($d,$i,$m) = (
'^'.alignment_string( substr($s,1), $t ),
'v'.alignment_string( $s, substr($t,1) ),
' '.alignment_string( substr($s,1), substr($t,1) ),
);
return $cache{$key}||=
_edit_dist( $d ) < _edit_dist( $i )
? ( _edit_dist( $d ) < _edit_dist( $m ) ? $d : $m )
: ( _edit_dist( $i ) < _edit_dist( $m ) ? $i : $m );
}

sub edit_distance {
return _edit_dist( alignment_string( @_ ) );
}

sub _edit_dist { ## Count inserts(v), deletes(^) & mis-matches( )
return $_[0] =~ tr/^v /^v /;
}

sub render_alignment {
my( $s, $t ) = @_;
my $a = alignment_string( $s, $t );
my( $top, $bot ) = ( '','' );
foreach ( split m{}, $a ) {
$top .= $_ eq 'v' ? '-' : substr $s, 0, 1, '';
$bot .= $_ eq '^' ? '-' : substr $t, 0, 1, '';
}
return sprintf "%s\n%s (%d)\n%s\n",
$top, $a, _edit_dist($a), $bot;
}

If we are not interested in the "alignment" diagram we can simplify the code:


sub edit_distance_simple {
  my( $s, $t ) = @_;
  return $cache_x{"$s\t$t"}||=
     $t eq q()          ? length $s
   : $s eq q()          ? length $t
   : (ord $s == ord $t) ? edit_distance(substr($s,1),substr($t,1))
   :                      1+(sort { $a <=> $b }
                            edit_distance(substr($s,1),$t),
                            edit_distance($s,substr$t,1),
                            edit_distance(substr($s,1),substr $t,1)
                          )[0]
   ;
}

Note re-caches - these memoize the function - from trials the approximate hit is 50% - this matches up with the non recursive solution.

The lighting in my bedroom uses Philips Hue bulbs — specifically, the coloured ones. Last night, I decided it would be nice to set the three lights in my bedroom to cycle slowly through a set of warm colours using a script.

I didn't want harsh transitions from one colour to the next, but for the lighting to fade from one colour to the next in a smooth gradient. Also, I didn't want the three bulbs to all be the exact same colour, but wanted each bulb to be at different stage in the cycle, like they're "chasing" each other through the colours.

So I whipped up a quick script. It requires the command-line tool hueadm to be installed and set up before we start. You can run hueadm lights to get a list of available lights, and in particular, their ID numbers.

First step, we need to be able blend two colours together. This is necessary to be able to create smooth transitions between colours. I stole a little code from Color::Fade on CPAN to do this.

Pretty simple averaging of two RGB values. You could arguably get a better effect by converting to HSL, averaging those values and then converting back to RGB, but in practice, using RGB seems fine.

Now we can build the gradients. We want to take an array of colours and make it into a cycle by copying the first colour to the end, then create a new array, copying the colours into the new array, but inserting a blend of each pair between them, so our initial array of N colours grows to an array of 2N colours. Then repeat that process a few times.

Nice. Now we know what colours our lights should be.

Here's a quick Moo class to control a lightbulb, given its ID. Nothing interesting to see here.

Finally we can work on the logic. We have an array of colours; we need to pick an index in that array for each bulb. My array of colours (after being expanded into a gradient), ended up being 160 colours, so it seemed that a good starting point was to set my three bulbs initially to colours 0, 16, and 32 in the array. Something along these lines:

Then pause for a second or two, then add one to each index (looping any that go over 160 back to 0) and repeat. Here's the full code for that:

Now let's test it out...

It works!

The full script is on GitHub Gist.

Perl 5 script to cycle through a gradient of colours on a set of Philips Hue lightbulbs.

Full script on Gist

Explanation on my blog

In this article we will be looking at how to generate different type of checksum across Linux and Windows.

In Perl, there are various module available to generate checksum -

  1. Digest::SHA - Perl core module
  2. CryptX::Digest

There are few other but either they are specific to particular digest or abandoned.

Digest::SHA is in Perl core since v5.9.3. So, if you have Perl installed having higher version than 5.9.3 you will have Digest::SHA and installation is not needed.
But what if you are in some closed system, stuck with some older Perl version. What if you don't have permission and you can't install/use any external modules. I have faced similar issue and I have created this script just for that purpose.

So lets get started.
Both linux and windows comes with inbuilt utilities to calculate different hash -

  • Linux - md5sum, sha1sum, sha256sum, sha512sum etc.
  • Windows - certutil.exe

We can execute these commands using backticks operator and get there output.
For linux the commands are present in /usr/bin. For windows it is in C:/Windows/System32/

#!/usr/bin/env perl

use strict;
use warnings;

# HashAlgorithm choices: MD2 MD4 MD5 SHA1 SHA256 SHA384 SHA512
sub get_checksum {
    my ($path_to_file, $hash_algorithm) = @_;
    my $checksum;
    my $os = $^O;
    eval {
        if (-s $path_to_file) {
            if ($os =~ /MSWin32/i) {
                $checksum = (
                    split(
                        '\n',
                        `C:/Windows/System32/certutil.exe -hashfile $path_to_file $hash_algorithm`
                    )
                )[1];
            }
            else {
                my $cmd = "/usr/bin/" . lc($hash_algorithm) . "sum $path_to_file";
                $checksum = (split(' ', `$cmd`))[0];
            }
        }
        else {
            $checksum = "File is empty";
        }
    };
    if ($@) {
        print("Unable to calculate the $hash_algorithm for $path_to_file : $@");
    }
    return $checksum;
}

# Calculate checksum of the given file
my $checksum = get_checksum("test.txt", "SHA256");
print $checksum;
Enter fullscreen mode Exit fullscreen mode

The output from external command contains some extra elements also. We are removing those and getting only the checksum.
Running this script generated the following output -

946576fd7b973e3a8c78e5695637727399f34ca8d6fd6b89f1f5328c1e0b0bdc
Enter fullscreen mode Exit fullscreen mode

We can pass different type of checksum parameter to get_checksum function and and get different output.
You can also modify the script to use it as command line tool.
This can come in handy if you want to check integrity of file across different OS without changing your code.

Perl onion logo taken from here
Hash function image taken from here

Let me preface this short post with this, I don't have the solution to this problem. Perhaps there is someone in the wider Perl space who is well placed to pick this up, but there seems to be little going on in terms of community engagement.

In the first week of 2021 I noticed a link to this sunset message for rt.cpan behind displayed on the rt.cpan homepage. Firstly I believe the notification on the page could be highlighted better, grey on grey, on a page with lots of grey isn't exactly eye catching.

At the time the linked article didn't contain much information, besides a date. It has since been updated with links to resources to migrate tickets elsewhere.

A reply to my post in the perlmonks news section was concerning to me, I shortly found the infrastructure working group post on topicbox (which I find no link to on any of the perl websites, or release documentation). This thread was concerning in so much as a single volunteer has decided to step back, which is of course fine, but it doesn't seem like the option of asking the wider community if anyone would be willing to step up and take it over has been explored. It doesn't even seem to be being openly discussed.

On the day it was posted I replied to A Static archive or rt.cpan.org, raising some concerns. The site told me my response was awaiting author moderation, as yet it remains unpublished.

I registered and was approved on Topicbox, posted in the thread linked above, raising some concerns about how this is is going, and how this has been communicated with the wider community. This too is apparently still in a moderation queue.

I reached out to Ricardo Signes, asking for clarity on the situation, Richardo responded to say that the static archive was more likely to be the course of action rather than someone else picking up the operation rt.cpan.

Paul Evans has posted FYI rt.cpan.org is going away over on p5p, which contains lots of good information, the whole thread is really worth reading, including the quote:

"To emphasise again: in 41 days time the bug tracker used by nearly 80%
of all of CPAN is going to be shut down and become unavailable for
either historic or newly-reported bugs. We *need* to find a solution in
that time."

Karen Etheridge posted a reply ending:

"I want to be clear that it is totally acceptable for a volunteer to decide
that they can't or won't have the time/energy/enthusiasm to continue a
task. Volunteer effort is greatly appreciated while it is there, but it
cannot be presumed to be provided in perpetuity. However, I find it greatly
distressing that the option for new volunteer(s) to step in and take over
is not being permitted. This is not something that can or should be decided
unilaterally."

This is a Raku answer for the 096 Perl Weekly Challenge

.

Exercise 1

The first task consists in writing a script to reverse the order of words without leading/trailing spaces. These are the examples:

Example 1:
Input: $S = "The Weekly Challenge"
Output: "Challenge Weekly The"

Example 2:
Input: $S = " Perl and Raku are part of the same family "
Output: "family same the of part are Raku and Perl"

This is easy to implement in Raku due to the many routines and methods included by default:

sub challenge( $string ) {
  return $string.words.reverse;
}

Exercise 2

The second exercise is the implementation of the Wagner–Fischer algorithm to compute the edit distance between two strings of characters. Raku shows the muscle of its support for OOP, allowing to almost literally write down the algorithm as a class. This is the pseudocode algorithm on Wikipedia:

function LevenshteinDistance(char s[1..m], char t[1..n]):
  declare int d[0..m, 0..n]
  set each element in d to zero
  for i from 1 to m:
      d[i, 0] := i
  for j from 1 to n:
      d[0, j] := j
  for j from 1 to n:
      for i from 1 to m:
          if s[i] = t[j]:
            substitutionCost := 0
          else:
            substitutionCost := 1
          d[i, j] := minimum(d[i-1, j] + 1,                   // deletion
                             d[i, j-1] + 1,                   // insertion
                             d[i-1, j-1] + substitutionCost)  // substitution
  return d[m, n]

And the definition of the class in Raku:

class Distance {

  has Str $.s1;
  has Str $.s2;

  method distance() {
    return $!s1.chars if $!s2.chars == 0;
    return $!s2.chars if $!s1.chars == 0;
    min (
      Distance.new( s1 => $!s1.chop, s2 => $!s2      ).distance + 1, 
      Distance.new( s1 => $!s1     , s2 => $!s2.chop ).distance + 1, 
      Distance.new( s1 => $!s1.chop, s2 => $!s2.chop ).distance + 
!($!s1.substr(*-1) eq $!s2.substr(*-1)).Num; # substitution
    );
  }
}

While this may be similar to the other dynamic programming solutions provided, this OOP recursion appears more intuitive and readable for neophytes like me. It has been quite amazing to be able to write the class definition and see that it works!

What is a role? Put simply, roles are a form of code reuse. Often, the term shared behavior is used. Roles are said to be consumed and the methods ( including attribute accessors ) are flattened into the consuming class.

One of the major benefits of roles is they attempt to solve the diamond problem encountered in multi-inheritance by requiring developers to resolve name collisions manually that arise in multi-inheritance. Don't be fooled however, roles are a form of multi-inheritance.

I often see roles being used in ways they shouldn’t be. Let’s look at the mis-use of roles, then see an example of shared behavior.

I’m using that word inheritance a lot for a reason, one of the two ways I see roles most often misused is to hide an inheritance nightmare.

"Look ma, no multi-inheritance support, no problem. I’ll just throw stuff in roles and glum them on wherever I really want to use inheritance. It all sounds fancy, but I am just lumping stuff into a class cause I don’t really understand OO principals."

One tenet of good object oriented design (GOOD) and not just OOPS ( object oriented programming syntax ) is Composition over Inheritance. All too often roles get used where composing an object, possibly with method forwarding, or an external function, is really what you want. How do I know? Because there is no use of the wrappee self in the ‘role’. Roles are a form of delegation ( this term is mis-used for method forwarding in some documentation ). Delegation is analogis to inheritance, and in delegation the *self* in the wrappee role is the consuming class’ (wrapper) self. Once execution is handed to a role method, all work is done inside role sub routines till either a side effect finishes or a result is generated then execution returns to the code in the wrapper class. Yet, in the class consuming the role, “self” is never called or not called in a way that is meaningful to the class.

This brings me to the next mis-use; roles for code organization. If there is a one to one relationship between the role and it’s use, then this is nothing more than code organization (and obfuscation), stuffing code under the bed like an adolescent boy who told to clean his room, doesn't make it clean. Roles being used as a way to break a 5000 line file into five 1000 line files. Why would someone do this? Because in their heart they know that they have that class doing way too much - it smells. Not understanding good OO, the class grew into a god object. Knowing this is wrong but either not knowing why or just not caring, code was broken into roles along some perceived associations. As other developers came along, prior art was followed and possible encouraged. Unfortunately, as functionality was further glummed on, things just got thrown into the most convenient role or the use of roles exploded as a way of expanding class functionality but pretending like it was being done in a good OO way. There is no shared behavior, the roles are consumed in exactly one class. How to get out of this is a topic for another article. For now, the code from the role should be merged into the same file as the class so you can really see what you have and go from there.

Another version of the one to one is when roles are used to define an interface, either with some function bodies or not, but mostly just “requires ”. In the outset it seemed like a good idea, but now a decade later the only place any interface defining role is used is in the one class that implements those methods. This is noise and liability in your code, delete the roles and remove the "with" line in the class.

So what are roles good for? In Perl roles are analogies to traits. They can define behavior with full methods built out and or require the composing class implement methods to parameterize the behavior.

So what does shared behavior look like? Here's a contrived example. Please don't do this, there are better ways to handle this sort of thing, I'm just showing shared behavior, not the correct way to handle THIS problem.


package Comparable;
use Role::Tiny;

requires 'area';
requires 'perimeter';

sub can_contain {
my ($self, $other) = @_;
my $can_contain = $self->area >= $other->area;
return $can_contain;
}

sub is_bigger {
my ($self, $other) = @_;
my $is_bigger = $self->perimeter > $other->perimeter;
}


package Square;
use Role::Tiny::With;
with 'Comparable';

sub area {}
sub perimeter {}


package Circle;
use Role::Tiny::With;
with 'Comparable';

sub area {}
sub perimeter {}


... somewhere else...
$square->can_contain($circle);
$circle->is_bigger_than($square);

Do you have any great examples of roles being used correctly?

As I researched this article, I discovered I don't think roles really fit with my view of objects in general. That is, they are often over used in ways that would be better suited by dependency injection, or external functions.

Peace.

I have a file that has the word "snake" in it

There is a snake in the first line.
No such word in the second line.
Another snake in the 3rd line.
There is a blue snake and a red snake here.
Enter fullscreen mode Exit fullscreen mode

This one-liner that you can run on the command line will replace the word "snake" by the word "camel" in the file:

$ perl -i -p -e 's/snake/camel/g' snakes_or_camels.txt
Enter fullscreen mode Exit fullscreen mode

This one-liner will create a backup of the original file before making the changes.

$ perl -i.bak -p -e 's/snake/camel/g' snakes_or_camels.txt
Enter fullscreen mode Exit fullscreen mode
  • -i means "inplace editing"
  • -i.bak means "copy to .bak then inplace editing"
  • -p means (in rough terms) "What ever you do, do it on every line of data."
  • -e means "execute the following code".
  • s/snake/camel/g is a substitution. The g at the end means globally. Without that only the first snake would be replaced.

The result is

There is a camel in the first line.
No such word in the second line.
Another camel in the 3rd line.
There is a blue camel and a red camel here.
Enter fullscreen mode Exit fullscreen mode

If you'd like to learn more about Perl check out my Perl Tutorial

We will be looking at sending HTML emails with attachments using Perl.
As per Perl TIMTOWTDI philosophy there are multiple ways to send email. Few of them are -

  1. Using /usr/sbin/sendmail utility - for linux
  2. MIME::Lite
  3. Email::Send
  4. Email::Sender

I remember when I started using Perl and when a day arrived where I have to send email, I found MIME::Lite. It was good for that time being and its gets the job done.
But as the time progress multiple alternative appears and also in current day MIME::Lite is not the recommended way to send email. From there documentation -

MIME::Lite is not recommended by its current maintainer. There are a number of alternatives, like Email::MIME or MIME::Entity and Email::Sender, which you should probably use instead. MIME::Lite continues to accrue weird bug reports, and it is not receiving a large amount of refactoring due to the availability of better alternatives. Please consider using something else.

Also, for Email::Send -

Email::Send is going away... well, not really going away, but it's being officially marked "out of favor." It has API design problems that make it hard to usefully extend and rather than try to deprecate features and slowly ease in a new interface, we've released Email::Sender which fixes these problems and others. As of today, 2008-12-19, Email::Sender is young, but it's fairly well-tested. Please consider using it instead for any new work.

I also got opportunity to work on some older codes and there were extensive use of MIME::Lite and sendmail (which is the Linux utility for sending emails) and I thought these are de-facto standards but as the time progress I realize there are more and better ways to send email.
Today we will be seeing one of those ways to end email which is recommended as of now - Email::Sender.

Lets revisit few of the basic process of sending email-

  1. A mail template containing mail body in HTML format. In case you want to send plain text email you can ignore this.
  2. Creating mail with the subject, attachment and mail body created in Step 1.
  3. Send Email using SMTP server.

1. Creating the Email package -

We will be creating a package containing all our email utilities(SendMail.pm).

package SendMail;
use strict;
use warnings;

sub new {
    my ( $class, @arguments ) = @_;
    my $self = {@arguments};
    bless $self, $class;
    return $self;
}

1;
Enter fullscreen mode Exit fullscreen mode

2. Generating Mail Template -

We will be creating a method for generating our mail template. You can use any library of your choice to generate this template. Few notable are -

I am using HTML::Template for this job as our scope is limited only to creating mail body and it is right tool for the job.
If you have some complex requirement you can check out other two (especially Text::Template).
Adding HTML::Template to the above code.

use HTML::Template;

sub generate_mail_template {
    my ( $self, $filename, $parameters ) = @_;

    # create mail body template. We don't want to die/exit if any of the parameters is missing
    my $template = HTML::Template->new( filename => $filename, die_on_bad_params => 0 );
    $template->param($parameters);
    return $template;
}
Enter fullscreen mode Exit fullscreen mode

This takes the absolute filename of the template file and the parameters we will be substituting in that template file.
Right now I am using a simple template which can be extended based on requirement (template.html)

<html>
    <head>
        <title>Test Email Template</title>
    </head>
    <body>
        My Name is <TMPL_VAR NAME=NAME>
        My Location is <TMPL_VAR NAME=LOCATION>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

3. Creating Email to send -

Now comes the part where we will be creating our email with subject, attachment and body before sending.
But before that, we want to make the users to which we will be sending email configurable and for that we will be creating a config file.
Right now I am creating a json based config (config.json). But, you can create it in any format of you choice.

{
    "mail" : {
                "mail_from" : "test@xyz.com",
                "mail_to" : ["grai@xyz.com"],
                "mail_cc_to" : ["grai@xyz.com", "grai2@xyz.com"],
                "smtp_server" : "abc.xyz"
             }
}
Enter fullscreen mode Exit fullscreen mode

The key names are self explanatory.

Now back to the part of creating email. We will create another method for this by adding to above code.


use Email::MIME;
use File::Basename qw( basename );

sub create_mail {
    my ( $self, $file_attachments, $mail_subject, $mail_body ) = @_;

    my @mail_attachments;
    if (@$file_attachments) {
        foreach my $attachment (@$file_attachments) {
            my $single_attachment = Email::MIME->create(
                attributes => {
                    filename     => basename($attachment),
                    content_type => "application/json",
                    disposition  => 'attachment',
                    encoding     => 'base64',
                    name         => basename($attachment)
                },
                body => io->file($attachment)->all
            );
            push( @mail_attachments, $single_attachment );
        }
    }
    # Multipart message : It contains attachment as well as html body
    my @parts = (
        @mail_attachments,
        Email::MIME->create(
            attributes => {
                content_type => 'text/html',
                encoding     => 'quoted-printable',
                charset      => 'US-ASCII'
            },
            body_str => $mail_body,
        ),
    );

    my $mail_to_users    = join ', ', @{ $self->{config}->{mail_to} };
    my $cc_mail_to_users = join ', ', @{ $self->{config}->{mail_cc_to} };

    my $email = Email::MIME->create(
        header => [
            From    => $self->{config}->{mail_from},
            To      => $mail_to_users,
            Cc      => $cc_mail_to_users,
            Subject => $mail_subject,
        ],
        parts => [@parts],
    );
    return $email;
}
Enter fullscreen mode Exit fullscreen mode

This method will take list of attachments, subject and body and create a MIME part using Email::MIME.
Also there is Email::Stuffer which you can also use for similar part. But for now I am using Email::MIME.

4. Sending Email -

Now our mail to ready for send. We will create another method for it in addition to above methods.


use Email::Sender::Simple qw( sendmail );
use Email::Sender::Transport::SMTP;

sub send_mail {
    my ( $self, $email ) = @_;
    my $transport = Email::Sender::Transport::SMTP->new(
        {
            host => $self->{config}->{smtp_server}
        }
    );
    eval { sendmail( $email, { transport => $transport } ); };
    if ($@) {
        return 0, $@;
    }
    else {
        return 1;
    }
}

1;
Enter fullscreen mode Exit fullscreen mode

This method will just take the email which we have created before and sent it to requested recipient.
Since our work is done here we will end the module with a true value.

5. Using the module -

Now we have the module ready. Lets create a perl script and try to utilize the methods which we have created(mail.pl).

#!/usr/bin/env perl

use strict;
use warnings;
use JSON;
use Cwd qw( abs_path );
use File::Basename qw( dirname );
use lib dirname(abs_path($0));
use SendMail;

sub read_json_file {
    my ($json_file) = @_;
    print "Reading $json_file";

    open (my $in, '<', $json_file) or print "Unable to open file $json_file : $!";
    my $json_text = do { local $/ = undef; <$in>; };
    close ($in) or print "Unable to close file : $!";

    my $config_data = decode_json($json_text);
    return ($config_data);
}

# Read the config file
my $config = read_json_file("config.json");

my $mail = SendMail->new("config" => $config->{'mail'});

# param which you want to substitute in mail template
my $mail_parameters = "NAME => 'Gaurav', LOCATION => 'INDIA'";

# path to mail attachments
my $attachments = ["abc.txt"];

# path to mail template
my $mail_template = "mail_template/template.html";

print "Generating HTML template for mail body";
my $template = $mail->generate_mail_template($mail_template, $mail_parameters);

print "Creating mail with body and attachments to send";
my $mail_subject = "Test Mail";
my $email = $mail->create_mail($attachments, $mail_subject, $template->output);

print "Sending email...";
my ($mail_return_code, $mail_exception) = $mail->send_mail($email);

if (defined $mail_exception) {
    print "Exception while sending mail: $mail_exception";
    return 0;
}
else {
    print "Mail Sent successfully";
    return 1;
}
Enter fullscreen mode Exit fullscreen mode

And we are done. You can run this script from your terminal and test this. The full code for reference is also available at Github for your reference

Perl onion logo taken from here
Mail logo taken from here and here

In Perl there are 3 loop control keywords. The two commonly used are next and last and there is a third which is rarely used called redo.

In most of the other languages the respective keywords are continue and break.

next of Perl is the same as the continue in other languages and the last if Perl is the same as the break of other languages.

redo probably does not have its counterpart.

We will be looking at how to automate windows application using Perl.
Again there are multiple ways to achieve this. Few of them are -

  1. Win32::GuiTest
  2. X11::GUITest
  3. Win32::OLE

There are other autohotkey and autoitscript which are quite good in windows automation. I encourage you to check them out.

For this blog we will be using Win32::GuiTest for automating windows application.
We will start with the a small example which can later be extended.
Lets visit what we will be doing -

  1. We will open the given directory in windows explorer (Right now C:/Perl64/lib/)
  2. Search for a file or folder (e.g. strict.pm)
  3. Right-click on that particular file or folder(this is achieved using keyboard shortcuts)
  4. Select an option (right now 'Open with Notepad++')
  5. Wait for the action to be completed because of the selected option
  6. After the action is successful close it (close Notepad++)
  7. Close the windows explorer

See, that quite simple thing we will be doing. We will take baby steps by starting with our first step.

1. Creating the package -

We will be creating a package containing all our automation utilities(Win32Operations.pm).

package Win32Operations;
use strict;
use warnings;

sub new {
    my $class = shift;
    my ($self) = {@_};
    bless $self, $class;
    return $self;
}
Enter fullscreen mode Exit fullscreen mode

2. Starting Windows Explorer operations -

Now we have to open a dir in windows explorer. We will create a method for this.

sub start_explorer_operations {
    my ($self, $root_directory, $filename_to_open, $max_retry_attempts) = @_;

    # Path to windows explorer. The Windows directory or SYSROOT(%windir%) is typically C:\Windows.
    my $explorer = "%windir%\\explorer.exe";

    # Since we are on windows removed forward slash with backslashes
    $root_directory =~ s/\//\\/g;

    # seconds to spend between keyboard key presses
    my $key_press_delay = 1;

    # this is the interval the tester sleeps before checking/closing
    # any window; this is just for an eye effect so we can
    # watch what happens
    my $wait_time_for_windows = 3;

    my $count = 0;
    while ($count < $max_retry_attempts) {
        $count++;
        $self->{'logger'}->info("Opening dir: $root_directory in windows explorer");
        my $status_code = system($explorer , "$root_directory");
        if ($status_code == -1) {
            $self->{'logger'}->error(
                "Unable to open 'explorer.exe' with mentioned dir : $root_directory. Exited with return code :". $status_code);
            next;
        }
        elsif ($status_code & 127) {
            $self->{'logger'}->error("Child died with signal : " . ($status_code & 127));
            next;
        }
        else {
            # Windows explorer is opened hear and we are in the given dir
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The comments are self explanatory. We will be opening windows explorer. It will retry for some time in case it fails.
Also we have to cross check whether the open window is the location to our expected folder or not.
We will also give some time to system for this operation.
For this we will create a generalized method -

sub _wait_for {
    my ($self, $title, $wait_time) = @_;
    my $win;
    while (1) {
        # This will look for the window with the given title
        ($win) = FindWindowLike(0, $title);

        if (defined $win) {
            $self->{'logger'}->info("Found window with title : $title.");
            last;
        }
        else {
            $self->{'logger'}->info("Unable to find window with title : $title. Retrying...");
            sleep $wait_time;
        }
    }
    return $win;
}
Enter fullscreen mode Exit fullscreen mode

3. Bringing Window to front -

Now we are able to open the explorer windows and confirm that it is that window.
We will be trying to bring back that particular window to front. Reason being what if in between opening of explorer and starting our operation, our focus moved to somewhere else (someone clicked outside the explorer or open some other application). For this we will be creating another method -

sub _bring_window_to_front {
    my ($self, $window, $retry_attempts, $pause_between_operations) = @_;
    my $success = 1;
    my $count   = 0;
    while ($count < $retry_attempts) {
        $count++;
        if (SetActiveWindow($window)) {
            $self->{'logger'}->info("* Successfully set the window id: $window active");
        }
        else {
            $self->{'logger'}->warn("* Could not set the window id: $window active: $!");
            $success = 0;
        }
        if (SetForegroundWindow($window)) {
            $self->{'logger'}->info("* Window id: $window brought to foreground");
        }
        else {
            $self->{'logger'}->warn("* Window id: $window could not be brought to foreground: $!");
            $success = 0;
        }
        sleep $pause_between_operations;
        my $foreground_window_id = GetForegroundWindow();
        if ($foreground_window_id =~ /$window/i) {
            last;
        }
        else {
            $self->{'logger'}->info(
                "Found - $foreground_window_id instead of expected - $window. Will try again...");
            next;
        }
    }
    return $success;
}
Enter fullscreen mode Exit fullscreen mode

The system restricts which processes can set the foreground window. A process can set the foreground window only if one of the following conditions is true:

  • The process is the foreground process.
  • The process was started by the foreground process.
  • The process received the last input event.
  • There is no foreground process.
  • The foreground process is being debugged.
  • The foreground is not locked.
  • The foreground lock time-out has expired.
  • No menus are active.

Please keep these in mind. Otherwise this method will fail.

4. Opening and closing in Notepad++ -

Now back to our 'else' part in 'start_explorer_operations' where we will be using these methods.
Now we have to search for our file, 'right-click' on it and select 'edit with Notepad++'.
After opening we will close it and close the explorer also.

  1. Searching file - We may have noticed that if we open explore and start typing our file name on our keyboard, window will automatically select that particular file. We will be using similar thing here.
  2. Right Click - we will be using context menu key on the keyboard (Another alternative - Shift + F10) for simulating right click.
  3. Open with Application - If you press context menu key or Shift + F10, you can see different options and each options have a underline on one of the characters. These are the shortcuts to select that particular option/ In our case the underline is under N meaning if we press N we will be selecting that option. After that we will press Enter to open it.
  4. Closing the application - We will be using the close command from the System Menu to close it. On the top of Notepad++ if you right click you can see the System Menu
sub start_explorer_operations {
    ....
        }
        else {
            my $window = $self->_wait_for(basename($root_directory), $wait_time_for_windows);
            $self->{'logger'}->info("Opened 'explorer.exe'. Window id : $window");

            $self->_bring_window_to_front($window, $max_retry_attempts, $wait_time_for_windows);
            $self->{'logger'}->info("Opening the file in Notepad++...");

            # This will use your keyboard to
            # Write the 'filename' to search
            # Right click on it(context menu) - {APP}
            # Select 'N' for Notepad++ and press 'Enter' to open
            # Replace 'N' with your own application shortcut if you are using something other application
            my @keys = ("$filename_to_open", "{APP}", "N", "{ENTER}");
            $self->_keys_press(\@keys, $key_press_delay);

            $self->{'logger'}->info("Opened the file in Notepad++. Closing it...");
            # Checking wheteher we are actually able to open Notepad++ or not
            my $opened_explore_window = $self->_wait_for($filename_to_open, $wait_time_for_windows);
            if (!$opened_explore_window) {
                $self->{'logger'}->warn("Cannot find window with title/caption $filename_to_open");
                next;
            }
            else {
               # We will come here when we successfully opened the file in Notepad++
               $self->{'logger'}
                    ->info("Window handle of $filename_to_open is " . $opened_explore_window);
                print $opened_explore_window;
                $self->_bring_window_to_front($opened_explore_window, $max_retry_attempts,
                    $wait_time_for_windows);
                $self->{'logger'}->info("Closing Notepad++ window...");
                MenuSelect("&Close", 0, GetSystemMenu($opened_explore_window, 0));
                $self->{'logger'}->info("Bringing Explorer window to front...");
                $self->_bring_window_to_front($window, $max_retry_attempts, $wait_time_for_windows);
                $self->{'logger'}->info("Closing Explorer window...");

                # There are different way to close windows File explorer -
                # 1. Alt+F4 (Will close anything on the top - little risky)
                # 2. Alt+F, then C
                # 3. CTRL+w (only closes the current files you're working on but leaves the program open)
                SendKeys("^w");
                return 1;
            }
        }
}

1;
Enter fullscreen mode Exit fullscreen mode

Again, the comments are self explanatory. Another thing to note here is that, there are various ways to close the application. One of the common one is Alt + F4. But is it also risky. Reason being it can close anything on the top, anything.
What if on top there is some other application which you don't want to close.
Better to use context menu or application defined close.

5. Using the module -

Now we have the module ready. Lets create a perl script and try to utilize the methods which we have created(Win32Operations.pm). We will be using Log4perl for our logging. You can use anyone of your choice.

use strict;
use warnings;
use Log::Log4perl;
use Cwd qw( abs_path );
use File::Basename qw( dirname );
use lib dirname(abs_path($0));

use Win32Operations;

sub initialize_logger {

    # initialize logger, you can put this in config file also
    my $conf = qq(
            log4perl.category                   = INFO, Logfile, Screen
            log4perl.appender.Logfile           = Log::Log4perl::Appender::File
            log4perl.appender.Logfile.filename  = win32op.log
            log4perl.appender.Logfile.mode      = write
            log4perl.appender.Logfile.autoflush = 1
            log4perl.appender.Logfile.buffered  = 0
            log4perl.appender.Logfile.layout    = Log::Log4perl::Layout::PatternLayout
            log4perl.appender.Logfile.layout.ConversionPattern = [%d{ISO8601} %p] [%r] (%F{3} line %L)> %m%n
            log4perl.appender.Screen            = Log::Log4perl::Appender::Screen
            log4perl.appender.Screen.stderr     = 0
            log4perl.appender.Screen.layout     = Log::Log4perl::Layout::SimpleLayout
        );
    Log::Log4perl->init(\$conf);
    my $logger = Log::Log4perl->get_logger;
    $Log::Log4perl::DateFormat::GMTIME = 1;
    return $logger;
}

my $logger    = initialize_logger();
my $win32_obj = Win32Operations->new("logger" => $logger);

# This will open the given dir in windows explorer
# Right click on the given filename and open it in Notepad++ and then close it.
my $input_dir          = "C:/Program Files/Notepad++";
my $filename_to_open   = "readme.txt";
my $max_retry_attempts = 5;

$win32_obj->start_explorer_operations($input_dir, $filename_to_open, $max_retry_attempts);
Enter fullscreen mode Exit fullscreen mode

And we are done. You can run this script from your terminal and test this. The full code for reference is also available at Github for your reference.

Perl onion logo taken from here
Windows logo taken from here

The Grants Committee is accepting grant proposals all the time. We evaluate them every two months and another round is starting.

If you have an idea for doing some work that will benefit the Perl or Raku communities, please consider submitting a grant application. The application deadline for this round is 23:59 January 27, 2021, UTC. We will publish the received applications, get community feedback through February 3rd, and we will conclude the process shortly thereafter.

We now accept grant requests for core Perl and Raku development. There are some eligibility requirements that must be met for each language when submitting a grant. For Perl: * The applicant must be a contributor to the Perl core. * The application must be endorsed by one or more people with commit rights to the Perl core.

For Raku: * The applicant must be a contributor to the Raku language specification or one of its implementations. * The application must be endorsed by one or more people in Raku Steering Council.

For more information, see this blog post.

To apply, please read How to Write a Proposal, GC Charter, Rules of Operation and Running Grants List will also help you understand how the grant process works. We also got some grant ideas from the community. Grant applications may be submitted via this Google Form.

We will confirm the receipt of application by January 28th.

If you have further questions, please contact me at tpf-grants-secretary at perl-foundation.org.

Am vorvergangenen Wochenende (9./10. Januar 2021) gab es relativ viel Wirbel um die Plattform Parler, die offensichtlich als Twitterersatz für hauptsächlich amerikanische "Konservative" diente. Amazon Web Services (AWS) kündigte Parler alle Services, da dort jede Menge Hass verbreitet wurde. Bevor alles abgeschaltet war, wurden wohl alle öffentlich verfügbaren Daten über ein API heruntergeladen. Ein paar der Fehler, die bei dem API gemacht wurden, werde ich kurz in der Schulung im März erläutern.
As one of the largest product and price comparison platforms in German-speaking countries, they know all about finding the best products at a price that would make Mozart shout, “Wunderbar!” They want to find a Perl programmer who thrives on delivering quality content, well-executed user information that’s easy to understand, and is as committed to transparency and excellence as they are.
Not all jobs are created equal. Sure, most pay the bills, but some do more. They impart a sense of purpose; when you log out at day’s end, it’s with the satisfaction that you are part of something bigger, something more important than yourself. You’ve left the world a little better than you found it, and isn’t that what life is really about?
With more than 4 million domains spanning nearly every country around the globe, our client manages over 100,000 retail and corporate clients and 2,300 resellers. That’s a whole lot of carts and bags brimming with goodies, which translates into enviable job security and money in your pocket.
The organization whose servers handle a jaw-dropping 80% of the UK’s job ads is looking for a few good humans, and you could be one of them! If you’ve got mad Perl skills and a hankering to mine the sandbox of data waiting for you, grab your shovel and pail and drop us an email!
Online trading is big. Big dividends, big excitement, and big barriers for most people. Enter our client, an international financial company who believe online trading should be open and accessible to all. With a brand that enjoys global recognition and the kind of growth that stokes envy in their competitors, they’re looking to add a few good Perl developers to their expansive team.
It will be in Leipzig on March 24-26 2021. CFP is open.
Thursday, February 4, 2021; 6:30 PM PST
Thursday, January 28, 2021; 7:00 PM EST
Wednesday, January 27, 2021; 7:00 PM GMT+1
Wednesday, January 27, 2021; 6:00 PM EST
Probably you don't care, but this what I try to do on a daily basis, or at least every 2 days to share a little bit of the knowledge I collected during the years and to gain followers that might one day trust me enough to buy a a training course from me.
In which I share my struggle to find the intersection of 'the knowledge I can share' and the 'interest of the people following my platforms'. It is not easy for me.
In this course I am showing how to create multiple processes to increase the speed of both CPU-intensive and IO-intensive applications. In a separate post I wrote a few fun things about the course.
The poll is already closed, but you can still comment on it and you can still follow the Perl Maven LinkedIn page. The results BTW: Testing: 32% - Functional programming: 29% - Web: 20% - OOP: 19%
Luis shows his creative side and shared few fun bits. It makes reading his blog such fun.
Simon using the dev.to platform for blogging is a great way to promote the weekly challenge. Thank you, Simon.
Roger shared Raku's method chaining feature, which is my favourite feature. It has changed the way I look at objects now.
Luca is an inspiration to all PWC members. Thank you for sharing your knowledge with us.
Thank you, Laurent for being a great supporter. As always, he showed both functional and object oriented approaches.
Before I read anything else, I check the section 'The questions' as it explains the technical errors in the task. It really scares me everytime.
Flavio is being a daily-blogging star. Please find yet another powerful discussion about 'Palindrome Number' task.
Thanks, James for promoting the use of CORE::* methods. A simple and easy-to-follow blog post.
Kang-min's solution to 'Demo Stack' is a great example showing Raku power. Highly Recommended.
Colin's blog is not just special for it technical values but also the literary value is super. I enjoy it the most.
Arne once again shared his Perl solutions this week. Arne's unique style of blogging is very catchy. Must Read.
Thanks, Adam for introducing struct in Perl. I have never used it before
Abigail is simply a genius. He found a technical error in the task. You have to check his blog to find it.
Abigail has a style which makes the task even more fun and challenging. Please check out the blog for more fun.
Aaron's running commentary on Raku is a gem. I enjoyed it the most.
Perl Solutions Review by Colin Crain.
Enjoy a quick recap of last week's contributions by Team PWC dealing with the "Palindrome Number" and "Demo Stack" tasks in Perl and Raku. You will find plenty of solutions to keep you busy.
Welcome to a new week with a couple of fun tasks - "Reverse Words" and "Edit Distance". If you are new to the weekly challenge then why not join us and have fun every week. For more information, please read FAQ page.
If you have not seen it yet, check out this Raku course
There are several options available: WWW::Mechanize::Firefox, Firefox::Marionette, and Selenium::Firefox.
The back-end uses Mojolicious::Lite the front-end is Highcharts
A very detailed example using Mojolicious as the Perl framework for the back-end code.
Using Mojolicios
New video course for people who would like to make it safter to change code by writing tests.
Thanks to Dave Crosss this email thread escaped to at least Reddit. Transparency and publicity are much needed in the Perl community. I am also glad there is some movement in the new Perl Steering Council.
The poll I refer to is already closed, but I still would be interested in the topics that are interesting to you.
How do you handle dates as long ago as 50,000 BC? Interesting subject.
Someone who is taking over a senior role in a company was looking for Perl training and advice for on Reddit.
A new identifier sub for the new representation.
Parsing inputs to fit the New representation.
Adopting a new, more compact representation.
On with Advent of Code puzzle 11 from 2016: moving to the A* algorithm.
One of my readers who does not know much Perl was trying to figure out some Perl code and asked for my help. After looking at the code I thought the best way might be if I show a slightly improved version of the code that is at least partially more readable than the original one.
Speaking at FOSDEM? Here's what you need to know
As an author and even just a maintainer, knowing that somebody is using your software is what it is all about. Yes some projects are for scratching your own itch, but when somebody else has the same itch, collaboration is the way to go.
Number of posts last week: BPO: 4; DevTo: 5; Perl.com: 1; PerlAcademy: 0; PerlHacks: 0; PerlMaven: 1; Reddit: 15; TPF: 3;
Last week there were a total of 312 uploads to CPAN of 233 distinct distributions by 81 different authors. Number of distributions with link to VCS: 191. Number of distros with CI: 135. Number of distros with bugtracker: 168.
In 2021, try a whole new Perl ecosystem with:
  • A unified, cloud-based toolchain for Linux & Windows (replaces PPM)
  • Virtual environment support (similar to Python's virtualenv)
  • Pure open source licensing (no more ActiveState license)
  • A new way to install, work with, and even consume Perl in 2021.

    Hi there!

    Thanks to all the people who stepped up and started to support Manwar, but there are still only 21 people. I am sure a lot more of you could afford it. Not because he needs it, but because you'd like to show that you value his work. Both as the editor of the Perl Weekly and as the person behind the Weekly Challenge for Perl and Raku. There aren't many people who are making public efforts for Perl. Let's show them, and the rest of the world, that there are people who value and support his efforts!

    Perl and other online Courses

    For a long time, I was struggling trying to get monthly subscribers to my site, but the site did not work well. I did not have enough new content and many people told me they'd prefer a one-time payment instead of a monthly fee. So I was really glad when I finally understood how I can do this on LeanPub. I published a Python course there and in December 2020 I recorded a Perl Dancer course. However I reached the conclusion that the LeanPub platform is not really good for the heavily video-oriented courses I created. Luckily at the end of December I found Teachable and re-published my courses there. I got so enthusiastic that I started to record five other courses.

    If you visit the Code Maven courses you see that there are already seven courses available. There are also two bundles to make it easier and more economical to buy all the courses.

    Enjoy your week

    The examples used here are from the weekly challenge problem statement and demonstrate the working solution.

    Part 1

    You are given a number $N. Write a script to figure out if the given number is a Palindrome. Print 1 if true, otherwise 0.

    Solution

    
    use strict;
    use warnings;
    
    use boolean;
    
    sub is_palindrome{
        my($n) = @_;
        return false if $n < 0;
        my @digits = split(//, $n);
        if(@digits % 2 == 0){
            do{
                my $a = shift @digits;
                my $b = pop @digits;
                return false if $a != $b;
            }while(@digits);
            return true;
        }
        while(@digits != 1){
            my $a = shift @digits;
            my $b = pop @digits;
            return false if $a != $b;
        };
        return true;
    }
    
    MAIN:{
        print is_palindrome(1221);
        print "\n";
        print is_palindrome(-101);
        print "\n";
        print is_palindrome(90);
        print "\n";
    }
    

    Sample Run

    
    $ perl perl/ch-1.pl
    1
    0
    0
    

    Notes

    One assumption is made and that is that the input is a valid integer.

    My approach here is straightforward iteration and matches what one might do manually: work inwards from both ends and if at any point there is not a match of the two elements being compared then return false. If we make it all the way to the middle then return true. Here the middle is either an empty array, in the case of an even number of elements or, in the case of an odd number of elements, an array of length 1.

    The case of a single digit has no special handling, if the number has an odd number of digits but that odd number happens to be 1 then the loop is not entered and we just return true.

    Part 2

    Write a script to demonstrate Stack operations.

    Solution

    
    use strict;
    use warnings;
    
    use Stack;
    
    my $stack = new Stack();
    $stack->push(2);
    $stack->push(-1);
    $stack->push(0);
    $stack->pop;       
    print $stack->top . "\n"; 
    $stack->push(0);
    print $stack->min . "\n"; 
    

    The Stack module used is of my own making. The next listing is that code.

    
    use strict;
    use warnings;
    package Stack{
        use boolean;
        use Class::Struct;
    
        struct(
            data => q/@/
        );
    
        sub push{
            my($self, $n) = @_;
            push @{$self->data()}, $n;
        }
    
        sub pop{
            my($self, $n) = @_;
            pop @{$self->data()};
        }
    
        sub top{
            my($self, $n) = @_;
            @{$self->data()}[@{$self->data()} - 1];
        }
        
        sub min{
            my($self, $n) = @_;
            my @sorted = sort {$a <=> $b} @{$self->data()};
            return $sorted[0];
        }
        true;
    }  
    

    Sample Run

    
    $ perl -Iperl perl/ch-2.pl
    -1
    -1
    

    Notes

    Like last week’s LinkedList module I use Class::Struct to create the Stack module.

    Class::Struct creates accessors for all the class variables automatically. In this way, by calling $self->data(), we get a reference to the internal array data and perform the required Stack operations.

    This is a monthly report by Tony Cook on his grant under Perl 5 Core Maintenance Fund. We thank the TPF sponsors to make this grant possible.

    Approximately 16 tickets were reviewed, and 4 patches were
    applied
    
    [Hours]         [Activity]
      2.62          #1420 debugging, research
                    #1420 research and comment, some work on a fix
      5.55          #17094 write up an approach
                    #17094 research, more write up
                    #17094 more editing, post
                    #17094 follow-up
      0.27          #17673 review and close
      2.03          #17724 testing, add regression test, apply to blead and
                    close PR
      0.57          #17906 rebase, testing and apply to blead
     10.28          #18005 hopefully final rebase and test for merge,
                    reconsider PathTools PerlLIO_symlink/readlink, research
                    #18005 add symlink/readlink aliases, look into
                    Devel::PPPort for new PerlLIO_* functions, don’t think
                    it’s needed, apply to blead
                    #18005 follow up: issue management, perldelta, add tests
                    for other possibly fixed issues (some were fixed, others
                    weren’t)
                    #18005 more testing, debug #7410 and comment
                    #18005 finish up and apply new regression tests to blead,
                    more work on perldelta
                    #18005 more ticket management, finish perldelta and apply
                    to blead
      0.55          #18341 discussion with khw
      0.23          #18365 comment
      6.18          #18380 debugging
                    #18380 more debugging, long comment
                    #18380 think about a fix, work on a KISS fix for the
                    immediate problem, testing
                    #18380 more testing.make PR 18390, comment on more
                    complete solutions
      2.00          #18388 research, testing, briefly comment
                    #18388 review discussion, research
      3.85          #18396 review, track down issue with RW mutexes
                    #18396 irc discussion with khw, work on a PR with test and
                    fix, back to review
                    #18396 follow-up
      2.15          #18402 review and research
                    #18402 more research and comment
      0.12          #18403 review and apply to blead
      0.80          review github notifications
                    review github notifications
      0.33          review khw’s alternate patch for the PERL_MEM_LOG
                    deadlock/potential getenv() corruption
      0.58          review khw’s khw-safe branch, research
    ======
     38.11 hours total
    
    Updates for great CPAN modules released last week. A module is considered great if its favorites count is greater or equal than 12.

    1. Alien::Build - Build external dependencies for use in CPAN
      • Version: 2.38 on 2021-01-11
      • Votes: 19
      • Previous version: 2.37 was 2 months, 9 days before
    2. Catmandu - a data toolkit
      • Version: 1.2014 on 2021-01-15
      • Votes: 21
      • Previous version: 1.2013 was 6 months, 6 days before
    3. Crypt::JWT - JSON Web Token
      • Version: 0.031 on 2021-01-10
      • Votes: 15
      • Previous version: 0.030 was 2 days before
    4. GraphViz2 - A wrapper for AT&T's Graphviz
      • Version: 2.65 on 2021-01-09
      • Votes: 12
      • Previous version: 2.64 was 8 days before
    5. Kelp - A web framework light, yet rich in nutrients.
      • Version: 1.03 on 2021-01-12
      • Votes: 42
      • Previous version: 1.02 was 3 years, 8 days before
    6. Module::ScanDeps - Recursively scan Perl code for dependencies
      • Version: 1.30 on 2021-01-13
      • Votes: 13
      • Previous version: 1.29 was 4 months, 28 days before
    7. PAR - Perl Archive Toolkit
      • Version: 1.017 on 2021-01-13
      • Votes: 17
      • Previous version: 1.016 was 1 year, 7 months, 24 days before
    8. PAR::Packer - PAR Packager
      • Version: 1.052 on 2021-01-13
      • Votes: 38
      • Previous version: 1.051 was 1 month, 14 days before
    9. Perl::Tidy - indent and reformat perl scripts
      • Version: 20210111 on 2021-01-10
      • Votes: 120
      • Previous version: 20201207 was 1 month, 4 days before
    10. Image::Magick - objected-oriented Perl interface to ImageMagick. Use it to create, edit, compose, or convert bitmap images from within a Perl script.
      • Version: v6.9.11 on 2021-01-16
      • Votes: 18
      • Previous version: 6.89-1 was 6 years, 3 months, 25 days before
    11. PerlPowerTools - BSD utilities written in pure Perl
      • Version: 1.022 on 2021-01-11
      • Votes: 27
      • Previous version: 1.020 was 3 months, 26 days before
    12. Role::Tiny - Roles: a nouvelle cuisine portion size slice of Moose
      • Version: 2.002003 on 2021-01-11
      • Votes: 62
      • Previous version: 2.001004 was 1 year, 2 months, 17 days before
    13. Sys::Virt - libvirt Perl API
      • Version: v7.0.0 on 2021-01-15
      • Votes: 15
      • Previous version: v6.10.0 was 1 month, 14 days before
    14. Type::Tiny - tiny, yet Moo(se)-compatible type constraint
      • Version: 1.012001 on 2021-01-10
      • Votes: 124
      • Previous version: 1.012000 was 2 months, 13 days before
    15. URI - Uniform Resource Identifiers (absolute and relative)
      • Version: 5.06 on 2021-01-14
      • Votes: 103
      • Previous version: 5.05 was 2 months, 24 days before

    This is the weekly favourites list of CPAN distributions. Votes count: 61

    Week's winner: Mojolicious (+2)

    Build date: 2021/01/16 22:35:36 GMT


    Clicked for first time:


    Increasing its reputation:

    Starting in 2021, the Grants Committee will be taking on an expanded role in supporting the development of Perl and Raku within The Perl Foundation. In addition to the usual set of grants that benefit the greater Perl and Raku communities, the Grants Committee will also be the caretakers of the Perl and Raku Development Funds, and therefore responsible for awarding grants that directly benefit the core development of these languages. Prior to this, Perl and Raku development funds were managed and awarded through a separate process that interacted directly with the TPF Board of Directors.

    While the eligibility requirements for Perl and Raku development grants are different, they will go through the same process that other grants go through: each grant request will be published publicly; a period of public discussion will take place, during which time, applicants may be asked to provide additional information or make other changes before the voting period begins; finally, there is a voting period, after which the results are publicly posted.

    So what are the eligibility requirements?

    Since grants involving these funds are very focused and targeted, each has a specific set of requirements that must be met before being considered for discussion and funding. For Perl: * The applicant must be a contributor to the Perl core. * The application must be endorsed by one or more people with commit rights to the Perl core.

    In the case of Raku: * The applicant must be a contributor to the Raku language specification or one of its implementations. * The application must be endorsed by one or more people in Raku Steering Council.

    For more information, check out the Perl Core Development Fund and Raku Development Fund pages.

    What are the benefits of this process?

    All grant requests now follow the same, consistent progress. Every grant request, regardless of what it's for will always be publicly posted and discussed in the open. The community will have a say in the decision making process, and will get to see how donations and grant money are allocated and awarded for the betterment of the Perl and Raku languages.

    How can I get involved?

    The next call for grants is coming up this week! The Grants Committee maintains a list of possible grant ideas, or you can propose your own ideas. The Perl and Raku communities grow from your involvement. Submit your request and let's get something positive accomplished!


    This is a monthly report by Andrew Shitov on his grant for the Raku course.

    Andrew is happy to report that the first part of the Raku course is completed and published. The course is available at course.raku.org.

    The grant was approved a year and a half ago right before the PerlCon conference in Rīga. Andrew was the organiser of the event and had to postpone the course due to high load. Also note that during the conference, it was proposed to rename Perl 6 which also lead to some confusion and wait before working on the grant.

    After months, the name was settled, the distinction between Perl and Raku became clearer, and, more importantly, external resourses and services, e.g., Rosettacode and glot.io started using the new name.

    It's now a perfect timing to start working on the course Andrew dreamed about a couple of years ago. Andrew started the main work in the middle of November 2020, and by the beginning of January 2021, he had the first part ready.

    The current plan includes five parts:

    1. Raku essentials
    2. Advanced Raku subjects
    3. Object-oriented programming in Raku
    4. Regexes and grammars
    5. Functional, concurrent, and reactive programming

    It differs a bit from the original plan published in the grant proposal. While the material stays the same, Andrew decided to split it differently. Initially, he was going to go through all the topics one after another. Now, the first sections reveal the basics of some topics, and we will return to the same topics on the next level in the second part.

    For example, in the first part, Andrew only talk about the basic data types: Int, Rat, Num, Str, Range, Array, List, and Hash and basic usage of them. The rest, including other types (e.g., Date or DateTime) and the methods such as @array.rotate or %hash.kv is delayed until the second part.

    Contrary, functions were a subject of the second part initially, but they are now discussed in the first part. So, we now have Part 1 “Raku essentials” and Part 2 “Advanced Raku topics”. This shuffling allowed Andrew to create a liner flow in such a way that the reader can start writing real programs already after they finish the first part of the course.

    It is quite a tricky task to organise the material without backward links. In the ideal course, any topic may only be based on the previously explained information. A couple of the most challenging cases were ranges and typed variables. They both causes a few chicken-and-egg loops.

    During the work on the first part, Andrew also prepared a ‘framework’ that generates the navigation through the site and helps with quiz automation.

    It is hosted as GitHub Pages and uses Jekyll and Liquid for generating static pages, and a couple of Raku programs to automate the process of adding new exercises and highlighting code snippets. Syntax highlighting is done with Pygments.

    Returning the to course itself, it includes pages of a few different types:

    • The theory that covers the current topic
    • Interactive quizzes that accomplish the theory of the topic and/or the section
    • Exercises for the material of the whole section
    • Answers to the exercises

    The quizzes were not part of the grant proposal, but Andrew think they help making a better user experience. All the quizzes have answers and comments. All the exercises are solved and published with the comments to explain the solution, or even to highlight some theoretical aspects.

    The first part covers 91 topics and includes 73 quizzes and 65 exercises (with 70 solutions :-). There are about 330 pages in total. The sources are kept in a GitHub repository github.com/ash/raku-course, so people can send pull requiest, etc.

    At this point, the first part is fully ready. Andrew could adjust and update that first part if the following parts require additional information about the topics covered in Part 1.

    On behalf of the Perl Foundation, I want to thanks Andew to provide to the community such a great online resource to learn raku online. If you missed it, have a look at the online course at course.raku.org.

    Recently I encountered a Perl script that had some issues. e.g. Lack of use strict.

    Let me show a quick refactoring of it:

    Speaking at FOSDEM? Here’s what you need to know

    Thank you to all speakers who have put themselves forward to speak at this year’s FOSDEM.

    As it’s a virtual event, all talks need to be pre-recorded by the speakers. This is the first time FOSDEM has operated in this way so please be aware that there may be some bumps along the way - everyone is working this out as they go!

    On the day your presentation is aired, you will be required to join an online Matrix chatroom with delegates as they ask questions during your talk - further details below. Afterwards, you will also be able to answer questions in a live video conference room.

    To make sure you, and anyone listening or asking questions, get the best out of this event, here are some guidance notes to support your planning.

    Perl Devroom video's should be submitted by 22/01/2021

    Deadlines

    All pre-recorded talks are expected to be uploaded by 17th January 2021 to Pentabarf - the upload link should be available by the time you read this, if you don't have it, contact your devroom organiser. After this date, there is one week scheduled for you to review and if necessary, to re-upload to fix problems.

    Please do not plan to run right up against these deadlines - you and FOSDEM need the contingency for things that genuinely go wrong.

    UPDATE: Recordings will be locked in on 27th January ready for FOSDEM team to test during the weekend

    Recording your talk

    You can use any tool that you prefer to record your presentation. Open Broadcast Studio (OBS) is a powerful tool and recommended by many speakers. Some useful documents here:

    Another option is to deliver your presentation through a video conferencing platform to a colleague who can then record the session. Either use the built-in recording of the conferencing platform or ask your colleague to remotely record the presentation.

    For example, you could use Jitsi to present your talk to a colleague, with picture-in-picture mode so that the speaker is in the corner of the slides, and, on Linux, use Simple Screen Recorder (SSR) to capture the talk in the appropriate format.

    Please bear in mind these tips for a good quality presentation:

    • Test your recording process first to make sure both video and sound are captured in the recording
    • Make sure the video fits comfortably into your time slot - FOSDEM is on a strict time schedule and you will be cut short if you over-run
    • Don’t overload your slides. Make sure everything is readable when scaled down slightly
      • The 1280x720 video you send in may be scaled down to fit onto a FOSDEM template
    • Make sure you use a good microphone for your presentation, ideally a headset
    • Have a suitable background during recording and live Q&A
    • Dress for the occasion

    Uploading videos

    Videos should be uploaded to https://penta.fosdem.org (through your account). Technical requirements for your presentation video are:

    • resolution: 1280x720
    • frame rate: 25 fps
    • video codec: h264 video codec, main profile
    • video bitrate: <= 2Mbit/s
    • audio codec: aac audio codec
    • audio sample rate: 48 KHz mono
    • audio bitrate: 128 Kbit/s
    • media container: whatever is easiest for you

    When you upload your video, the system will verify that it meets these requirements. If it does not, the video will be transcoded first (but this will take longer).

    A 'review' link will be available so you can confirm that you have uploaded the correct video.

    If you want to change the video, you can overwrite by uploading a replacement. However once you have confirmd the video, you can't upload it without FOSDEM resetting your confirmation. This enables them to take control of last-minute changes.

    Bernard Tyers has created a slide share showing exactly how the upload process works.

    Update 15/01/2021

    At the time of writing, upload/review buttons are only available on the submission interface and not in the main admin section.

    The 'Review' link is not easy to find - look for the following dialogue box at the bottom of the submission page, and locate the review link:

    Note that Duration is the proposed length of your session in the live stream combining both pre-recorded talk and a short amount of live Q&A.

    The presentation length is the length of the talk recording. This will be updated to the exact length of the recording when the video is processed.

    Extended Q&A will be offered through break-out rooms after the pre-recorded video and Q&A are broadcast.

    Q&A sessions

    Your Q&A session will follow your pre-recorded video. If your timeslot is 10:00 - 10:30, for example, your video will be played out over the stream from 10am. There will be an associated text chatroom where the speaker, host and audience can talk. This means any questions can be answered directly whilst the video plays.

    When the video completes, for example at 21 minutes, 30 seconds, the live stream will automatically switch over to a live video room with the speaker and speaker host. The speaker host will lead a live Q&A/discussion on video, asking questions that have been upvoted in the text chat or asking questions of their own, clarifying anything that may not have been clear.

    Just before the 10:30 close of the session, the live stream will end and if any of the audience want to continue the Q&A/discussion, they will be directed to the right place to go do that.

    After the talk closes at 10:30, and the next one begins, you can continue with your extended Q&A in a more open, informal fashion than during the session itself.

    Real-time chat

    The Matrix platform has been selected for the real-time organisers chat during the event. FOSDEM have released an article explaining the rationale here. Note that there is also a bridge for IRC and XMPP / Jabber users.

    If you are a devroom organiser / host / moderator, you will need to join the chat through one of these connected channels on the day to process Q&A and communicate with FOSDEM staff.

    Hosts and coordinators

    All speakers must ensure they have a host and a coordinator to support their talk.

    The coordinator is the devroom manager and the host will work with the speaker to ensure they upload a suitable presentation, as well as host the live Q&A session. Hosts only need a normal self-created pentabarf account, like a speaker (not a devroom manager). Adding them to the event with the ‘host’ role gives them access to what they need.

    Speakers, devroom managers and hosts can see the events you are listed against at the ‘Own Events’ link and upload videos will be available here.

    Please make sure that hosts and coordinators are not taking part in a talk before or after your own – otherwise there could be problems with overlap during Q&A sessions outside of the presentation time. Remember, your Q&A can continue in a separate live video conference after your talk has ended.

    Further support

    Now I want to grab the entire list of O’Reilly cover animals, and Mojolicious is going to help me do that.

    O’Reilly Media, who publishes most of my books, is distinctively known by the animals it chooses for their covers. Edie Freedman explains how she came up with the well-known design in A short history of the O’Reilly animals. I think I first saw this design on the cover of sed & awk; those Slender Lorises (Lori?) are a bit creepy, but not creepy enough to keep me away from the command line.

    sed & awk

    Not that a Perler should talk since Larry Wall choose a camel: it’s ugly but it gets the job done under tough conditions. And, for own of my own books, the alpaca is a bit cuter, but they are nasty beasts as well.

    O’Reilly lists almost all of the animals from their covers, even if “animals” is a bit of a loose term that encompasses “Catholic Priests” (Ethics of Big Data) or “Soldiers or rangers, with rifles” (SELinux). You can page through that list 20 results at a time, or search it. But, as with most lists I see online, I want to grab the entire list at once. Show me a paginated resources and I’ll show you the program I automated to unpaginate it.

    Scraping a bunch of pages is no problem for Perl, especially with Mojolicious (as I write about in Mojo Web Clients). I whipped up a quick script and soon had all of the animals in a JSON file.

    There’s nothing particularly fancy in my programming, although I do use Mojo::Promise so I can make the requests concurrently. That wasn’t something that I cared that much about, but I had just answered a StackOverflow question about Promises so it was on my mind. I set up all of the web requests but don’t run them right away. Once I have all of them, I run them at once through the all() Promise:

    #!perl
    use v5.26;
    use experimental qw(signatures);
    
    use Mojo::JSON qw(encode_json);
    use Mojo::Promise;
    use Mojo::UserAgent;
    use Mojo::Util qw(dumper);
    
    my @grand;
    END {
    	# Since the results come out of order,
    	# sort by animal name then title
    	@grand = sort {
    		$a->{animal} cmp $b->{animal}
    			or
    		$a->{title} cmp $b->{title}
    		} @grand;
    
    	my $json = encode_json( \@grand );
    	say $json;
    	}
    
    my $url = 'https://www.oreilly.com/animals.csp';
    my( $start, $interval, $total );
    
    my $ua = Mojo::UserAgent->new;
    
    # We need to get the first request to get the total number of
    # requests. Note that that number is actually larger than the
    # number of results there will be, by about 80.
    my $first_page_tx = $ua->get_p( $url )->then(
    	sub ( $tx ) {
    		push @grand, parse_page( $tx )->@*;
    		( $start, $interval, $total ) = get_pagination( $tx );
    		},
    	sub ( $tx ) { die "Initial fetch failed!" }
    	)->wait;
    
    my @requests =
    	map {
    		my $page = $_;
    		$ua->get_p( $url => form => { 'x-o' => $page } )->then(
    			sub ( $tx ) { push @grand, parse_page( $tx )->@* },
    			sub ( $tx ) { warn "Something is wrong" }
    			);
    		}
    	map {
    		$_ * $interval
    		}
    	1 .. ($total / $interval)
    	;
    
    Mojo::Promise->all( @requests )->wait;
    
    sub get_pagination ( $tx ) {
    	# 1141 to 1160 of 1244
    	my $pagination = $tx
    		->result
    		->dom
    		->at( 'span.cs-prevnext' )
    		->text;
    
    	my( $start, $interval, $total ) = $pagination =~ /
    		(\d+) \h+ to \h+ (\d+) \h+ of \h+ (\d+) /x;
    	}
    
    sub parse_page ( $tx ) {
    =pod
    
    <div class="animal-row">
        <a class="book" href="..." title="">
          <img class="book-cvr" src="..." />
          <p class="book-title">Perl 6 and Parrot Essentials</p>
        </a>
        <p class="animal-name">Aoudad, aka Barbary sheep</p>
      </div>
    
    =cut
    
    	my $results = eval {
    		$tx
    			->result
    			->dom
    			->find( 'div.animal-row' )
    			->map( sub {
    				my %h;
    				$h{link}      = $_->at( 'a.book' )->attr( 'href' );
    				$h{cover_src} = $_->at( 'img.book-cvr' )->attr( 'src' );
    				$h{title}     = $_->at( 'p.book-title' )->text;
    				$h{animal}    = $_->at( 'p.animal-name' )->text;
    				\%h;
    				} )
    			->to_array
    		} or do {
    			warn "Could not process a request!\n";
    			[];
    			};
    	}

    Those concurrent requests make this program much faster than it would be if I did them individually one after the other, although it can really hammer a server if I’m not careful. Most of the web request time is simply waiting and I get all of those requests to wait at the same time. Now, this isn’t really parallelism because once one request has something to do, such as reading the data, the other requests still need to wait their turn. Perhaps I’ll rewrite this program later to use Minion, the Mojo-based job queue that can do things in different processes.

    The rest of the program is data extraction. In parse_page, I have various CSS Selectors to extract all of the div.animal-row and turn each animal into a hash (again, I have lots of examples in Mojo Web Clients). Each Promise adds its results to the @grand array. At the end, I turn that into a JSON file, which I’ve also uploaded as a gist.

    As someone who has been doing this sort of extraction for quite a while, I’m always quite pleased how easy Mojolicious makes this. Everything I need is already there, uses the same idioms, and works together nicely. I get the page and select some elements. A long time ago, I would have had long series of substitutions, regexes, and other low-level text processing. Perl’s certainly good at text processing, but that doesn’t mean I want to work at that level in every program. Do something powerful a couple times and it doesn’t seem so cool anymore, although the next step for Mojolicious might be Minority Report-style pre-fetching where it knows what I want before I do.

    A nifty trick

    I do use a few interesting tricks just because I do. Lately in these sorts of programs I’m collecting things into a data structure then presenting it at the end. Typically that means I do the setup at the top of the program file and the output at the end. However, after I’ve defined the @grand variable, I immediately define an END block to specify what to do with @grand once everything else has happened:

    my @grand;
    END {
    	# Since the results come out of order,
    	# sort by animal name then title
    	@grand = sort {
    		$a->{animal} cmp $b->{animal}
    			or
    		$a->{title} cmp $b->{title}
    		} @grand;
    
    	my $json = encode_json( \@grand );
    	say $json;
    	}

    That keeps the details of the data structure together. The entire point of the program is to get those data out to the JSON file.

    I could have just as easily kept that together with a normal Perl subroutine, but END is a subroutine that I don’t need to call explicitly. This is merely something I’ve been doing lately and I might change my mind later.

    A little safari

    And I leave you with a little safari for your own amusement. My animals are the Llama, Alpaca, Vicuñas, Camel, and Hamadryas Butterfly. Search the O’Reilly list (or my JSON) to find those titles. Some of them are missing and some have surprising results.

    Here are some interesting jq commands to play with the Animals JSON file:

    # get all the title
    $ jq -r '.[].title' < animals.json | sort | head -10
    .NET & XML
    .NET Compact Framework Pocket Guide
    .NET Framework Essentials
    .NET Gotchas
    .NET Windows Forms in a Nutshell
    20 Recipes for Programming MVC 3
    20 Recipes for Programming PhoneGap
    21 Recipes for Mining Twitter
    25 Recipes for Getting Started with R
    50 Tips and Tricks for MongoDB Develope
    
    # tab-separated list of animals and titles
    $ jq -r '.[] | "\(.animal) => \(.title)"' < animals.json | sort
    12-Wired Bird of Paradise	Mobile Design and Development
    3-Banded Armadillo	Windows PowerShell for Developers
    Aardvark	Jakarta Commons Cookbook
    Aardwolf	Clojure Cookbook
    Addax, aka Screwhorn Antelope	Ubuntu: Up and Running
    Adjutant (Storks)	Social eCommerce
    Aegina Citrea, narcomedusae, jellyfish	BioBuilder
    African Civet	JRuby Cookbook
    African Crowned Crane aka Grey Crowned Crane	C# 5.0 Pocket Reference
    African Crowned Crane aka Grey Crowned Crane	Programming C# 3.0
    
    # find a title by exact match of animal
    $ jq -r '.[] | select(.animal=="Llama") | .title' < animals.json
    Randal Schwartz on Learning Perl
    
    # find a title with a regex match against the animal
    $ jq -r '.[] | select(.animal|test("ama")) | .title' < animals.json | sort
    Access Cookbook
    Access Database Design & Programming
    ActionScript for Flash MX Pocket Reference
    ActionScript for Flash MX: The Definitive Guide
    Ajax on Java
    Appcelerator Titanium: Up and Running
    Embedding Perl in HTML with Mason
    Fluent Python
    Identity, Authentication, and Access Management in OpenStack
    Introduction to Machine Learning with Python
    Learning Perl 6
    PDF Explained
    Randal Schwartz on Learning Perl
    SQL Pocket Guide
    SQL Tuning
    Solaris 8 Administrator's Guide
    The Little Book on CoffeeScript
    Writing Game Center Apps in iOS
    
    # find an animal with a regex match against the title
    $ jq -r '.[] | select(.title|test("Perl")) | .animal' < animals.json | sort
    Alpaca
    Aoudad, aka Barbary sheep
    Arabian Camel, aka Dromedary
    Arabian Camel, aka Dromedary
    Arabian Camel, aka Dromedary, Head
    Badger
    Bighorn Sheep
    Black Leopard
    Blesbok (African antelope)
    Camel, aka Dromedary
    Cheetah
    Emu, large and fluffy
    Emu, young
    Fan-footed Gecko, aka Wall Gecko
    Flying Dragon (lizard)
    Flying Dragon (lizard)
    Greater Honeyguide
    Green Monkey 1 (adult holding a baby)
    Hamadryas Baboon
    Hamadryas Butterfly
    Llama
    Mouse
    North American Bullfrog
    Proboscis Monkey
    Red Colobus Monkey
    Sea Otter
    Staghound
    Tadpole of a Greenfrog (sketch)
    Thread-winged Lacewing, aka Antlion
    White-tailed Eagle
    Wolf
    

    Part 1

    You are given an array of strings @S. Write a script to group Anagrams together in any random order.

    Solution

    
    use strict;
    use warnings;
    my %letter_factor = (
        e => 2,
        t => 3,
        a => 5,
        o => 7,
        i => 11,
        n => 13,
        s => 17,
        h => 19,
        r => 23,
        d => 29,
        l => 31,
        c => 37,
        u => 41,
        m => 43,
        w => 47,
        f => 53,
        g => 59,
        y => 61,
        p => 67,
        b => 71,
        v => 73,
        k => 79,
        j => 83,
        x => 89,
        q => 97,
        z => 101  
    );  
    
    MAIN:{
        my $word;
        my %anagrams;
        while($word = ){
            chomp($word); 
            my @letters = split(//, $word);
            my $word_product = 1;
            map {$word_product *= $_} map{$letter_factor{$_}} @letters;
            push @{$anagrams{$word_product}} , $word if $anagrams{$word_product};  
            $anagrams{$word_product} = [$word] unless $anagrams{$word_product};  
        }  
        close(DATA);
        print "Organized anagrams:\n";
        for my $key (keys %anagrams){
            print "  "; 
            for my $word (@{$anagrams{$key}}){
                print "$word ";
            }
            print "\n";
        }   
    }   
    
    __DATA__
    opt
    bat
    saw
    tab
    pot
    top
    was
    

    Sample Run

    
    $ perl ch-1.pl
    Organized anagrams:
      saw was 
      bat tab 
      opt pot top 
    

    Notes

    • I am using the same mathematical trick that I have used for anagrams in the past, starting with Challenge 005. The By the Fundamental Theorem of Arithmetic every integer greater than 1 is either a prime number itself or can be represented as the unique product of prime numbers. We use that to our advantage by having a prime number associated with each letter. Each word is a product of these numbers and words with the same product are anagrams.

    • In this way we build a hash keyed by word product whose values are list of anagrams. After constructing this data structure we then just print out the contents of all the lists.

    • The choice of letters and prime numbers is based on the Lewand Ordering and it isn’t at all necessary but it does little harm so I left it in anyway.

    Part 2

    You are given a binary tree. Write a script to represent the given binary tree as an object and flatten it to a linked list object. Finally, print the linked list object.

    Solution

    
    use strict;
    use warnings;
    
    use Graph;
    use LinkedList;
    
    sub build_linked_list{ 
        my($tree) = @_;
        my $linked_list = new LinkedList(); 
        my @paths = build_paths($tree);
        my $root = $paths[0]->[0]; 
        my $next = $linked_list->insert($root, undef); 
        for my $path (@paths){
            for my $node (@{$path}){
                $next = $linked_list->insert($node, $next) if !$linked_list->in_list($node);    
            }
        } 
        return $linked_list;
    }
    
    sub build_paths {
       my ($graph) = @_;
       my @paths;
       local *_helper = sub{
          my $v = $_[-1];
          my @successors = $graph->successors($v);
          if(@successors){
             _helper(@_, $_) for @successors;
          } 
          else{
             unshift @paths, [@_];
          }
       };
       _helper($_) for $graph->source_vertices();
       return @paths;
    }
    
    MAIN:{
        my $Tree;
        $Tree = new Graph();
        $Tree->add_vertices(1, 2, 3, 4, 5, 6, 7);
        $Tree->add_edge(1, 2);
        $Tree->add_edge(1, 3);
        $Tree->add_edge(2, 4);
        $Tree->add_edge(2, 5);
        $Tree->add_edge(5, 6);
        $Tree->add_edge(5, 7);
        print build_linked_list($Tree)->stringify();
    }
    

    The LinkedList module used is of my own making. I am using a somewhat modified version of the LinkedList module I made for Challenge 059. Next is what that code looks like.

    
    use strict;
    use warnings;
    package LinkedList{
        use boolean;
        use Tie::RefHash;
        use Class::Struct;
        package Node{
            use Class::Struct;
    
            struct(
                data => q/$/,
                next => q/Node/
            );
        }
    
        struct(
            head => q/Node/
        );
    
        sub stringify{
            my($self) = @_;
            my $s = "";
            my $next = $self->head()->next();
            while($next && $next->next()){
                $s .= " -> " if $s; 
                $s = $s . $next->data();
                $next = $next->next();
            }
            $s = $s . " -> " . $next->data() if $next->data();
            $s .= "\n"; 
            return $s;
        }
    
        sub insert{
            my($self, $data, $previous) = @_;
            if(!$previous){
                $previous=new Node(data => undef, next => undef);
                $self->head($previous);
            }
            my $next=new Node(data => $data, next => undef);
            $previous->next($next);
            return $next;
        }
    
        sub in_list{
            my($self, $k) = @_;
            my $previous = $self->head();
            my $next = $self->head()->next();
            tie my %node_value, "Tie::RefHash";
            while($next){
                return true if($next->data() == $k); 
                $next = $next->next(); 
            }
            return false;
        }
        true;
    }
    

    Sample Run

    
    $ perl -I. ch-2.pl
    1 -> 2 -> 4 -> 5 -> 6 -> 7 -> 3
    

    Notes

    • The Depth First Search (DFS) code for building the paths is the same as last week.

    • After the DFS returns all the paths they are simply inserted into the list.

    • My LinkedList module is one of my favorite uses of Class::Struct.

    • My write up for Challenge 059 has some more notes on this LinkedList.pm.

    References

    Lewand Ordering

    Fundamental Theorem of Arithmetic

    Updates for great CPAN modules released last week. A module is considered great if its favorites count is greater or equal than 12.

    1. App::cpm - a fast CPAN module installer
      • Version: 0.997000 on 2021-01-08
      • Votes: 51
      • Previous version: 0.996 was 1 month, 2 days before
    2. App::TimeTracker - time tracking for impatient and lazy command line lovers
      • Version: 3.008 on 2021-01-05
      • Votes: 14
      • Previous version: 3.007 was 8 days before
    3. Crypt::JWT - JSON Web Token
      • Version: 0.030 on 2021-01-08
      • Votes: 15
      • Previous version: 0.029 was 6 months, 16 days before
    4. HTTP::Message - HTTP style message (base class)
      • Version: 6.27 on 2021-01-05
      • Votes: 59
      • Previous version: 6.26 was 3 months, 25 days before
    5. IO - Perl core IO modules
      • Version: 1.45 on 2021-01-04
      • Votes: 52
      • Previous version: 1.42 was 11 months, 15 days before
    6. Compress::Zlib - IO Interface to compressed data files/buffers
      • Version: 2.100 on 2021-01-07
      • Votes: 13
      • Previous version: 2.096 was 5 months, 7 days before
    7. LWP - The World-Wide Web library for Perl
      • Version: 6.52 on 2021-01-07
      • Votes: 149
      • Previous version: 6.51 was 9 days before
    8. Minion - Job queue
      • Version: 10.15 on 2021-01-03
      • Votes: 80
      • Previous version: 10.14 was 2 months, 9 days before
    9. Mojo::Pg - Mojolicious ♥ PostgreSQL
      • Version: 4.23 on 2021-01-03
      • Votes: 66
      • Previous version: 4.22 was 1 month, 27 days before
    10. Mojolicious::Plugin::Status - Mojolicious server status
      • Version: 1.13 on 2021-01-03
      • Votes: 12
      • Previous version: 1.12 was 6 months, 21 days before
    11. Net::LDAP - LDAP client library
      • Version: 0.68 on 2021-01-03
      • Votes: 15
      • Previous version: 0.67 was 8 days before
    12. RapidApp - Turnkey ajaxy webapps
      • Version: 1.3401 on 2021-01-08
      • Votes: 27
      • Previous version: 1.3400 was 22 days before
    13. Selenium::Remote::Driver - Perl Client for Selenium Remote Driver
      • Version: 1.39 on 2021-01-08
      • Votes: 41
      • Previous version: 1.38 was 2 months, 20 days before
    14. Test::LeakTrace - Traces memory leaks
      • Version: 0.17 on 2021-01-05
      • Votes: 14
      • Previous version: 0.16 was 3 years, 6 months, 17 days before
    15. Test::MockModule - Override subroutines in a module for unit testing
      • Version: v0.176.0 on 2021-01-05
      • Votes: 14
      • Previous version: v0.175.0 was 3 months, 18 days before
    16. Zydeco - Jazz up your Perl
      • Version: 0.613 on 2021-01-09
      • Votes: 15
      • Previous version: 0.612 was 2 months, 30 days before

    This is the weekly favourites list of CPAN distributions. Votes count: 62

    Week's winner: Mojolicious (+3)

    Build date: 2021/01/09 21:32:29 GMT


    Clicked for first time:


    Increasing its reputation:

    Git ist eine weit verbreitete Software zur Versionsverwaltung. Wir nutzen Git seit vielen Jahren, um unseren Perl-Code zu verwalten. Soll im Git-Workflow etwas erzwungen werden, kommen sogenannte Git-Hooks zum Einsatz.

    Part 1

    You are given set of co-ordinates @N. Write a script to count maximum points on a straight line when given co-ordinates plotted on 2-d plane.

    Solution

    
    use strict;
    use warnings;
    ##
    # You are given set of co-ordinates @N.
    # Write a script to count maximum points 
    # on a straight line when given co-ordinates 
    # plotted on 2-d plane.
    ##
    sub triangle_area{
        my($i, $j, $k) = @_;
        return ($i->[0] * ($j->[1] - $k->[1])) 
             + ($j->[0] * ($k->[1] - $i->[1]))
             + ($k->[0] * ($i->[1] - $j->[1]));
    }
    
    sub collinear_points{
        my(@points) = @_;
        my @collinear;
        for my $i (@points){
            for my $j (@points){
                for my $k (@points){
                    if(triangle_area($i, $j, $k) == 0){
                        my $i_string = join(",", @{$i});
                        my $j_string = join(",", @{$j});
                        my $k_string = join(",", @{$k});
                        if(($i_string ne $j_string) && ($i_string ne $k_string) && ($j_string ne $k_string)){
                            my $has_i = grep { $i_string eq join(",", @{$_}) } @collinear;
                            push @collinear, $i if !$has_i;
                            my $has_j = grep { $j_string eq join(",", @{$_}) } @collinear;
                            push @collinear, $j if !$has_j;
                            my $has_k = grep { $k_string eq join(",", @{$_}) } @collinear;
                            push @collinear, $k if !$has_k;
                        }
                    }
                }
            }
        }
        return @collinear;
    }
    
    MAIN:{
        my @N;
        @N = ([5,3], [1,1], [2,2], [3,1], [1,3]);
        my @collinear = collinear_points(@N);
        print "There are a maximum of " . @collinear . " collinear points.\n"
    }
    

    Sample Run

    
    $ perl perl/ch-1.pl
    There are a maximum of 3 collinear points.
    

    Notes

    Keep in mind that any two points determine a line. Therefore to consider all possible non-trivial lines we need to review all triples of points. This method will work in the most general case when the starting data may contain multiple lines with a larger number of points.

    In determining collinearity I calculate the area of a triangle using the triple of points. If the area is zero we know that all the points lay on the same line.

    Part 2

    You are given a binary tree containing only the numbers 0-9. Write a script to sum all possible paths from root to leaf.

    Solution

    
    use strict;
    use warnings;
    ##
    # You are given a binary tree containing 
    # only the numbers 0-9.
    # Write a script to sum all possible paths 
    # from root to leaf.
    ##
    use Graph;
    
    sub travserse_sum{
        my($tree) = @_;
        my @paths = build_paths($tree);
        my $path_sum = 0;
        for my $path (@paths){
            $path_sum +=  unpack("%32C*", pack("C*", @{$path})); 
        }
        return $path_sum;
    }
    
    sub build_paths {
       my ($graph) = @_;
       my @paths;
       local *_helper = sub{
          my $v = $_[-1];
          my @successors = $graph->successors($v);
          if(@successors){
             _helper(@_, $_) for @successors;
          } 
          else{
             push @paths, [@_];
          }
       };
       _helper($_) for $graph->source_vertices();
       return @paths;
    }
    
    MAIN:{
        my $Tree;
        $Tree = new Graph();
        $Tree->add_vertices(1, 2, 3, 4);
        $Tree->add_edge(1, 2);
        $Tree->add_edge(2, 3);
        $Tree->add_edge(2, 4);
        print travserse_sum($Tree) . "\n";
        
        $Tree = new Graph();
        $Tree->add_vertices(1, 2, 3, 4, 5, 6);
        $Tree->add_edge(1, 2);
        $Tree->add_edge(1, 3);
        $Tree->add_edge(2, 4);
        $Tree->add_edge(3, 5);
        $Tree->add_edge(3, 6);
        print travserse_sum($Tree) . "\n";
    }
    

    Sample Run

    
    $ perl perl/ch-2.pl
    13
    26
    

    Notes

    This is straightforward enough, at a high level anyway: (1) Get all paths and then (2) sum all the nodes on the paths.

    • I am always happy to have a chance to use the Graph module!
    • The Graph module has a bunch of nice algorithms implemented but what we want here is not a shortest path but all paths. The Graph module doesn’t have anything for us to use for that. Implementing a recursive Depth First Search and collecting all the paths is not such a hard thing to do, but in the Holiday Spirit (i.e. laziness) I just re-used Ikegami’s code. See the References section.
    • I first used the pack/unpack trick for summing array back in Challenge 007.

    References

    Collinear Points

    All Paths

    In some regards, I'm a very old school person. For example I do not like the concept of streaming audio (via Spotify et.al.). I want MP3s on my hard disk (and/or vinyl on my record player). I want access to my music when I'm offline (and I'm offline a lot) and without using a so-called smart phone (I prefer vintage USB-stick MP3 players). My partner thinks the same (I guess 25+ years of my propaganda had some influence..).

    But "modern" sites make it rather hard to actually download content (even if it's free). They offer links to a myriad of apps, but often no download button. At least a lot of podcasts still provide an RSS feed. So when my partner cannot download a newly discovered podcast, she asked me if I can do it for her. Which I'm of course happy to do, and which is often done with a few lines of code:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use 5.030;
    
    use XML::Feed;
    use URI;
    use String::Ident;
    
    my $feed = XML::Feed->parse( URI->new( $ARGV[0] ) );
    
    for my $entry ( $feed->entries ) {
        my $date = $entry->issued;
        $date =~ s/T.*$//;
        my $filename = join( '-', $date, String::Ident->cleanup( $entry->title ) ) . '.mp3';
        next if -f $filename;
        say "wget -O $filename " . $entry->enclosure->url;
    }

    Here I use XML::Feed to fetch and parse the RSS feed, passing in the URL as the first command line argument. I create a nice filename based on the date the podcast was issued (removing the time-part) and a cleanup()ed version of the title. (String::Ident is a nice little helper module Jozef created for a project we were working on some time ago).

    If the filename already exists in the current directory, we skip, because we don't need to download it again.

    Then I output a wget command to download the URL (provided by $entry->enclosure->url) and save it under the nice filename.

    Why do I not download the file directly in the script?

    I just find it easier to use an external tool, especially as I like to pipe the output of this script into a file, so I can munge the file a bit. Eg, for this podcast, I did not download all 131 episodes, but only the 5 oldest and the 5 newest:

    ~/media/podcasts$ fetch_podcast.pl https://example.com/podcast.rss > all
    ~/media/podcasts$ head -n 5 all > test_it
    ~/media/podcasts$ tail -n 5 all >> test_it
    ~/media/podcasts$ bash test_it
    ...

    Nice and easy!

    Back in mid-November 2020, I noticed that Hackage (the Haskell package repository) probably has roughly the same daily upload rate as CPAN, or even higher.

    Since the Hackage API does not provide a way to list releases (uploads), I had to download the recent additions page periodically, parse each page, and merge the results into a single large list. Because I have just collected the recent additions page since mid-November, I'm looking at December 2020 period.

    % http-tiny-plugin-every --every 3h http://hackage.haskell.org/packages/recent --dir . --trace
    % for f in 2*.log; do parse-hackage-page "$f" --format ltsv > "$$.ltsv"; done
    % combine-overlap 2*.ltsv > hackage_release_202012.ltsv
    

    For CPAN, the MetaCPAN API lets us query various things in many ways so the simple task of listing recent releases is not a problem at all. I'm using a CLI to do this:

    % list-metacpan-releases --from-date 2020-12-01 --to-date 2020-12-31 --json > cpan_release_202012.json
    

    With this two pieces of data, I just need to perform some SQL (again, using CLI for this) to get what I want.

    So for December 2020, there are 957 releases:

    % fsql -a cpan_release_202012.json:t 'SELECT COUNT(*) FROM t' -f tsv
    COUNT
    957
    

    while for Hackage there are 629:

    % fsql -a hackage_release_202012.ltsv:t 'SELECT COUNT(*) FROM t' -f tsv
    COUNT
    629
    

    As for number of authors who did releases in this period, the two are more similar:

    % fsql -a cpan_release_202012.json:t 'SELECT COUNT(DISTINCT author) FROM t' -f tsv
    COUNT
    207
    

    while for Hackage there are 191:

    % fsql -a hackage_release_202012.ltsv:t 'SELECT COUNT(DISTINCT author) FROM t' -f tsv
    COUNT
    191
    

    So this does confirm my guess that the upload activity for both repositories are currently in the same order of magnitude, but does not confirm the suspicion that Hackage is more active than CPAN, at least in December 2020. I plan to do a follow up next year in January after I collected all 2021 data.

    Das in vielerlei Hinsicht ungewöhnliche Jahr 2020 ist vorbei. Wir wünschen allen treuen und neuen Leser\*innen unseres Blogs ein frohes neues Jahr. Wir hoffen, dass Sie gut durch das vergangene Jahr gekommen sind und dass 2021 besser wird.
    dist author version abstract
    AWS-ARN JWRIGHT 0.001 Dumb module to parse and generate ARNs
    AnyPAN KARUPA 0.09 CPAN Mirror and DarkPAN merging toolkit
    AnyPAN-Storage-S3 KARUPA 0.05 AnyPAN storage plugin for Amazon S3
    App-ImageMagickUtils PERLANCAR 0.001 Utilities related to ImageMagick
    App-MetaCPANUtils PERLANCAR 0.001 CLI utilities related to MetaCPAN
    App-OverlapUtils PERLANCAR 0.001 Command-line utilities related to overlapping lines
    App-Toolforge-MixNMatch SKIM 0.01 Toolforge Mix'n'match tool app.
    App-jupiter SCHROEDER 1 an app that serves a river of news as a static page
    Array-OverlapFinder PERLANCAR 0.001 Find/remove overlapping items between two ordered sequences
    Async-Template OKLAS 0.12 Async Template Toolkit
    Atomic-Pipe EXODIST 0.001 Send atomic messages from multiple writers across a POSIX pipe.
    AtteanX-Store-DBI GWILLIAMS 0.000_01 Database quad-store
    Badge-Depot-Plugin-Githubactions CSSON 0.0100 Github Actions plugin for Badge::Depot
    CLI-Meta-diff PERLANCAR 0.001 Metadata for diff CLI
    CLI-MetaUtil-Getopt-Long PERLANCAR 0.001 Routine related to Getopt::Long
    CPANfile-Parse-PPI PERLSRVDE 0.01 Parse cpanfiles with PPI
    Data-CompactReadonly DCANTRELL v0.0.1 a description of CompactReadonly data format, version 0.
    Devel-WatchVars TOMC v1.0.0 trace access to scalar variables
    Device-Chip-BME280 PEVANS 0.01 chip driver for BME280
    Dist-Mgr STEVEB 1.00 Distribution development cycle toolkit
    Dist-Zilla-Plugin-Author-CSSON-GithubActions CSSON 0.0100 Ease creation of common Github Actions workflows
    Dist-Zilla-Plugin-CheckForUnwantedFiles CSSON 0.0100 Check for unwanted files
    Dist-Zilla-PluginBundle-Author-GTERMARS GTERMARS 0.01 Plugin Bundle for distributions built by GTERMARS
    Finance-Crypto-Exchange-Kraken WATERKIP 0.001 A Perl implementation of the Kraken REST API
    Finance-IG MARKWIN 0.01 – Module for doing useful stuff with IG Markets REST API.
    Geo-Compass-Direction STEVEB 0.01 Convert a compass heading degree into human readable direction
    HEAT-Crypto TMM 0.06 HEAT cryptographic routines
    HEAT-Crypto-X25519 TMM 0.04 HEAT cryptographic routines
    Health-BladderDiary-GenChart PERLANCAR 0.001 Create bladder diary table from entries
    JavaScript-Const-Exporter RRWO v0.1.0 Convert exported Perl constants to JavaScript
    LocalConf-Parser NICKNIU 0.01 read config to an hashref from local conf files.
    Locale-Places NHORNE 0.01 Translate places using http://download.geonames.org/
    Log-Log4perl-Appender-Redis FRAZAO 0.01 Log to a Redis channel
    Log-ger-UseBaheForDump PERLANCAR 0.001 Use Data::Bahe to dump data structures
    Log-ger-UseJSONForDump PERLANCAR 0.003 Use JSON::MaybeXS to dump data structures (as JSON)
    Log-ger-UseYAMLForDump PERLANCAR 0.001 Use YAML::PP to dump data structures (as JSON)
    Log4perlAppenderRedis FRAZAO 0.01 Log to a Redis channel
    LogicMonitor-REST-Signature VVELOX v0.0.1 Builds LMv1 token header info for Logicmonitor.
    LyricFinder TURNERJW 1.00 Fetch song lyrics from several internet lyric sites.
    Math-Sidef TRIZEN 0.01 Perl interface to Sidef's mathematical library.
    Mo-utils SKIM 0.01 Mo utilities.
    Module-Installed STEVEB 1.00 Check whether a module, or a file's list of includes are installed.
    Mojolicious-Plugin-Parametry ZOFFIX 1.001001 Mojolicious plugin providing param helpers
    MsOffice-Word-Template DAMI 1.0 treat a Word document as Template Toolkit document
    Music-Chord-Progression GENE 0.0001 Create network transition chord progressions
    Music-MelodicDevice-Inversion GENE 0.0100 Apply melodic inversion to a series of notes
    Music-MelodicDevice-Ornamentation GENE 0.0100 Chromatic and diatonic musical ornamentation
    Music-MelodicDevice-Transposition GENE 0.0100 Apply chromatic and diatonic transposition to notes
    Nano AWNCORP 0.01 Minimalist Object Persistence
    Net-Cloudflare-DNS WESLEY 0.01 DNS API for Cloudflare API v4
    Net-OAuth2Server-TokenExchange ARISTOTLE 0.001 A Token Exchange extension for Net::OAuth2Server
    PDF-QRCode OETIKER v0.1.0 Add QRCode method to PDF::API2
    PerlIO-bom LEONT 0.001 Automatic BOM handling in Unicode IO
    Plack-App-DirectoryIndex DAVECROSS v0.0.1 Serve static files from document root with an index file.
    Plack-Middleware-Text-Minify RRWO v0.1.0 minify text responses on the fly
    RogueCurses KOBOLDWIZ v0.0.1 game system for RPG/Adventure games using Curses.
    RogueQuest KOBOLDWIZ v0.1.1 game system for RPG/Adventure games using SDL for graphics.
    SMS-Send-Adapter-Node-Red MRDVT 0.04 SMS::Send Adapter to Node-RED JSON HTTP request
    SQL-PatchDAG ARISTOTLE 0.100 A minimal DB schema patch manager
    STEVEB-Dist-Mgr STEVEB 0.01 My distribution release cycle toolkit
    Statocles-Plugin-AudioTag GENE 0.0102 Change mp3 anchors to audio elements
    Syntax-Check STEVEB 1.00 Wraps 'perl -c' so it works even if modules are unavailable
    Sys-Linux-KernelVersion SIMCOP 0.100 Gives tools for checking the current running linux kernel version
    Test-Mojo-Role-DOMinizer ZOFFIX 1.001001 Test::Mojo role to examine DOM mid test chain
    Text-Minify-XS RRWO v0.1.0 Simple text minification
    Toolforge-MixNMatch-Diff SKIM 0.01 Toolforge Mix'n'match tool diff.
    Toolforge-MixNMatch-Object SKIM 0.01 Toolforge Mix'n'match tool objects.
    Toolforge-MixNMatch-Print SKIM 0.01 Toolforge Mix'n'match tool object print routines.
    Toolforge-MixNMatch-Struct SKIM 0.01 Toolforge Mix'n'match tool structures.
    WWW-Picnic GETTY 0.001 Library to access Picnic Supermarket API
    Wikibase-Datatype SKIM 0.01 Wikibase data types.
    Wikibase-Datatype-Struct SKIM 0.01 Wikibase data types struct conversions.
    Word-Rhymes STEVEB 0.01 Takes a word and fetches rhyming matches from RhymeZone.com
    WordList-ColorName-Any PERLANCAR 0.002 Wordlist from any Graphics::ColorNames::* module
    Zing-Encoder-Json AWNCORP 0.01 JSON Serialization Abstraction
    Zing-Encoder-Jwt AWNCORP 0.01 JWT Serialization Abstraction
    Zing-Store-Mysql AWNCORP 0.01 Mysql Storage Abstraction
    Zing-Store-Pg AWNCORP 0.01 Postgres Storage Abstraction
    Zing-Store-Redis AWNCORP 0.01 Redis Storage Abstraction
    Zing-Store-Sqlite AWNCORP 0.01 Sqlite Storage Abstraction
    Zing-Zang AWNCORP 0.01 Callback-based Zing Processes
    config-parser NICKNIU 0.01 read config to an hashref from local conf files.
    crazy-fast-media-scan ART 0.001 methods to identify files using random sampling

    Stats

    Number of new CPAN distributions this period: 83

    Number of authors releasing new CPAN distributions this period: 39

    Authors by number of new CPAN distributions this period:

    No Author Distributions
    1 PERLANCAR 11
    2 SKIM 8
    3 AWNCORP 8
    4 STEVEB 6
    5 GENE 5
    6 CSSON 3
    7 RRWO 3
    8 KARUPA 2
    9 FRAZAO 2
    10 NICKNIU 2
    11 KOBOLDWIZ 2
    12 ZOFFIX 2
    13 TMM 2
    14 ARISTOTLE 2
    15 NHORNE 1
    16 EXODIST 1
    17 TOMC 1
    18 TRIZEN 1
    19 GWILLIAMS 1
    20 GETTY 1
    21 SIMCOP 1
    22 DAVECROSS 1
    23 LEONT 1
    24 OKLAS 1
    25 GTERMARS 1
    26 WATERKIP 1
    27 PEVANS 1
    28 TURNERJW 1
    29 MRDVT 1
    30 DAMI 1
    31 PERLSRVDE 1
    32 DCANTRELL 1
    33 OETIKER 1
    34 VVELOX 1
    35 JWRIGHT 1
    36 ART 1
    37 SCHROEDER 1
    38 WESLEY 1
    39 MARKWIN 1

    Recently a number of people have contacted me with various Perl-based projects. I had the opportunity to have an email exchange with them to try to understand what they need and if I can provide the help. A few question came up and for my future reference I wrote them down.

    These were almost always old projects that needed some new feature added.

    Auch in den letzten beiden Monaten dieses Jahres waren wir nicht ganz untätig – teilweise mit Hilfe anderer Perl-Programmierer\*innen.
    Gitlab bietet viel Funktionalität, die für Perl-Projekte sinnvoll genutzt werden kann. Unsere Schulung soll am praktischen Beispiel einer kleinen CPAN-Distribution einen Einblick in die Möglichkeiten bieten.
    @davorg / Friday 22 January 2021 13:36 UTC