Checking if a module is installed (without actually loading it)

One of the easiest ways to check if a module is installed is simply by trying to load it:

if (eval { require Foo::Bar; 1 }) {
    # Foo::Bar is loadable
}

However, when Foo::Bar happens to be installed, this actually loads the module. Which is not always desirable, for example in the cases of: 1) checking a lot of modules; 2) checking a module which is OS-specific and might not work under your OS when loaded; 3) checking a module which might conflict with another module that is already loaded; 4) wanting to avoid the security implication of executing the module’s code.

Another way to check is by trying to locate the module file by iterating over @INC yourself or using something like Module::Path or Module::Path::More. Those modules search for the module in directories specified in @INC like Perl’s require would:

use Module::Path qw(module_path);
if (module_path "Foo::Bar") {
    # Foo::Bar is available
}

However, this only works when Foo::Bar is indeed located on the filesystem and does not work when Foo::Bar is loaded using a require hook (coderef or object in @INC), like in a fatpacked or datapacked script. Also, it does not work nicely with other uses of require hooks, like emulating a missing module (lib::filter or lib::disallow).

Perl core module Module::Load::Conditional provides check_install which can handle both the cases of the module file is on the filesystem or the module is retrieved from the require hook:

use Module::Load::Conditional qw(check_install);
if (check_install(module => "Foo::Bar")) {
    # Foo::bar is available
}

In addition to the above, check_install can also be instructed to check for minimum required version:

unless (check_install(module => "Foo::Bar", version=>"1.23")) {
    # Foo::Bar is not available, or its version is < 1.23
}

Note that checking version number is not performed by loading the module and reading its $VERSION, but instead by using Module::Metadata which tries to extract the version number from the module’s source code (which might fail on some weird module that obfuscate its $VERSION, but for normal cases should suffice).

I also recently wrote Module::Installed::Tiny which does the same as Module::Load::Conditional‘s check_install but with a bit less code and dependency:

use Module::Installed::Tiny qw(module_installed);
if (module_installed "Foo::Bar") {
    # Foo::Bar is available
}

Note that check_install nor module_installed does not guarantee that the module will be loaded successfully, as there might be syntax errors in the module’s code or runtime errors when running the code. All the routines do is check that the module’s source code is available.

UPDATE [2016-08-03]: This post is originally about Module::Loadable before I was made aware of Module::Load::Conditional‘s check_install. In the original post I wrote that I hoped I didn’t reinvent the wheel by writing Module::Loadable. I was happily proven wrongπŸ™‚

Podcast filenames

Like many of you, I listen to some podcasts. There are various ways people get their episodes, but I do it manually on a PC: browse the podcast’s website and download the MP3 files using the browser or wget or curl. I listen on a variety of devices, including television and car audio which can only get the files via USB flashdisks, so I figure it’s better to organize the files on the PC and transfer them to other devices as needed.

Now there’s this minor (or major, depending on how OCD you are) issue of the various inconsistent ways the podcasters like to name their MP3 files. Me, I’m standardizing on this: each filename should include, in the following order, 1) the podcast name (preferably short, a few letters, initials); 2) the episode number in the form of at least 000, or date in YYYYMMDD format; 3) episode title (one to a few words).

This way, whenever I see a file lying around in some folder in some device I can immediately know which podcast this is and what the episode is all about (because on smartphones it’s usually a pain to move files around). The order and the format of the number/date let the files get sorted nicely (because not all apps can do natural sorting). And the short podcast name/initials will prevent the annoyance of not being able to see the date/title on narrower screens (sometimes an app will scroll the filename horizontally a la stock ticker, but sometimes not).

Oh, and I also stick to lowercase alphanumerical characters and dashes/underscores, avoiding whitespaces or other strange characters, for ease of typing, selecting, and tab-completioning.

Here are some samples of filenames which I will definitely rename:

  • The Secret Emotional Life of Clothes.mp3 (from Invisibilia): spaces in filename, no podcast name, no episode number/date. I’d rename it to invisib-20160722-the_secret_emotional_life_of_clothes.mp3.
  • obm20episode2016320-207_19_162C209.0420PM.mp3 (from One Bad Mother): no title, needless string episode as well as time of day, date not in YYYYMMDD order, also the space got mangled into 20 (probably from %20). I’d rename it to obm-163-when_kids_share_a_room.mp3.
  • ShmanQuestions.mp3 (from Shmanners): no episode number/date. I’d rename it to shmanners-20160722-etiquette_catch_all.mp3. Sometimes the title in the filename doesn’t match the title in the post, so I also correct that.
  • OhNoRossAndCarrie_47_RossAndCarrieRememberTonyAlamoPart1.mp3 (from Oh No, Ross and Carrie!): too long. I’d rename it to: onrac-047-tony_alamo_p1.mp3.

And here are some that are already good enough:

  • sm237_limabeans.mp3 (from Spilled Milk). Although before it reaches episode #10 and #100, it uses one and two digits for the episode number so I pad them with leading zeroes.
  • Sawbones146Tea.mp3 (from Sawbones). All the pieces of information are already there in the desired order, I just need to format and lowercase the filename.

How do you name your podcast files?

Cascade bumping of prerequisite version

Introducing backward-incompatible change to a piece of code, especially if that code has a lot of dependants (i.e. located more upstream in the river, if we’re using the river of CPAN analogy), will cause pain. But sometimes you need or want to do it anyway.

Suppose you have this tree of dependencies:

Aa (0.01)
    Bb (0.01, requires Aa=0)
    Cc (0.01, requires Aa=0)
        Dd (0.01, requires Cc=0)
        Ee (0.01, requires Cc=0)
            Ff (0.01, requires Ee=0)
            Gg (0.01, requires Ee=0)
    Hh (0.01, requires Aa=0.01)

Now a backward-incompatible change is introduced in Aa, and you release Aa 0.02. Let’s say this change happens to affect Bb and Cc but not Hh.

If a system updates Aa to 0.02, suddenly Bb and Cc will break. And Dd, Ee, Ff, Gg will break too because Bb and Cc break. Aa can be updated due to a variety of causes, from manually for testing (like in a CPAN Testers machine) or because user installs something else like say Ii which needs Aa=0.02.

So you now release Bb 0.02 and Cc 0.02 to cope with the changes introduced in Aa. And these updates are not backward-incompatible so users of Bb and Cc can still specify Bb=0 or Cc=0.01. The dependency tree becomes like this:

Aa (0.02)
    Bb (0.02, requires Aa=0.02)
    Cc (0.02, requires Aa=0.02)
        Dd (0.01, requires Cc=0)
        Ee (0.01, requires Cc=0)
            Ff (0.01, requires Ee=0)
            Gg (0.01, requires Ee=0)
    Hh (0.01, requires Aa=0.01)

If a system happens to update just Bb or Cc, Aa will be correctly updated automatically to 0.02.

If a system just updates Aa, the same situation still happens: Bb & Cc will break, Dd, Ee, Ff, Gh will break too because Bb and Cc break.

Now suppose you add a new feature to Ff and release new version of Ff (0.02, requires Ee=0). Even though this does not have to do with backward-incompatible change of A 0.02, breakage might still happen. Let’s say a CPAN Testers machine tries to install Ff 0.02. The machine won’t automatically upgrade Bb and Cc because the specified dependency of Ff 0.02 doesn’t require it too. Now the test will fail when there is a new Aa (0.02) installed but old versions of Bb and Cc.

This is exactly what happens to me a few times, most recently in the case of Data::Sah 0.79. After I released Data::Sah 0.79, and then a couple of weeks after that release some other distributions that do not directly depend on it, some CPAN Testers machines will start reporting failure for these distributions. This is because the machines happen to have the updated Data::Sah but some older direct dependants which break under the new Data::Sah.

So back to our Aa example, to properly induce a cascade update, after we release Bb 0.02 and Cc 0.02, we also need to release Dd and Ee just to bump the prerequisite version of Cc to 0.02 even though Dd and Ee don’t exactly require Cc 0.02 (they can live with Cc 0.01). And repeat the process recursively: update Ff and Gg just to bump prerequisite version of Ee, and so on.

Thus, if a system updates Gg 0.02, Ee will automatically be upgraded to 0.02, Cc automatically upgraded to 0.02, and Aa automatically upgraded to 0.02.

To reiterate: after we introduce a backward-incompatible update to a module, we must update all the direct dependants of that module that are affected by the change, and also recursively update all their dependants just to bump the minimum prerequisite version and force pulling the module’s and direct dependants’ update.

In the case of Data::Sah, this involves hundreds of distributions because Perinci::CmdLine::Lite is a direct dependant that is affected. And Perinci::CmdLine::Lite (via Perinci::CmdLine::Any) is used by many of my App:: distributions. But fortunately, on a production system, Data::Sah typically won’t be updated without Perinci::CmdLine::Lite also being updated.

Using monotonic versioning in Perl

The Monotonic Versioning Manifesto looks interesting. Here’s the summary. Version is composed of only two numbers: X.Y. No leading zero is allowed for both numbers (so you must start at 1.1, not 0.1 nor 1.0). X indicates compatibility and must be increased whenever you introduce a backward-incompatible change. Y is the release number and must always increase, even though X is increased. In other words, Y is never reset. In this aspect, Y is akin to build number often used in compiled projects.

If you maintain two versions of your software, e.g. 7.5304 and 8.5822 and want to backport some feature/changes from version 8 to 7, you would release 7.5823.

Oh, and one more thing: since the popular semantic versioning scheme uses X.Y.Z, to aid interoperability X.Y.0 is allowed as an alternate display format and is identical to X.Y. Any other number for Z is not allowed.

Sure, the numbering scheme won’t be sufficient for larger/complex software, but for many Perl modules it could be a fit.

The important thing is to decide the number of digits you will need for the release number. 1.1, 1.10, 1.100, and so on are all the same in Perl (this is according to version->parse(V1) == version->parse(V2)), so you cannot just start with 1.1 and expect to go more than 9 releases without increasing X.

A sane choice is either 3 or 6 digits*). If 3 digits is enough for you, then go for it. A total of 900 releases, ever, sounds limiting at first but I don’t think there is a CPAN distribution that has close to 900 releases, are there? Otherwise, go for 6.

*) Because the way version number works in Perl, you should not choose e.g. 2 or 4. Because version->parse(“1.10”) < version->parse(“1.9”) and version->parse(“1.10010”) < version->parse(“1.1009”).

Blogging with org2blog

This post you’re seeing is the first one posted using org2blog. Finally, I can blog from Emacs, yay!

Frankly I’m not too fussy with my blog setup: any decent website will do as long as I can type text in, format stuffs, and the final page is readable. My previous blog was on blogs.perl.org, which I had to abandon because the login system was screwed up for many months, preventing me from even logging in, much less posting comments or new entries.

My current blog, the site you’re reading now hosted by WordPress, is not too bad. But there are two problems I’m having: first, the in-browser editor is limited and sometimes has annoying couple-of-second freezes every now and then. And second, the worse one, is that I’ve noticed the text changed at least twice. A few months ago I saw my Perl source code inside <pre> got HTML-escaped, so “$a > $b” becomes “$a &gt; $b”. This does not happen to all posts, only some of them. But I’m just too lazy to check my posts one by one. And more recently, suddenly all my “pericmd-tut” (with a lowercase i) tags became “perlcmd-tut” (with a lowercase ell), as if someone had run an autocorrect on the tags database. I updated a few dozen posts this time, reverting the tags back to “pericmd-tut” and meanwhile starting to ponder for alternatives.

Today I decide to give org2blog a try. I’ve heard about this tool like a couple of weeks ago and yesterday I had an idea for a blog post. Here are the general steps and some examples if you happen to be interested to follow suit.

Installation

The first thing I had to do was put the *.el from the org2blog package under my $HOME/.emacs.d directory as M-x install-package didn’t work out for me. ($HOME/.emacs.d/lisp should be a better choice, but I haven’t bothered to move my *.el files there.) I also had to drop xml-rpc.el and metaweblog.el from into this same directory. Those two are the requirements mentioned in the org2blog README.

Then I appended these lines to my $HOME/.emacs:

(require 'org2blog-autoloads)
(setq org2blog/wp-blog-alist
      '(("perlancar"
         :url "https://perlancar.wordpress.com/xmlrpc.php"
         :username "<censored>"
         :password "<censored>"
         :default-title "Post"
         :default-categories ("perl")
         :tags-as-categories nil)
         ))
(setq org2blog/wp-use-sourcecode-shortcode t)

I guess the README doesn’t recommend putting the password right there along with the username, but for simplicity sake I just put it anyway.

Also note that you can use an existing blog (as in my case). org2blog can add new posts or delete existing ones based on post ID’s which you supply later.

Then I restarted my Emacs.

Creating a new post

Each post is put in a separate buffer (file).

To create a new post, M-x org2blog/wp-new-entry. org2blog will create a new buffer containing something like this:

#+DATE: [2016-05-03 Tue 14:47]
#+OPTIONS: toc:nil num:nil todo:nil pri:nil tags:nil ^:nil
#+CATEGORY: perl
#+TAGS:
#+DESCRIPTION:
#+TITLE: Post

You can then edit the title, tags, or categories. And start writing. You can save the buffer anywhere as regular files. This post itself is saved as an .org file then put under a Git repository.

Posting to WordPress

To post the buffer as draft, M-x org2blog/wp-post-buffer (or C-c M-p d). And to publish the buffer C-u M-x org2blog/wp-post-buffer (or C-c M-p p). org2blog will also ask you whether you want to open the post in a browser to see how the final result looks like.

That’s it, it’s that simple. Now you can write in the cozy and powerful Emacs environment, and your source document is in Org format instead of the default bastardized HTML that WordPress uses.

After the first sending to the server, org2blog will add a #+POST_ID: line containing the post’s ID. If you edit the buffer, then re-post, it will update the existing post since the buffer already specifies the post’s ID. Editing your blog posts is now as simple as editing a file with an extra single command to push it to WordPress.

Some formatting examples

To see the Org source code that produces these result, see the abovementioned Git repository.

This is a Perl code block:

#!/usr/bin/perl

use strict;
use warnings;
use Org::Parser;

my $orgp = Org::Parser->new;
my $doc = $orgp->parse_file("$ENV{HOME}/todo.org");

This is another Perl code block, which we set to begin from line 75 and have its lines 77-78 highlighted:

my @elems = $doc->select("Table:first TableCell:empty");
for my $elem (@elems) {
    $log->trace("Deleting empty element %s", $elem);
    $elem->delete;
}

Selecting elements of Org document with CSS-selector-like syntax

If you’ve dabbled with jQuery or CSS selector, or Mojo::DOM (or DOM::Tiny), you’ll find that CSS selector offers a nice way to select elements.

I’ve created Data::CSel to extend this concept not just for HTML element tree, but also for any kind of tree object in Perl. The syntax is similar enough so you should be able to get up and running in no time.

For the first application that uses this, I’ve added the select-org-elements script to App-OrgUtils distribution. This script can select elements of an Org document tree using the CSel language. This should make it easier to select/extract parts of your Org document without having to resort to using Perl code to manipulate the tree.

Example Org document (you can grab it from https://github.com/perlancar/samples/blob/master/org/table.org ):

 emacs determines whether a column mostly contains numbers or non-numbers. if
numbers then a column will be left-justified. if non-numbers then
right-justified.

| col1   |      col2 | col3 | col4     |    col5 |
|--------+-----------+------+----------+---------|
| foo    |      -1.3 |      | abc      |     123 |
| bar    |   -1900.3 |      | abcdefgh | 1500000 |
| baz    |      23.1 |      | 10       |     abc |
| quux   |         0 |      | foo      |     def |
| garply | 3,000,000 |      | 999      |     234 |

table without header:

| one   | two  |
| three | four |

To help see the structure, first use the dump-org-structure script:

% dump-org-structure org/tables.org
Document:
  Text: "emacs determines whether a column mostly..."
  Table:
    TableRow:
      TableCell:
        Text: "col1"
      TableCell:
        Text: "col2"
      TableCell:
        Text: "col3"
      TableCell:
        Text: "col4"
      TableCell:
        Text: "col5"
    TableVLine: "|---\n"
    TableRow:
      TableCell:
        Text: "foo"
      TableCell:
        Text: "-1.3"
      TableCell:
      TableCell:
        Text: "abc"
      TableCell:
        Text: "123"
    TableRow:
      TableCell:
        Text: "bar"
      TableCell:
        Text: "-1900.3"
      TableCell:
      TableCell:
        Text: "abcdefgh"
      TableCell:
        Text: "1500000"
    TableRow:
      TableCell:
        Text: "baz"
      TableCell:
        Text: "23.1"
      TableCell:
      TableCell:
        Text: "10"
      TableCell:
        Text: "abc"
    TableRow:
      TableCell:
        Text: "quux"
      TableCell:
        Text: "0"
      TableCell:
      TableCell:
        Text: "foo"
      TableCell:
        Text: "def"
    TableRow:
      TableCell:
        Text: "garply"
      TableCell:
        Text: "3,000,000"
      TableCell:
      TableCell:
        Text: "999"
      TableCell:
        Text: "234"
  Text: "\ntable without header:\n\n"
  Table:
    TableRow:
      TableCell:
        Text: "one"
      TableCell:
        Text: "two"
    TableRow:
      TableCell:
        Text: "three"
      TableCell:
        Text: "four"

Now let’s select some elements:

% select-org-elements TableRow org/table.org
|col1|col2|col3|col4|col5
|foo|-1.3||abc|123
|bar|-1900.3||abcdefgh|1500000
|baz|23.1||10|abc
|quux|0||foo|def
|garply|3,000,000||999|234
|one|two
|three|four

% select-org-elements TableRow:first org/table.org
|col1|col2|col3|col4|col5

% select-org-elements 'TableCell:nth-of-type(5):last' org/table.org
234

Selecting via Perl code is equally easy:

use Org::Parser;
use Data::CSel qw(csel);

my $doc = Org::Parser->new->parse_file("yourfile.org");
my @headlines = csel({class_prefixes=>['Org::Element']}, "Headline[level=2]", $doc);

Enjoy.

lcpan tips 015: Munging lcpan text output with ‘td’

About this series: a collection of short, daily blog posts about lcpan tips/recipes. Some posts will also end up in the upcoming App::lcpan::Manual::Cookbook POD to be included in the App-lcpan distribution.

About lcpan: an application to download and index a mini CPAN mirror on your local filesystem, so in effect you will have something like your own CPAN with a command-line tool (or perl API) to query and extract information from your mirror. I find it perfect for my own personal use when working offline.

Often the output of lcpan is too wide to view on your terminal, causing wrapped text that’s hard to read, e.g.:

% lcpan related-mods Text::Roman
+------------------------------+------------------------------------------------------------------+--------------+-----------------------+-----------------------+-----
--+---------------------------+----------+
| module                       | abstract                                                         | num_mentions | num_mentions_together | pct_mentions_together | scor
e | dist                      | author   |
+------------------------------+------------------------------------------------------------------+--------------+-----------------------+-----------------------+-----
--+---------------------------+----------+
| Math::Roman                  | Arbitrary sized Roman numbers and conversion from and to Arabic. | 5            | 3                     | 60                    | 540 
  | Math-Roman                | TELS     |
| Convert::Number::Roman       | Convert Between Western and Roman Numeral Systems                | 2            | 2                     | 100                   | 400 
  | Convert-Number-Roman      | DYACOB   |
| Roman                        | functions for converting between Roman and Arabic numerals       | 4            | 2                     | 50                    | 200 
  | Roman                     | CHORNY   |
| Roman::Unicode               | Make roman numerals, using the Unicode characters for them       | 4            | 2                     | 50                    | 200 
  | Roman-Unicode             | BDFOY    |
| Acme::MetaSyntactic::roman   | The roman theme                                                  | 1            | 1                     | 100                   | 100 
  | Acme-MetaSyntactic-Themes | BOOK     |
| Acme::Roman                  | Do maths like Romans did                                         | 1            | 1                     | 100                   | 100 
  | Acme-Roman                | FERREIRA |
| Convert::Number::Digits      | Convert Digits Between the Scripts of Unicode.                   | 1            | 1                     | 100                   | 100 
  | Convert-Number-Digits     | DYACOB   |
| Language::Befunge::lib::ROMA | Roman numerals extension                                         | 1            | 1                     | 100                   | 100 
  | Language-Befunge          | JQUELIN  |
| Convert::Number::Coptic      | Convert Between Western and Coptic Numeral Systems               | 4            | 1                     | 25                    | 25  
  | Convert-Number-Coptic     | DYACOB   |
+------------------------------+------------------------------------------------------------------+--------------+-----------------------+-----------------------+-----
--+---------------------------+----------+

In a GUI terminal emulator, you usually can shrink the font size so more characters can fit in a single row. For example, in Konsole you can press Ctrl-[-] to do this. However, as the font becomes smaller it’s harder to read. Sometimes you just want to read some columns and ignore the others.

Since lcpan is using Perinci::CmdLine framework, its text output is actually a data structure, as you can see if you ask it to output in JSON format instead:

% lcpan related-mods Text::Roman --json
[
   {
      "abstract" : "Arbitrary sized Roman numbers and conversion from and to Arabic.",
      "author" : "TELS",
      "dist" : "Math-Roman",
      "module" : "Math::Roman",
      "num_mentions" : 5,
      "num_mentions_together" : 3,
      "pct_mentions_together" : 60,                                                                                                                                    
      "score" : 540                                                                                                                                                    
   },                                                                                                                                                                  
   {                                                                                                                                                                   
      "abstract" : "Convert Between Western and Roman Numeral Systems",                                                                                                
      "author" : "DYACOB",
      "dist" : "Convert-Number-Roman",
      "module" : "Convert::Number::Roman",
      "num_mentions" : 2,
      "num_mentions_together" : 2,
      "pct_mentions_together" : 100,
      "score" : 400
   },
   {
      "abstract" : "functions for converting between Roman and Arabic numerals",
      "author" : "CHORNY",
      "dist" : "Roman",
      "module" : "Roman",
      "num_mentions" : 4,
      "num_mentions_together" : 2,
      "pct_mentions_together" : 50,
      "score" : 200
   },
   {
      "abstract" : "Make roman numerals, using the Unicode characters for them",
      "author" : "BDFOY",
      "dist" : "Roman-Unicode",
      "module" : "Roman::Unicode",
      "num_mentions" : 4,
      "num_mentions_together" : 2,
      "pct_mentions_together" : 50,
      "score" : 200
   },
   {
      "abstract" : "The roman theme",
      "author" : "BOOK",
      "dist" : "Acme-MetaSyntactic-Themes",
      "module" : "Acme::MetaSyntactic::roman",
      "num_mentions" : 1,
      "num_mentions_together" : 1,
      "pct_mentions_together" : 100,
      "score" : 100
   },
   {
      "abstract" : "Do maths like Romans did",
      "author" : "FERREIRA",
      "dist" : "Acme-Roman",
      "module" : "Acme::Roman",
      "num_mentions" : 1,
      "num_mentions_together" : 1,
      "pct_mentions_together" : 100,
      "score" : 100
   },
   {
      "abstract" : "Convert Digits Between the Scripts of Unicode.",
      "author" : "DYACOB",
      "dist" : "Convert-Number-Digits",
      "module" : "Convert::Number::Digits",
      "num_mentions" : 1,
      "num_mentions_together" : 1,
      "pct_mentions_together" : 100,
      "score" : 100
   },
   {
      "abstract" : "Roman numerals extension",
      "author" : "JQUELIN",
      "dist" : "Language-Befunge",
      "module" : "Language::Befunge::lib::ROMA",
      "num_mentions" : 1,
      "num_mentions_together" : 1,
      "pct_mentions_together" : 100,
      "score" : 100
   },
   {
      "abstract" : "Convert Between Western and Coptic Numeral Systems",
      "author" : "DYACOB",
      "dist" : "Convert-Number-Coptic",
      "module" : "Convert::Number::Coptic",
      "num_mentions" : 4,
      "num_mentions_together" : 1,
      "pct_mentions_together" : 25,
      "score" : 25
   }
]

Now you can use a JSON munging tool like jq to filter the fields of each record, and then later render the JSON back to text table using tool like pretty, e.g.:

% lcpan related-mods Text::Roman --json | jq '[ .[] | {module, abstract} ]'
[
  {
    "module": "Math::Roman",
    "abstract": "Arbitrary sized Roman numbers and conversion from and to Arabic."
  },
  {
    "module": "Convert::Number::Roman",
    "abstract": "Convert Between Western and Roman Numeral Systems"
  },
  {
    "module": "Roman",
    "abstract": "functions for converting between Roman and Arabic numerals"
  },
  {
    "module": "Roman::Unicode",
    "abstract": "Make roman numerals, using the Unicode characters for them"
  },
  {
    "module": "Acme::MetaSyntactic::roman",
    "abstract": "The roman theme"
  },
  {
    "module": "Acme::Roman",
    "abstract": "Do maths like Romans did"
  },
  {
    "module": "Convert::Number::Digits",
    "abstract": "Convert Digits Between the Scripts of Unicode."
  },
  {
    "module": "Language::Befunge::lib::ROMA",
    "abstract": "Roman numerals extension"
  },
  {
    "module": "Convert::Number::Coptic",
    "abstract": "Convert Between Western and Coptic Numeral Systems"
  }
]
% lcpan related-mods Text::Roman --json | jq '[ .[] | {module, abstract} ]' | pretty
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ abstract                                        module                       β”‚
β”‚                                                                              β”‚
β”‚ Arbitrary sized Roman numbers and conversion    Math::Roman                  β”‚
β”‚ from and to Arabic.                                                          β”‚
β”‚ Convert Between Western and Roman Numeral       Convert::Number::Roman       β”‚
β”‚ Systems                                                                      β”‚
β”‚ functions for converting between Roman and      Roman                        β”‚
β”‚ Arabic numerals                                                              β”‚
β”‚ Make roman numerals, using the Unicode          Roman::Unicode               β”‚
β”‚ characters for them                                                          β”‚
β”‚ The roman theme                                 Acme::MetaSyntactic::roman   β”‚
β”‚ Do maths like Romans did                        Acme::Roman                  β”‚
β”‚ Convert Digits Between the Scripts of           Convert::Number::Digits      β”‚
β”‚ Unicode.                                                                     β”‚
β”‚ Roman numerals extension                        Language::Befunge::lib::ROMA β”‚
β”‚ Convert Between Western and Coptic Numeral      Convert::Number::Coptic      β”‚
β”‚ Systems                                                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Or, if you want to make sure that the order of the columns is maintained:

% lcpan related-mods Text::Roman --json | jq '[ .[] | [.module, .abstract] ]' | pretty
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ column0                        column1                                       β”‚
β”‚                                                                              β”‚
β”‚ Math::Roman                    Arbitrary sized Roman numbers and conversion  β”‚
β”‚                                from and to Arabic.                           β”‚
β”‚ Convert::Number::Roman         Convert Between Western and Roman Numeral     β”‚
β”‚                                Systems                                       β”‚
β”‚ Roman                          functions for converting between Roman and    β”‚
β”‚                                Arabic numerals                               β”‚
β”‚ Roman::Unicode                 Make roman numerals, using the Unicode        β”‚
β”‚                                characters for them                           β”‚
β”‚ Acme::MetaSyntactic::roman     The roman theme                               β”‚
β”‚ Acme::Roman                    Do maths like Romans did                      β”‚
β”‚ Convert::Number::Digits        Convert Digits Between the Scripts of         β”‚
β”‚                                Unicode.                                      β”‚
β”‚ Language::Befunge::lib::ROMA   Roman numerals extension                      β”‚
β”‚ Convert::Number::Coptic        Convert Between Western and Coptic Numeral    β”‚
β”‚                                Systems                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

There’s an easier way though, using td. It takes a JSON input, processes it according to the given command, and then renders it back to text table (among other formats). For example:

% lcpan related-mods Text::Roman --json | td select module abstract
+------------------------------+------------------------------------------------------------------+
| module                       | abstract                                                         |
+------------------------------+------------------------------------------------------------------+
| Math::Roman                  | Arbitrary sized Roman numbers and conversion from and to Arabic. |
| Convert::Number::Roman       | Convert Between Western and Roman Numeral Systems                |
| Roman                        | functions for converting between Roman and Arabic numerals       |
| Roman::Unicode               | Make roman numerals, using the Unicode characters for them       |
| Acme::MetaSyntactic::roman   | The roman theme                                                  |
| Acme::Roman                  | Do maths like Romans did                                         |
| Convert::Number::Digits      | Convert Digits Between the Scripts of Unicode.                   |
| Language::Befunge::lib::ROMA | Roman numerals extension                                         |
| Convert::Number::Coptic      | Convert Between Western and Coptic Numeral Systems               |
+------------------------------+------------------------------------------------------------------+

In the above command, we tell td to just select a couple of columns from the table.

There are a few other td commands available, e.g. to sort rows based on one or more columns, to perform sum/count/and so on. So basically td is the equivalent of Unix toolbox command cut, head, tail, sum, and so on, except that it operates on a table data structure instead of text stream.

There’s an extra convenience put in if you use td. If Pipe::Find module is available, Perinci::CmdLine can use it to check that the program called “td” is at the right side of the pipeline and automatically defaults to JSON output, so you don’t need to specify --json:

% lcpan related-mods Text::Roman | td select module abstract

Note that this tip is not only for lcpan, but for all Perinci::CmdLine-based CLI applications.