smart arguments
- 3 minutes read - 513 wordsIt’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
.
- In the above example,
- each arg value
a_k
has a qualifier predicateq_k
that returns true for a valuev
ifv
is valid for arga_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
- qualifier predicate:
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