$ clj
Clojure 1.11.2
user=>
Clojure 提供用于以下操作的命令行工具:
运行交互式 REPL(读取-求值-打印循环)
运行 Clojure 程序
求值 Clojure 表达式
在所有上述场景中,您可能希望使用其他 Clojure 和 Java 库(依赖项或“deps”)。这些可能是您在本地编写的库,git 中的项目(例如,在 GitHub 上),或者更常见的是,在 Maven 生态系统中可用并由 Maven Central 或 Clojars 等中央存储库托管的库。
在所有情况下,使用库都涉及:
指定要使用的库,提供其名称和其他方面,如版本
从 git 或 maven 存储库获取它(一次)到您的本地机器
使其在 JVM 类路径上可用,以便 Clojure 在您的 REPL 或程序运行时可以找到它
Clojure 工具指定了一种语法和文件 (deps.edn
) 用于 (a),在这种情况下,它们将自动处理 (b) 和 (c)。
有关如何安装工具的详细信息,请参见 入门。在这里,我们将演示如何入门。有关完整参考,请参见 Clojure CLI 和 deps.edn。有关版本信息,请参见 变更日志。
下载并安装工具后,您可以通过运行 clj
工具启动 REPL
$ clj
Clojure 1.11.2
user=>
进入 REPL 后,您可以键入 Clojure 表达式并按 Enter 键进行求值。键入 Control-D 退出 REPL
user=> (+ 2 3) # press the `enter` key after typing the expression to evaluate it
5 # result of expression
user=> # type Ctrl-D here to exit the REPL (does not print)
$
有许多可用的 Clojure 和 Java 库,它们提供了您可能需要的几乎所有功能的访问权限。例如,考虑常用的 Clojure 库 clojure.java-time 用于处理日期和时间。
要使用此库,您需要将其声明为依赖项,以便工具可以确保它已下载并将其添加到类路径中。大多数项目的自述文件都显示了要使用的名称和版本。创建一个 deps.edn
文件来声明依赖项
{:deps
{clojure.java-time/clojure.java-time {:mvn/version "1.1.0"}}}
或者,如果您不知道版本,可以使用 find-versions
工具,它将以排序顺序列出所有可用的坐标
$ clj -X:deps find-versions :lib clojure.java-time/clojure.java-time
...omitted
{:mvn/version "1.0.0"}
{:mvn/version "1.1.0"}
使用 clj
工具重新启动 REPL
$ clj
Downloading: clojure/java-time/clojure.java-time/1.1.0/clojure.java-time-1.1.0.pom from clojars
Downloading: clojure/java-time/clojure.java-time/1.1.0/clojure.java-time-1.1.0.jar from clojars
Clojure 1.11.2
user=> (require '[java-time.api :as t])
nil
user=> (str (t/instant))
"2022-10-07T16:06:50.067221Z"
您将在第一次使用依赖项时看到有关库下载的信息。一旦文件下载完成(通常下载到 ~/.m2
或 ~/.gitlibs
),它将在将来被重复使用。您可以使用相同的过程将其他库添加到您的 deps.edn
文件中并探索 Clojure 或 Java 库。
很快您就会想要构建和保存自己的代码,这些代码利用了这些库。创建一个新目录并将此 deps.edn
复制到其中
$ mkdir hello-world
$ cp deps.edn hello-world
$ cd hello-world
$ mkdir src
默认情况下,clj
工具将在 src
目录中查找源文件。创建 src/hello.clj
(ns hello
(:require [java-time.api :as t]))
(defn time-str
"Returns a string representation of a datetime in the local time zone."
[instant]
(t/format
(t/with-zone (t/formatter "hh:mm a") (t/zone-id))
instant))
(defn run [opts]
(println "Hello world, the time is" (time-str (t/instant))))
您可能决定将此应用程序的一部分移到库中。clj
工具使用本地坐标来支持仅存在于本地磁盘上的项目。让我们将此应用程序的 java-time 部分提取到并行目录 time-lib 中的库中。最终结构将如下所示
├── time-lib │ ├── deps.edn │ └── src │ └── hello_time.clj └── hello-world ├── deps.edn └── src └── hello.clj
在 time-lib 下,使用您已经拥有的 deps.edn
文件的副本,并创建一个文件 src/hello_time.clj
(ns hello-time
(:require [java-time.api :as t]))
(defn now
"Returns the current datetime"
[]
(t/instant))
(defn time-str
"Returns a string representation of a datetime in the local time zone."
[instant]
(t/format
(t/with-zone (t/formatter "hh:mm a") (t/zone-id))
instant))
更新 hello-world/src/hello.clj
中的应用程序以使用您的库
(ns hello
(:require [hello-time :as ht]))
(defn run [opts]
(println "Hello world, the time is" (ht/time-str (ht/now))))
修改 hello-world/deps.edn
以使用引用 time-lib 库根目录的本地坐标(确保更新您的机器的路径)
{:deps
{time-lib/time-lib {:local/root "../time-lib"}}}
然后,您可以通过运行应用程序从 hello-world 目录测试所有内容
$ clj -X hello/run
Hello world, the time is 12:22 PM
与他人共享该库将是一件很棒的事情。您可以通过将项目推送到公共或私有 git 存储库并让其他人使用 git 依赖项坐标来完成此操作。
首先,为 time-lib 创建一个 git 库
cd ../time-lib
git init
git add deps.edn src
git commit -m 'init'
然后转到公共 git 存储库主机(如 GitHub)并按照创建和发布此 git 存储库的说明进行操作。
我们还希望标记此版本,使其具有有意义的版本
git tag -a 'v0.0.1' -m 'initial release'
git push --tags
最后,修改您的应用程序以使用 git 依赖项。您需要收集以下信息
存储库 lib - Clojure CLI 使用一种约定,如果您使用类似 io.github.yourname/time-lib
的库名称,则无需指定 URL,对应 GitHub url 为 https://github.com/yourname/time-lib.git
。
tag - v0.0.1
是我们在上面创建的
sha - 标签处的短 sha,如果您本地拥有该 repo,则使用 git rev-parse --short v0.0.1^{commit}
查找它,或者如果您是远程的,则使用 git ls-remote https://github.com/yourname/time-lib.git v0.0.1
查找它。您也可以使用 GitHub repo 查看标签及其支持的提交。
更新 hello-world/deps.edn
以使用 git 坐标
{:deps
{io.github.yourname/time-lib {:git/tag "v0.0.1" :git/sha "4c4a34d"}}}
现在,您可以再次运行应用程序,使用(共享)git 存储库库。第一次运行时,您将在控制台上看到额外的消息,当 clj
下载和缓存存储库以及提交工作树时
$ clj -X hello/run
Cloning: https://github.com/yourname/time-lib
Checking out: https://github.com/yourname/time-lib at 4c4a34d
Hello world, the time is 02:10 PM
现在,您的朋友也可以使用 time-lib
了!
随着您的程序变得更加复杂,您可能需要创建标准类路径的变体。Clojure 工具使用别名支持类路径修改,别名是 deps 文件的一部分,仅在提供相应的别名时使用。您可以做的一些事情是
通常,项目类路径默认情况下只包含项目源代码,不包含其测试源代码。您可以在类路径构建的 make-classpath 步骤中,将额外的路径添加为对主要类路径的修改。为此,添加一个别名 :test
,它包含额外的相对源代码路径 "test"
{:deps
{org.clojure/core.async {:mvn/version "1.3.610"}}
:aliases
{:test {:extra-paths ["test"]}}}
应用该类路径修改并通过调用 clj -A:test -Spath
检查修改后的类路径
$ clj -A:test -Spath
test:
src:
/Users/me/.m2/repository/org/clojure/clojure/1.11.2/clojure-1.11.2.jar:
... same as before (split here for readability)
请注意,测试目录现在已包含在类路径中。
您可以扩展上一节中的 :test
别名以包含 cognitect-labs test-runner,用于运行所有 clojure.test 测试
扩展 :test
别名
{:deps
{org.clojure/core.async {:mvn/version "1.3.610"}}
:aliases
{:test {:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}}}
然后使用默认配置执行测试运行器(运行 -test 命名空间下的所有测试,位于 test/ 目录下)
clj -X:test
deps.edn
文件中的别名也可以用于添加影响类路径的可选依赖项
{:aliases
{:bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}}
这里 :bench
别名用于添加额外的依赖项,即 criterium 基准测试库。
您可以通过将 :bench
别名添加到修改依赖项解析中来将此依赖项添加到您的类路径中:clj -A:bench
。
在不将其添加到现有 deps.edn
文件中或不创建一个文件的情况下,尝试使用一个库可能很有用。
$ clojure -Sdeps '{:deps {org.clojure/core.async {:mvn/version "1.5.648"}}}'
Clojure 1.11.2
user=> (require '[clojure.core.async :as a])
nil
请注意,由于转义规则,最好将配置数据放在单引号中。
一些依赖项在可以在类路径上使用之前需要一个准备步骤。这些库应该在其 deps.edn
中说明这种需求
{:paths ["src" "target/classes"]
:deps/prep-lib {:alias :build
:fn compile
:ensure "target/classes"}}
包含顶层键 :deps/prep-lib
告诉 tools.deps 类路径构建,需要额外的东西来准备此库,并且可以通过调用 :build
别名中的 compile
函数来执行。一旦准备步骤完成,它应该创建路径 "target/classes"
,并且可以检查是否已完成。
您像使用任何其他基于源代码的库一样依赖此库(可能是 git 或本地)
{:deps {my/lib {:local/root "../needs-prep"}}}
如果您随后尝试将该库包含到您的类路径中,您将看到一个错误
$ clj
Error building classpath. The following libs must be prepared before use: [my/lib]
然后,您可以使用此命令告诉 CLI 准备(对于特定库版本,这是一次性操作)
$ clj -X:deps prep
Prepping io.github.puredanger/cool-lib in /Users/me/demo/needs-prep
$ clj
Clojure 1.11.2
user=>
您可以组合使用多个别名。例如,此 deps.edn
文件定义了两个别名 - :old-async
用于强制使用旧版本的 core.async,以及 :bench
用于添加额外的依赖项
{:deps
{org.clojure/core.async {:mvn/version "0.3.465"}}
:aliases
{:old-async {:override-deps {org.clojure/core.async {:mvn/version "0.3.426"}}}
:bench {:extra-deps {criterium/criterium {:mvn/version "0.4.4"}}}}}
按如下方式激活这两个别名:clj -A:bench:old-async
。
有时您可能需要直接引用磁盘上不在 Maven 存储库中的 jar,例如数据库驱动程序 jar。
使用指向 jar 文件而不是目录的本地坐标指定本地 jar 依赖项
{:deps
{db/driver {:local/root "/path/to/db/driver.jar"}}}
使用 gen-class 或 gen-interface 时,Clojure 源代码必须提前编译以生成 java 类。
这可以通过调用 compile
来完成。编译后的类文件的默认目标是 classes/
,需要创建并将它添加到类路径中
$ mkdir classes
编辑 deps.edn
以将 "classes"
添加到路径中
{:paths ["src" "classes"]}
在 src/my_class.clj
中使用 gen-class 声明一个类
(ns my-class)
(gen-class
:name my_class.MyClass
:methods [[hello [] String]])
(defn -hello [this]
"Hello, World!")
然后您可以在另一个源文件 src/hello.clj
中使用 :import
引用该类。请注意,命名空间也已添加到 :require
中,以便编译可以自动找到所有依赖的命名空间并编译它们。
(ns hello
(:require [my-class])
(:import (my_class MyClass)))
(defn -main [& args]
(let [inst (MyClass.)]
(println (.hello inst))))
您可以在 REPL 中编译或运行脚本进行编译
$ clj -M -e "(compile 'hello)"
然后运行 hello 命名空间
$ clj -M -m hello
Hello, World!
有关完整的参考,请参见 编译和类生成。
Clojure 提供了对运行 套接字服务器 的内置支持,特别是使用它们来托管远程 REPL。
要配置套接字服务器 REPL,请将以下基本配置添加到您的 deps.edn
中
{:aliases
{:repl-server
{:exec-fn clojure.core.server/start-server
:exec-args {:name "repl-server"
:port 5555
:accept clojure.core.server/repl
:server-daemon false}}}}
然后通过使用别名调用来启动服务器
clojure -X:repl-server
如果您愿意,您也可以在命令行上覆盖默认参数(或添加其他选项)
clojure -X:repl-server :port 51234
您可以使用 netcat 从另一个终端连接
nc localhost 51234
user=> (+ 1 1)
2
使用 Ctrl-D 退出 REPL,使用 Ctrl-C 退出服务器。
内置的 :deps
别名中包含几个有用的工具,用于探索项目使用的所有传递依赖项(及其许可证)。
要 列出 类路径上包含的所有依赖项的完整集,请使用 clj -X:deps list
。例如,在本指南顶部的 hello-world
应用程序中,您将看到类似于以下内容
% clj -X:deps list
clojure.java-time/clojure.java-time 1.1.0 (MIT)
org.clojure/clojure 1.11.2 (EPL-1.0)
org.clojure/core.specs.alpha 0.2.62 (EPL-1.0)
org.clojure/spec.alpha 0.3.218 (EPL-1.0)
time-lib/time-lib ../cli-getting-started/time-lib
应用程序使用的所有传递依赖项的完整集按字母顺序列出,并附带版本和许可证。有关其他打印选项,请参见 api 文档。
如果您想了解依赖项的 树 结构以及如何做出版本选择,请使用 clj -X:deps tree
% clj -X:deps tree
org.clojure/clojure 1.11.2
. org.clojure/spec.alpha 0.3.218
. org.clojure/core.specs.alpha 0.2.62
time-lib/time-lib /Users/alex.miller/tmp/cli-getting-started/time-lib
. clojure.java-time/clojure.java-time 1.1.0
这里没有进行版本选择,但是请参见 文档,了解如何在需要时在树中解释选择的更多信息。
这两个辅助函数都接受一个可选的 :aliases
参数,如果您希望使用一个或多个别名(例如 clj -X:deps list :aliases '[:alias1 :alias2]'
)检查依赖项列表或树。
原始作者:Alex Miller