Konubinix' opinionated web of thoughts

Polymorphic Command Line Interface (In Clk)


polymorphic command line interface

Creating command line interfaces is in several aspects similar to creating a traditional program. For example, you have to think about the input/output and try to respect the common programming idioms, like SRP or DRY.

I want to highlight that polymorphism is also an idiom that can make sense in creating command line interfaces. To do so, I will use some of the common situations of my day to day life.

Say you have several computers, in different locations, connected to different networks and with different connected hardware.

I like manually starting a script in any of my interactive computers when it is turned on. For instance, in my desktop computer, it:

  1. finds one of the connected drives that may contains one my shamir’s secret sharing of my gpg key
  2. create the gpg key
  3. start the gpg agent
  4. fetch my rss and mails, updating my notmuch database
  5. run supervisor
  6. fetch my calendars and export them into org-mode files

In my phone, the start script does :

  1. run tinc,
  2. start supervisor,

Even thought there share the same abstract logic, they both are very different.

This is where I talk about polymorphism. Indeed, I run the same “interface” on all my computer (clk start), but, depending on the computer, the script does very different things. In other terms, “clk start” is the interface and each computer has an implementation.

Another example is when you navigate from project to projects in the same company. All the project share a same logic

  1. setting up the dependencies
  2. bootstrapping a database
  3. building the project
  4. running the project

In that is so, I can easily create dumb commands that impersonate the workflow, like

  1. clk setup-dependencies
  2. clk db bootstrap
  3. clk build
  4. clk run

Then, I can easily connect them using a flow. I can even create a flow command with the purpose of connecting them.

clk alias --global set work.flow echo "Everything went well"
clk flowdep --global set work.flow setup-dependencies db.bootstrap build run
clk parameter --global set work.flow --flow

And then, I simply have to run “clk work flow” to have all the command run. I can even run “clk work flow –flow-after db.bootstrap” to avoid running once again the bootstrap (it might be sensible sometimes).

And finally, in each project, I can easily overwrite the commands to fit the internal project need.

For instance, in one project, I might want to build using maven, then, I can run

clk alias --global set mvn exec mvn  # I advise to always create a command to exec something to ease customizing later
clk alias --local set build mvn

In another project, I can use cargo

clk alias --local set build exec cargo build

That way, whatever the project I fall into, I know I simply have to call the command “clk work flow” (the interface) and the sensible commands (the implementation) will be called.

I use this a lot in my personal and professional projects.

Also, if for a short time I want to test something new, like I want to make maven skip the tests globally. I simply can run

clk paremeter --global set mvn -Dmaven.test.skip=true -DskipTests

If, for some reason I only want to skip the tests for a particular project, I can simply run the same thing locally.

clk paremeter --local set mvn -Dmaven.test.skip=true -DskipTests

Also, combined with clk extensions, I can put such high level logic in a dedicated extension to enable/disable it at will.

clk extension create mvn_skip_tests
clk paremeter --mvn-skip-tests set mvn -Dmaven.test.skip=true -DskipTests

Then, skipping the tests is simply a matter of running

clk extension enable mvn_skip_tests

And disabling them running

clk extension disable mvn_skip_tests

I hope I illustrated how polymorphism, when applied to command line interfaces, helps creating your own powerful and versatile workflow. I hope I also showed how clk helps you in the process of creating this workflow.