请分享您的看法参加 2024 年 Clojure 状态调查!

欢迎!有关如何工作的更多信息,请参阅 关于 页面。

0
tools.cli

我有一个程序,它接受在 CLI 中指定的一组选项,我也允许这些选项在配置文件中指定。这样,你不需要在所有调用中都传递 --quiet,你只需在 .splint.edn 中放入地图 {quiet true} 以自动启用它。有时通过在 CLI 中传递选项来覆盖配置文件是有价值的,因此在构建选项映射时,我首先加载配置文件,然后合并 CLI 选项:(merge local-config cli-options)

然而,如果我工具-cli 规范中指定了默认值,我将失去这种功能,因为默认选项将在合并时无条件地覆盖配置文件,因为它总是存在于 CLI 选项映射中。

我倾向于从我的工具-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

Noah 选中
 
最佳答案

我不太确定您要做什么,但我想 `: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`选项还是`tools.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}
```
我忘记正式提出帮助了,所以如果你接受 pull requests,我 可以写一个或两个示例。
贡献库不接受 PR。我将为这个问题创建一个 JIRA,如果你已经签署了 CLA,你可以提交一个补丁。

如果你不想太正式,可以在Slack上给我一些建议对文档进行修改,我会根据这些建议更新readme文件:)
就事论事,README里已经说

;; :default值首先应用于选项。有时你可能想
;; 在解析完成后应用默认值,或者特别地
;; 根据地图中其他选项值计算默认值。在这些
;; 情况下,你可以使用::default-fn来指定一个函数,
;; 用于解析完成后没有值的任何选项,并且
;; 它只接受完整的、解析后的选项映射作为单一参数。
;; :default-fn (constantly 42) 实际上是等同于 :default 42,除非
;; 你有一个非幂等选项(带有:update-fn或:assoc-fn)--在这种情况下
;; **any**:default值将作为初始选项值而不是nil,并且
;; :default-fn将被调用以计算最终的选项值,如果命令行没有
;; 给出(因此:default-fn可以覆盖:default)

我将要添加

;; 注意::default-fn的结果**不会**进行验证(这是一个
;; 留待讨论的开放问题,目前并不认为这是一个错误)。
我读过了,但我觉得内容相当密集。把那段话跟readme里面上面演示规格各部分多个例子比较吧。

我将尝试写得更易于理解一点。
...