2024年Clojure状态调查!中分享您的看法。

欢迎!请在关于页面查看有关如何使用本站的更多信息。

0
tools.cli

我有一个程序,该程序在命令行接口(CLI)中接受许多选项,并允许我在配置文件中指定这些选项。这样,您就不需要总是传递--quiet到所有调用中,而可以把映射{quiet true}放入.splint.edn中以自动启用它。有时,通过在命令行中传递选项来覆盖配置文件是非常有价值的。因此,在构建选项映射时,我首先加载配置文件,然后合并CLI选项:(merge local-config cli-options)

然而,如果在tools.cli规范中指定默认值,就会失去这种行为,因为默认选项在合并时无条件地覆盖配置文件,因为它总是出现在CLI选项映射中。

因此,我倾向于不在我的tools.cli规范中省略:default键,而是在选项描述字符串中写"默认为true。",例如[nil "--[no-]parallel" "并行运行splint。默认为true。"],然后是(get config :parallel true),接着是(merge local-config cli-options)。这意味着摘要中不会显示格式化的FALSE(例如,--[no]-parallel FALSE 并行运行splint。

我有一些解决这个小问题的想法(除了“你已经解决它了,不要做任何事情!”以外)
1) 从cli/parse-opts返回一个额外的键,例如:defaults。这将是一个使用了默认值的选项的序列或集合。这样,我可以在其中添加一些逻辑,例如(merge (select-keys cli-options defaults) local-config (apply dissoc cli-options defaults))
2) 向cli/parse-opts规范添加一个:default-display,这将打印默认值而不将其添加到选项映射中。这将取决于用户(开发者)确保实际将其添加到选项映射中或以某种方式使用它。
3) 添加一些功能,使您能够在摘要中指定默认值,但将另一个值放入映射中。例如,示例中可能为--[no]-parallel FALSE,但映射为{:parallel :false}。这将使我可以根据需要选择正确的值。

非常感谢!

您可以通过几种方法实现这一点,而无需修改tools.cli工具。

如果配置文件是众所周知的,那么程序可以首先读取配置文件,并使用其内容作为CLI的默认值。(技术上来说,配置文件将是一个默认值文件。)

如果配置文件由命令选项设置,或者帮助信息必须说明基础默认值而不是配置文件设置,那么程序可以在一个独立的映射中声明默认值,并从它计算CLI定义和有效的选项设置:使用将默认值组合到CLI定义选项描述中的函数从默认值映射中计算CLI定义,并通过合并默认值映射、配置设置和CLI设置按此顺序来计算有效的选项设置。
我不确定我是否完全理解你所描述的代码路径。我在被接受的答案下的评论中进行了更详细的说明,因为`:default-fn`(有些帮助)对我来说将起作用。如果您愿意分享,我很乐意听到更多关于您的想法。这看起来很有趣,但将抽象转换为实际代码有点困难。

 
最佳答案

我不太确定你试图做什么,但我认为`:default-fn`选项可能更适合你?

抱歉它让人困惑。也许我可以简单一点来解释。

我的程序可以将内容打印到标准输出。要检查是否应该这样做,它会查看上下文对象上的`:quiet`。`:quiet`的值确定逻辑如下:

如果用户传入了`--quiet`,则`:quiet`为真。
否则如果用户在配置文件中设置了`quiet`,则`:quiet`为真。
否则`:quiet`为假。

我希望工具的`.cli`从`--help`摘要中显示`false`默认值,因为它格式很漂亮(如下所示)。

```
--[no-]quiet              false  不打印诊断信息,仅显示摘要。
```

然而,在`cli-options`中添加`:default false`后,`cli/parse-opts`会无条件返回`{:options {:quiet false}}`,因此我无法知道用户是否提供了`--quiet`选项,还是工具的`.cli`只是返回了默认值。

再次尝试`:default-fn`,我认为它应该会工作,但文档中没有关于使用示例,这就是为什么我之前没有成功。也许一个或两个示例将帮助未来的其他人。

以下是如何`:default-fn`与`:default`和传入参数交互的演示,任何有相同要求的人都可以参考

```
(def cli-options
  [[nil "--[no-]quiet" "不打印诊断信息,仅显示摘要。"
    :default false
    :default-fn (fn [v] (prn :default-fn v) :new-val)]])

(defn validate-opts
  [args]
  (let [{:keys [options]} (cli/parse-opts args cli-options)]
    (prn :options options)))
```

```
$ clojure -M:run
:default-fn {:quiet false}
:options {:quiet :new-val}

$ clojure -M:run --quiet
:options {:quiet true}

$ clojure -M:run --no-quiet
:options {:quiet false}
```
by
我忘记正式提出了,如果你们接受pull requests,我可以写一个或两个示例。
by
贡献库不接受PRs。我会为这个问题创建一个JIRA,如果你签订了CLA,你可以为此提供一个补丁。

如果你不想那么正式,请在Slack上给我一些关于文档建议的更改,我会基于此更新readme :)
by
FWIW,README 中已经说明了

;; 首先,:default 值应用于选项。有时候你可能在解析完成后
;; 应用默认值,或者专门根据
;; 映射中其他选项值来计算一个默认值。在这些
;; 情况下,你可以使用 :default-fn 来指定一个在解析完成后
;; 被调用的函数,该函数将完整的解析后的选项映射作为
;; 它的唯一参数传入。
;; :default-fn (constantly 42) 与 :default 42等效
;; 除非你有非幂等的选项(带有 :update-fn 或 :assoc-fn)-- 在这种
;; 情况下,任何 :default 值都将用作初始选项值而不是 nil,
;; :default-fn 将在命令行上未给出时调用以计算最终的选项值(因此,:default-fn 可以
;; 覆盖 :default)

我即将添加

;; 注意::default-fn 的结果不会进行验证(这是一个
;; 敞开讨论的问题,目前不认为是错误)。
by
我阅读了那段内容,但我发现它相当密集。将那段内容与readme中上面展示的规格的多个示例进行对比。

我来尝试一下更易于阅读的内容。
...