February 16, 2021
By: Kevin
babashka的pods
babashka是一个基于Graalvm的clojure解释器, 避免了传统JVM启动慢, 内存消耗高的问题. 非常适合写脚本.
我们blog已经有一篇用babashka进行文本处理的例子
最近这个项目演进迅速(捐助者多). 最新两个引入了两个很吸引我的功能
- 使用tools.deps引入大量的clojure兼容库
- pods可以把任意的第三方库/程序, 封装为可以被babashka使用的库, 极大扩展了使用领域.
下面用honeySQL和sqlite-go, 分别进行了展示.
babashka对tools.deps的支持
clojure官方的依赖管理工具, 可以动态加入依赖(执行时).
tools.deps是个值得单独介绍的工具, 随后专门写一篇.
以sqlite为例子, 下面的脚本动态载入了honysql (一个动态生成sql的库), 实现了本地sqlite数据库通过shell的CRUD.
#!/usr/bin/env bb
;; Hashbang开头, 和其他脚本并无区别, 保证可以直接执行
(ns example
(:require [babashka.deps :as deps]
[clojure.data.csv :as csv]
[clojure.java.io :as io]
[clojure.java.shell :refer [sh]]
[clojure.pprint :as pp]
[clojure.string :as str]))
(def db "/tmp/analysis.db")
(defn query [& args]
(apply sh "sqlite3" "-quote" "-header" db args))
;;此处动态的载入 HoneySQL
(deps/add-deps '{:deps {seancorfield/honeysql {:mvn/version "2.0.0-alpha2"}}})
(require '[honey.sql :as sql]
'[honey.sql.helpers :as h])
(def crate-sql
(sql/format
(-> (h/create-table :animals)
(h/with-columns [[:eng-name :text]
[:chi-name :text]]))))
(defn create-db! []
(when-not (.exists (io/file db))
(query (first crate-sql))))
(defn insert-sql [animals]
(sql/format (-> (h/insert-into :animals)
(h/values animals))
{:inline true}))
(comment
(insert-sql [{:eng-name "Bison" :chi-name "水牛"}
{:eng-name "Duck" :chi-name "鸭子"}])
(query "INSERT INTO animals (eng_name, chi_name) VALUES ('a', 'b')")
)
(defn insert-rows! [animals]
(let [sql (insert-sql animals)
{:keys [exit err]} (query (first sql))]
(when (not (zero? exit))
(println "Error insert var!" err))))
(.delete (io/file db))
(create-db!)
(insert-rows! [{:eng-name "Bison" :chi-name "水牛"}
{:eng-name "Duck" :chi-name "鸭子"}])
(defn csv-data->maps [csv-data]
(map zipmap
(->> (first csv-data) ;; header
(map keyword) ;; unless you want string keys instead.
repeat)
(rest csv-data)))
(defn all-animals []
(let [csv-string (-> (query (str/join " " ["select * from animals"]))
:out)
reader (java.io.StringReader. csv-string)]
(with-open [rdr reader]
(doall (csv-data->maps (csv/read-csv rdr :quote \'))))))
(pp/print-table (all-animals))
babashka对pods的支持
pods是一些外部程序, 可以作为clojure的libary被babashka使用, pods本身可以用任何语言写成.比如下面的例子使用了go语言对sqlite的封装.
所以数据库操作不需要通过shell, 而是函数调用来完成, 省掉了组装sql和解析sql返回数据解析的过程, 也不需要在中间生成额外的shell进程.
#!/usr/bin/env bb
(ns example
(:require [babashka.deps :as deps]
[babashka.pods :as pods]
[clojure.data.csv :as csv]
[clojure.java.io :as io]
[clojure.pprint :as pp]
[clojure.string :as str]))
(def db "/tmp/analysis.db")
;; sqlite pod, golang写的
(pods/load-pod 'org.babashka/go-sqlite3 "0.0.1")
;; 执行上一语句本地会产生一个pod `~/.babashka/pods/repository/org.babashka/go-sqlite3`
(require '[pod.babashka.go-sqlite3 :as sqlite])
(deps/add-deps '{:deps {seancorfield/honeysql {:mvn/version "2.0.0-alpha2"}}})
(require '[honey.sql :as sql]
'[honey.sql.helpers :as h])
(def crate-sql
(sql/format
(-> (h/create-table :animals)
(h/with-columns [[:eng-name :text]
[:chi-name :text]]))))
(defn create-db! []
(when-not (.exists (io/file db))
(sqlite/execute! db crate-sql)))
(defn insert-sql [animals]
(sql/format (-> (h/insert-into :animals)
(h/values animals))))
(defn insert-rows! [animals]
(let [sql (insert-sql animals)]
(sqlite/execute! db sql)))
(.delete (io/file db))
(create-db!)
(insert-rows! [{:eng-name "Bison" :chi-name "水牛"}
{:eng-name "Duck" :chi-name "鸭子"}])
(defn all-animals []
(sqlite/query db ["select * from animals"]))
(pp/print-table (all-animals))
脚本执行
$time bb scripts/sqlite-pod.clj
| :eng_name | :chi_name |
|-----------+-----------|
| Bison | 水牛 |
| Duck | 鸭子 |
0.40s user 0.07s system 104% cpu 0.454 total