Well Behaved Command Line Tools

栏目: IT技术 · 发布时间: 4年前

内容简介:Yesterday Vlaaad wrote a pretty cool blog post titledThe benefits are illustrated well in the post. It removes all possible friction. Want to add a new subcommand? Just add a function. Adding CLI arguments and options equals adding arguments and options to

Yesterday Vlaaad wrote a pretty cool blog post titled Alternative to tools.cli in 10 lines of code . It’s a great read and a very cool hack. It uses read-string to parse command line arguments, resolves symbols, and invokes the first symbol/function, passing in the rest of the parsed arguments. In other words: it’s the most straightforward way to translate a command line invocation into a Clojure function call.

The benefits are illustrated well in the post. It removes all possible friction. Want to add a new subcommand? Just add a function. Adding CLI arguments and options equals adding arguments and options to said function.

It’s actually not too different from what people do in shell scripts sometimes.

#!/bin/sh

hello() { echo "Hi, $1"; }
world() { echo "It's big and it's blue"; }

# Expand all arguments to the script, so the first one is interpreted as a
# command
"$@"

What I like about Vlaad’s approach is that it establishes clear semantics. If you’re a Clojure programmer you are familiar with how Clojure’s reader works, and so your intuition transfers nicely. If you squint it almost looks like you’re just invoking a function from the REPL.

So for tools that are either personal/internal, or that are really only meant to be used by a Clojure audience I think this makes sense. That said I would like to make a case for clojure.cli . Don’t dismiss it too easily.

Perhaps it seems a little large for what it does (how hard can command line argument parsing be, right? … right?), but that’s because it takes care of a lot of things for you. It makes sure your program follows established conventions, minimizing suprises for your users.

With Babashka and GraalVM more and more Clojure friends are venturing into writing command line utilities. deps.edn is also helping with this resurgance, since a lot of tools that used to be Leiningen plugins are now simply libraries with a -main namespace. They all need some form of command line argument parsing, and many end up rolling their own ad-hoc version, loosely but inconsistently based on existing UNIX conventions.

Now UNIX is famously inconsistent as well (looking at you dd ). Just like in Clojure there’s a big desire for retaining compatibility, so older tools tend to have their idiosyncracies, but generally speaking things are converging towards common conventions, which are described for instance in the GNU libc manual .

I should note that these are the GNU conventions, they are not universal, but they are widely used. GNU in particular pioneered the double dash for long option names which has become ubiquitous. POSIX (the standard that most UNIXes adhere to) only mentions single-letter options . If you want to read more about the overgrowth of command line styles this archived StackOverflow question has you covered.

So what are those conventions? First of all a distinction is made between “options” and “arguments”. Options start with - or -- , the rest are arguments.

ls --color ./*
           ^------ argument
   ^-------------- option

Arguments may be positional, that’s up to how the program interprets them, but options can be supplied in any order typically without changing the semantics. They can be written before, after, or in between arguments, or all of the above at the same time. Not dealing with this correctly is probably the most common “violation” I see out there.

A high profile example is cljs.main I use this example because a lot of people will be familiar with it, and because I think the ClojureScript maintainers can take this bit of constructive criticism. Their design choices are of course valid, and they have good reason to avoid adding more dependencies, I’m merely using this as an illustration of what it looks like when one deviates from these — admittedly fuzzy — “UNIX conventions”, because this is actually a case that has tripped me up more than once.

When invoking cljs.main the --main , --compile or --repl options are positional, they have to come last, after any “init options”.

Say you run a command and it’s not clear if it’s succeeded or not, so you want to run it with --verbose . You press “up” in your terminal and tack on --verbose . Oops, that doesn’t work. --verbose is an “init option”, it has to come before the main/repl/compile option… Not very intuitive.

At this point it’s maybe a good time to say a few words about - vs -- . GNU tools tend to have “short options” that start with a single dash followed by a single letter, and “long options” starting with a double dash. For example ls -a and ls --all . The short ones are always a single letter because this allows combining them in a single chunk, like ls -adR (the equivalent of ls --all --directory --recursive ).

Options can take associated values. The GNU conventions use = for this, a lot of tools also just use a space. tools.cli (and a lot of GNU tools it seems) will understand both.

ls --format=commas
ls --format commas

tools.cli lets you pick both short and long options, but my advise is to start with only long options. It’s more readable and explicit. If certain options are used very commonly then look into providing some short versions. By that time you should have an overview of the different options you’ll be offering, and can make reasoned decisions about which letter to allocate to which option.

The only short options I tend to always add are -h / --help and -v / --verbose . While you’re at it maybe also add a --version .

- and -- also have a specific meaning when used by themselves. -- acts as a terminator, anything after it is treated as arguments instead of options, whether it starts with a dash or not. This is especially useful if your tool ends up invoking some other tool, and you want to allow passing options through verbatim. This is another thing that tools.cli gives you for free.

- by itself can often be used instead of a file name, in which case STDIN will be used as input file. It is also common for tools to read from STDIN if no input file is specified, which makes it convenient to combine them with UNIX pipes. But this convention you’ll have to implement yourself, tools.cli can’t help you with that.

I hope this post will convince people to not dismiss tools.cli too quickly. For general purpose tools, especially those with reach beyond the Clojure community sticking to existing conventions is a Good Thing To Do.

To finish up here’s a little template to get you started. Don’t be daunted by the hefty README, you don’t need that much to get started.

(ns org.example.my.tool
    (:gen-class)
    (:require [clojure.tools.cli :as cli]))

  (def VERSION "0.0.1")

  (def cli-opts
    [["-v" "--verbose" "Increase verbosity" :default 0 :update-fn inc]
     [nil  "--version" "Print version and exit"]
     ["-h" "--help" "Print this help information"]])

  (defn main [args {:keys [] :as opts}]
    ;; main functionality goes here, you get a seq of positional arguments, and a
    ;; map of options.
    )

  (defn -main [& args]
    (let [{:keys [errors options arguments summary]} (cli/parse-opts args cli-opts)]
      (cond
        (seq errors)
        (do
          (run! println errors)
          (println summary)
          (System/exit -1)) ;; non-zero exit code if something goes wrong

        (:help options)
        (do
          (println summary)
          (System/exit 0))

        (:version options)
        (do
          (println VERSION)
          (System/exit 0))

        :else
        (main arguments options))))

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

《Unity3D网络游戏实战(第2版)》

《Unity3D网络游戏实战(第2版)》

罗培羽 / 机械工业出版社 / 2019-1-1 / 89.00元

详解Socket编程,搭建稳健的网络框架;解决网游中常见的卡顿、频繁掉线等问题;探求适宜的实时同步算法。完整的多人对战游戏案例,揭秘登录注册、游戏大厅、战斗系统等模块的实现细节。 想要制作当今热门的网络游戏,特别是开发手机网络游戏,或者想要到游戏公司求职,都需要深入了解网络游戏的开发技术。本书分为三大部分,揭示网络游戏开发的细节。 第一部分“扎基础”(1-5章) 介绍TCP网络游......一起来看看 《《Unity3D网络游戏实战(第2版)》》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具