Polymorphic Command Line Interface (In Clk)
Fleetingpolymorphic 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:
- finds one of the connected drives that may contains one my shamir’s secret sharing of my gpg key
- create the gpg key
- start the gpg agent
- fetch my rss and mails, updating my notmuch database
- run supervisor
- fetch my calendars and export them into org-mode files
In my phone, the start script does :
- run tinc,
- 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
- setting up the dependencies
- bootstrapping a database
- building the project
- running the project
In that is so, I can easily create dumb commands that impersonate the workflow, like
- clk setup-dependencies
- clk db bootstrap
- clk build
- 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.