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

欢迎!请参阅关于页面了解有关该工作的一些更多信息。

0
tools.cli

我有一个程序,它接受在CLI中指定的多个选项,我也允许将它们指定在配置文件中。这样,而不是在所有调用中传递--quiet,可以将{quiet true}映射放入.splint.edn中,以自动启用它。有时候,通过在CLI中传递选项来覆盖配置文件是有价值的,所以在构建选项映射时,我首先加载配置文件,然后将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`(在一些人帮助下)对我来说会有效。如果你愿意分享,我很想了解更多关于你的想法。这似乎很有趣,只是很难从抽象转换为实际代码。

1 个答案

+1 投票

选择
 
最佳答案

我不太确定你试图做什么,但是我正在考虑`:default-fn`选项可能对你更有用?

很抱歉有点复杂。也许我可以简单解释一下。

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

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

我希望能从`tools.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
我忘了正式提出,所以如果你接受补丁请求,我可以编写一些示例。
by
贡献库不接受PR。我会为这个创建一个JIRA,如果你已经签署了CLA,你可以为此提供一个补丁。

如果你不想那样正式,可以在Slack上给我一些关于文档更改的建议,我会根据那些建议更新readme。
FTW,README中已经说明了

;; :default值首先应用于选项。有时你可能想在
;; 解析完成后应用默认值,或者具体来说,根据
;; 映射中其他选项的值来计算默认值。在这些情况下,
;; 你可以使用:default-fn来指定一个函数,这个函数
;; 在解析完成后没有任何选项值的情况下被调用,并且
;; 这个函数传递完整的解析选项映射作为它的单个参数。
;; :default-fn (constantly 42) 等同于 :default 42,除非
;; 你有一个非幂等选项(具有 :update-fn 或 :assoc-fn)-- 在这种
;; 的情况下,任何:default值都用作初始选项值而不是nil,
;; 并且在命令行未提供任何值时,:default-fn将调用
;; 来计算最终选项值(因此,:default-fn可以覆盖 :default)

接下来我将添加

;; 备注::default-fn的结果不会进行验证(这是一个
;; 开放讨论的问题,目前不认为这是一个错误)
我已经阅读了这段内容,但我发现它相当难懂。将这段内容与README中说明上述规格的多个示例进行对比。

我将尝试将其写得更易读一些。
...