May 19, 2020
By: Kevin
Clojure脚本开发bb和nbb
使用Clojure开发脚本
脚本有别于web和桌面应用开发, 第一需求是快, 快速启动, 快速结束. 执行效率不是要求特别高. 主要看中是启动速度.
能够方便的调用其他脚本, 文本操作能力强.
在最早写这片文章的时候, 是2020年, 当时Lumo看起来还是个不错的方案, 发展到2023年, clojure脚本领域的王者无疑是bb(babashka)和nbb(nodejs babashka), 这俩都是毫秒级启动.
而且生态强大(借助nodejs和java生态), 文法规范, 表达力强, 特别适合做项目脚本.
- nbb基于nodejs, 完善的nrepl支持, 对js生态熟悉的可以尝试下.
- bb基于GraalVM, 完善的nrepl支持. 启动迅速, 依赖简单, 只依赖与一个不到10m的解释器.
问题
写一个脚本, 读取当前的git commit, 获得当前时间, 把这两个内容插入到源代码合适的位置(便于显示).
代码
nbb版本
使用nbb来执行以下代码
;; 使用shelljs, moment, os, moment
(require '["shelljs$default" :as sh])
(require '["fs" :as fs])
(require '["os" :as os])
(require '["moment$default" :as moment])
(require '[clojure.string :as str])
(require '[clojure.tools.cli :refer [parse-opts]])
;; 我们要修改的文件
(def portal-cljs "public/index.html")
(defn osx? []
(str/starts-with? (.platform os) "darwin"))
(defn shell []
(if (osx?)
"bash"
"powershell"))
(defn latest-commit []
(->> (sh/exec "git log -1")
str
(re-seq #"commit (\w+)")
first
second))
(defn package-time []
(-> (new js/Date)
moment
(.format "yyyy-MM-DD HH:mm")))
(defn package-day []
(-> (package-time)
(str/split #" ")
first))
(defn version-time []
(str "version:" (latest-commit) " build at: " (package-time)))
(defn process-file [file]
(-> file
(fs/readFileSync)
str
(str/replace #"version.*" (version-time))
(#(fs/writeFileSync file %))))
(def cli-options
[["-p" "--package"]])
(let [options (parse-opts *command-line-args* cli-options)
package? (get-in options [:options :package])]
(process-file portal-cljs)
)
bb版本
使用 bb script.clj 来执行, 你发这两个脚本非常类似, 除了os, shell, 时间处理依赖的库不太一样.
#!/usr/bin/env bb
(require '[clojure.java.shell :as shell])
(require '[clojure.string :as str])
(require '[clojure.tools.cli :refer [parse-opts]])
(def portal-cljs "src/cljs/pages/portal.cljs")
(def ws-clj "src/clj/ws.clj")
(defn win? []
(str/starts-with? (System/getProperty "os.name") "Windows"))
(defn shell []
(if (win?)
"powershell"
"bash"))
(defn latest-commit []
(->> (shell/sh "git" "log" "-1")
:out
(re-seq #"commit (\w+)")
first
second))
(defn package-time []
(let [zone (java.time.ZoneId/of "Asia/Shanghai")
pattern (java.time.format.DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm")]
(-> (java.time.ZonedDateTime/now)
(.withZoneSameInstant zone)
(.format pattern))))
(defn package-day []
(-> (package-time)
(str/split #" ")
first))
(defn version-time []
(format "version:%s build at:%s, trail version will expire in 7 days" (latest-commit) (package-time)))
(defn process-file [file]
(-> file
slurp
(str/replace #"(def \^\:private PACKAGE-DAY )\".+\"" (format "$1\"%s\"" (package-day)))
(str/replace #"(def \^\:private VERSION )\".+\"" (format "$1\"%s\"" (version-time)))
(#(spit file %))))
(defn build-package []
(apply shell/sh (shell) (-> "npx shadow-cljs release :app :electron;"
(str/split #" ")))
(apply shell/sh (shell) (-> "npx electron-builder build"
(str/split #" "))))
(def cli-options
[["-p" "--package"]])
(let [options (parse-opts *command-line-args* cli-options)
package? (get-in options [:options :package])]
(process-file portal-cljs)
(process-file ws-clj)
(when package?
(build-package)))
repl的支持
两者的参数完全一致, 推荐使用 nrepl-server 启动后再用cider/calva connect上来进行开发.
- repl Start REPL. Use rlwrap for history.
- socket-repl [addr] Start a socket REPL. Address defaults to localhost:1666.
- nrepl-server [addr] Start nREPL server. Address defaults to localhost:1667.
结论
比起shell来, clojure有更丰富的数据结构, 更好的调试体验(repl). 鼓励大家尝试