It's easy to love the terseness one can get out of a command-line tool with a "pass by order" convention. For example, maybe I run something like

deploy myapp sea2 production

to deploy an application called myapp to the second production datacenter near Seattle. This works great, except now I need to remember the order of arguments is: deploy [app] [datacenter] [environment].

The typical solve for this problem is introducing named arguments, so you'd end up with

deploy --app myapp --datacenter sea2 --environment production

This no longer depends on the order, because we're explicit about which value we're passing for each argument. The command itself is a LOT longer though!

In [41]: len("deploy myapp sea2 production")
Out[41]: 28

In [42]: len("deploy --app myapp --datacenter sea2 --environment production")
Out[42]: 61

We can improve this a bit with short named arguments, like

deploy -a myapp -d sea2 -e production

In [43]: len("deploy -a myapp -d sea2 -e production")
Out[43]: 37

but now we still need to remember that the arguments are a, d, and e and which values they'll correspond to. Indeed, we might even have some code that checks that the value of environment is either "staging" or "production"!

So what if we turned that on its head? We can use our error checking code to automagically figure out which argument value corresponds to which argument!

formalism

  • you know you need N args, a0...aN.
    • In the above example, a0=myapp, a1=sea2, a2=production.
  • each arg value a_k has a qualifier predicate q_k that returns true for a value v if v is valid for arg a_k
  • last v wins (e.g. http://a.com + http://b.com prefers the second)

example 1

Suppose we want a markdown link generator, which takes two args:

  • a url (like https://traviscj.com/blog)
    • qualifier predicate: "starts with http"
    • qualifier predicate: "contains ://"
  • a slug (like myblog)
    • qualifier predicate: !url

example 2

Another nice example is if our arguments are a combination of app, datacenter, and environment, with values like these:

  • app: a / b / c / ...
  • datacenter: sea1 / sea2 / sea3 / ... / ord1 / ord2 / ord3 / ...
  • environment: staging / production

We can define simple predicates for the latter two:

  • is_datacenter(a) := a.startswith("sea") or a.startswith("ord")
  • is_environment(a) := a in ["staging", "production"]

The app arg is a bit trickier, though, because the engineers might name things however they want! The ideal case would be some internal app that maintains a list of valid applications, but failing that, we could also declare something like

# is_app(a) := not is_datacenter(a) and not is_environment(a)
def is_app(a):
  return not is_datacenter(a) and not is_environment(a)

example 3

md link generator w/ alt text

like ex1, but:

  • a link text

    • qual predicate: slug arg exists & string length greater than slug arg length
    • qual predicate: any whitespace
  • test impl in Scratch_kFFXI4

ambiguity resolutions

One trouble we might have is a given argument value qualifying for multiple arguments. When that happens, we can (roughly in order of increasing danger):

  • blow up
  • ask for resolution (ie prompt)
  • assume a default value
  • guess
  • loop over possibilities

other extensions

  • disqualifier predicates