About this mini-article series. Each day for 24 days, I will be reviewing a module (but 3 modules for today) that parse command-line options (such modules are usually under the Getopt::* namespace). First article is here.

In 2012, the Python option parsing library docopt made its first appearance and took the world by storm, so to speak. It was regarded by many as a fresh approach that is quite revolutionary. Ports for other languages follow, from PHP to Ruby, Haskell to Go, C/C++ to Rust. The npm port itself boasts 500 dependents. Docopt also eventually inspires a few forks or projects that aim to extend the expressive power of the DSL. Oh, and a Perl port exists too of course, written by Tokuhiro Matsuno (TOKUHIROM). Sadly, Docopt.pm still has the “under development” label from the author and hasn’t been updated since 2013, although it already works at least for a subset of specification.

Meanwhile, the concept is not new nor invented in the Python community. As far back as 1998 (14 years earlier), Damian Conway (DCONWAY) released Getopt::Declare on CPAN which has the same basic idea: parse options based on a documentation-like or usage-message-like specification. And I suspect the Perl folks in turn stole this concept from some other even older language.

In 2005, the Perl Best Practices book (also by Damian Conway) came out. In it, a then-yet-unwritten new module called Getopt::Clade is mentioned and is supposed to be the blessed successor to Getopt::Declare. Unfortunately, that module never got written. But Getopt::Euclid was born instead, with a similar but slightly different concept: instead of using a usage-message-like specification, the specification is read from the POD.

How popular are these modules? None of them are very, but at least all of them have some CPAN distributions depending on them, unlike most other Getopt:: modules. Docopt has 5 CPAN distributions depending on it, Getopt::Declare only has 4, while Getopt::Euclid is slightly better at 10.

% lcpan rdeps Docopt +---------+----------+-----------------------+---------+--------------+-------------+ | phase | rel | dist | author | dist_version | req_version | +---------+----------+-----------------------+---------+--------------+-------------+ | runtime | requires | App-ReorderGoProFiles | VTI | 0.02 | 0 | | runtime | requires | App-WhatTimeIsIt | BAYASHI | 0.01 | 0.03 | | runtime | requires | App-plmetrics | BAYASHI | 0.06 | 0 | | runtime | requires | CLI-Dispatch-Docopt | BAYASHI | 0.01 | 0.03 | | runtime | requires | Devel-Mutator | VTI | 0.03 | 0 | +---------+----------+-----------------------+---------+--------------+-------------+

% lcpan rdeps Getopt::Declare +---------+----------+-----------------------+----------+--------------+-------------+ | phase | rel | dist | author | dist_version | req_version | +---------+----------+-----------------------+----------+--------------+-------------+ | runtime | requires | Finnigan | SELKOVJR | 0.0206 | 1.13 | | runtime | requires | MKDoc-Text-Structured | BPOSTLE | 0.83 | | | runtime | requires | SVN-Churn | RCLAMP | 0.02 | 0 | | runtime | requires | Task-MasteringPerl | BDFOY | 1.002 | 0 | +---------+----------+-----------------------+----------+--------------+-------------+

% lcpan rdeps Getopt::Euclid +---------+----------+------------------------------+-----------+--------------+-------------+ | phase | rel | dist | author | dist_version | req_version | +---------+----------+------------------------------+-----------+--------------+-------------+ | runtime | requires | Audio-MPD | JQUELIN | 2.004 | 0 | | runtime | requires | Games-RailRoad | JQUELIN | 1.101330 | 0 | | runtime | requires | MARC-Record-Stats | CRUSOE | v0.0.4 | 0 | | runtime | requires | Module-Install-PodFromEuclid | FANGLY | 0.01 | 0.3.4 | | runtime | requires | NetHack-PriceID | SARTAK | 0.05 | 0 | | runtime | requires | Task-Cpanel-Internal | CPANEL | 11.36.001 | 0 | | runtime | requires | Test-Approvals | JRCOUNTS | v0.0.5 | 0 | | runtime | requires | VSGDR-StaticData | DEDMEDVED | 0.31 | 0 | | runtime | requires | VSGDR-TestScriptGen | DEDMEDVED | 0.16 | 0 | | runtime | requires | VSGDR-UnitTest-TestSet | DEDMEDVED | 1.34 | 0 | +---------+----------+------------------------------+-----------+--------------+-------------+

If I have to pick betwen Getopt::Declare and Getopt::Euclid, I choose Getopt::Euclid. Getopt::Declare uses Tab character to separate specification fields, which is problematic in some editor configurations. The specification is not exactly a usage message. It mixes in some Perl code in { … } blocks, so you cannot directly use it as a usage message (although a single usage() will produce the final usage message for you). And, if you are writing a documentation POD for your CLI program anyway, you might as well use the POD and parse your list your options from it.

But if I have to pick between the abovementioned two and Docopt, I pick Docopt. Using these modules means learning yet another DSL and you might as well learn Docopt’s flavor which has implementations in many languages. The docopt syntax also makes it easy to express dependencies between options in a compact way, e.g. which options must be mutually exclusive (e.g. (--verbose | --debug | --quiet) ) and which option depends on the existence of other option.

Buuut, and this is the final but, if I have to pick between the Docopt UI-first approach and the “normal” approach (something like Getopt::Long::Descriptive or App::Options or Getopt::Long::More where a structured specification is used to generate usage/documentation instead of the other way around), I’d pick the latter. True, with Docopt we can tune the exact formatting of the usage message. But I usually prefer my usage message to be generated automatically anyway. Using Perl data structure as the specification is better because the syntax can be checked by your usual IDE (on the other hand, I’m sure someone could create or have created a docopt Emacs mode or something.)

And, unless for simpler scripts, I also usually want an option parser module to have the ability to read configuration files (and environment variables). So far, no such Docopt-style modules have been written. Anyone?