Skip to content

Commit 7810dcf

Browse files
authored
feat: [#23] Lazy loading of occasionally needed code (#27)
1 parent c66e06f commit 7810dcf

File tree

9 files changed

+193
-107
lines changed

9 files changed

+193
-107
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ jobs:
1818
- name: Setup Babashka
1919
uses: turtlequeue/[email protected]
2020
with:
21-
babashka-version: 0.7.0
21+
babashka-version: 0.7.3
22+
23+
- name: Register bb clojure as clojure command
24+
run: |
25+
echo -e '#!/usr/bin/env bash\n\nbb --clojure "$@"' > /usr/local/bin/clojure
26+
chmod +x /usr/local/bin/clojure
2227
2328
- uses: actions/checkout@v2
2429
- uses: actions/setup-java@v2

bb.edn

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
:sha "a83ee8da47d56a80b6380cbb6b4b9274048067bd"}}
66

77
:tasks {:requires [[babashka.fs :as fs]
8+
[clojure.string :as str]
89
[clojure.edn :as edn]]
910

10-
:init (do (def target-dir "out")
11+
:init (do (def target-dir (fs/absolutize "out"))
1112
(def executable-name "obb")
1213
(def bin-dir (str (fs/file target-dir "bin")))
14+
(def main-js (str (fs/file target-dir "obb.js")))
1315
(def executable-path (str (fs/file bin-dir executable-name)))
14-
(def version (:version (edn/read-string (slurp "project.edn")))))
16+
(def version (:version (edn/read-string (slurp "project.edn"))))
17+
(def tar-file (fs/file target-dir "obb.tar.gz")))
1518

1619
clean (fs/delete-tree target-dir)
1720

18-
compile (clojure "-M -m cljs.main -co build.edn -c")
19-
2021
shadow-server (do
2122
(shell "npm install")
2223
(shell "npx shadow-cljs server"))
@@ -25,23 +26,33 @@
2526

2627
dev (load-file "bb/watch.clj")
2728

28-
build (when (seq (fs/modified-since executable-path ["deps.edn" "src"]))
29-
(run 'compile)
30-
(let []
31-
(fs/create-dirs bin-dir)
32-
(spit executable-path
33-
(str "#!/usr/bin/env osascript -l JavaScript\n\n"
34-
(slurp "out/main.js")))
35-
(shell (str "chmod u+x " executable-path))))
29+
shadow-release (shell "npx shadow-cljs release obb"
30+
"--config-merge"
31+
"{:compiler-options {:optimizations :advanced}}")
32+
33+
build (when (seq (fs/modified-since executable-path ["deps.edn"
34+
"shadow-cljs.edn"
35+
"src"]))
36+
(println "Compiling obb")
37+
(run 'shadow-release)
38+
(fs/create-dirs bin-dir)
39+
(println "Preparing executable")
40+
(spit executable-path
41+
(str "#!/usr/bin/env osascript -l JavaScript\n\n"
42+
(slurp "out/obb.js")))
43+
(shell (str "chmod u+x " executable-path)))
44+
45+
tar {:depends [build]
46+
:requires ([tar])
47+
:task (tar/tar {:executable-path executable-path
48+
:executable-name executable-name
49+
:tar-file tar-file})}
3650

3751
upload-assets {:doc "Uploads jar and vsix to Github"
38-
:depends [build]
52+
:depends [tar]
3953
:requires ([upload-release :as ur])
40-
:task (do
41-
(fs/copy executable-path ".")
42-
(shell "tar -czvf" "obb.tar.gz" executable-name)
43-
(ur/release {:file "obb.tar.gz"
44-
:version version}))}
54+
:task (ur/release {:file tar-file
55+
:version version})}
4556

4657
test {:doc "Run integration tests"
4758
:depends [build]

bb/tar.clj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
(ns tar
2+
(:require [babashka.fs :as fs]
3+
[babashka.tasks :refer [shell]]))
4+
5+
(defn tar [{:keys [executable-name
6+
executable-path
7+
tar-file]}]
8+
(fs/with-temp-dir [tmp-dir {}]
9+
(fs/copy executable-path tmp-dir)
10+
(fs/copy "script/brew_install.sh" tmp-dir)
11+
(let [libexec (fs/file tmp-dir "libexec")]
12+
(fs/create-dirs libexec)
13+
(run! (fn [js-file]
14+
(when-not (contains? #{"obb.js" "inferred_externs.js"}
15+
(fs/file-name js-file))
16+
(fs/copy js-file (fs/file libexec (fs/file-name js-file)))))
17+
(fs/glob "out" "*.js"))
18+
(shell {:dir (str tmp-dir)}
19+
"tar -czvf" "obb.tar.gz" executable-name "brew_install.sh" "libexec")
20+
(fs/copy (fs/file tmp-dir "obb.tar.gz") tar-file {:replace-existing true}))))

build.edn

Lines changed: 0 additions & 5 deletions
This file was deleted.

script/brew_install.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
3+
prefix="$1"
4+
5+
# obb executable
6+
mkdir -p "$prefix/bin"
7+
echo 'globalThis.obb_brew = true;' >> obb
8+
echo "globalThis.obb_lib_dir = \"$prefix/libexec\";" >> obb
9+
cp obb "$prefix/bin"
10+
11+
# internal libs
12+
mkdir -p "$prefix/libexec"
13+
cp libexec/* "$prefix/libexec"
14+
15+
mkdir -p "$prefix/bin"
16+
cp obb "$prefix/bin"

shadow-cljs.edn

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{:deps {:aliases [:dev]}
22
:builds {:obb {:target :browser
3-
:modules {:obb {:entries [obb.main]}}
3+
:modules {:obb {:entries [obb.main]}
4+
:obb_repl {:entries [obb.impl.repl]
5+
:depends-on #{:obb}}}
46
:output-dir "out"
57
:release {:compiler-options {:optimizations :simple}}}}
8+
;; used for detecting when server is finished loading
69
:nrepl {:port 13337}}

src/obb/impl/core.cljs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
(ns obb.impl.core
2+
(:refer-clojure :exclude [prn slurp])
3+
(:require [clojure.core :as clojure]
4+
[obb.impl.sci :as impl.sci]
5+
[sci.core :as sci]
6+
[sci.impl.interop :as interop]))
7+
8+
(def app
9+
(delay (let [app (js/Application.currentApplication)]
10+
(set! (.-includeStandardAdditions app) true)
11+
app)))
12+
13+
(set! (.-obb_lib_dir js/globalThis) "out")
14+
15+
(defn prefix []
16+
(.-obb_lib_dir js/globalThis))
17+
18+
(defn slurp
19+
"Like `clojure.core/slurp` but only accepts paths."
20+
[x]
21+
(.read @app (js/Path x #js {})))
22+
23+
(defn load-fn [{:keys [namespace]}]
24+
(case namespace
25+
obb.repl (let [code (slurp (str (prefix) "/obb_repl.js"))]
26+
;; @zane: there might be a better way to evaluate in osascript JS
27+
(js/eval code)
28+
{:source ""})))
29+
30+
(defn display-string
31+
"Returns the JXA display string for an object specifier."
32+
[os]
33+
(js/Automation.getDisplayString os))
34+
35+
(defn object-specifier?
36+
"Returns true if x is an object specifier."
37+
[x]
38+
(js/ObjectSpecifier.hasInstance x))
39+
40+
(defn not-object-specifier-pred-1 [f]
41+
(fn [x]
42+
(when-not (object-specifier? x)
43+
(f x))))
44+
45+
(defn not-object-specifier-pred-2 [f]
46+
(fn [x y]
47+
(when-not (object-specifier? x)
48+
(f x y))))
49+
50+
(set! interop/invoke-instance-method impl.sci/invoke-instance-method)
51+
52+
(set! interop/invoke-static-method impl.sci/invoke-static-method)
53+
54+
(set! clojure/map? (not-object-specifier-pred-1 map?))
55+
56+
(set! clojure/meta (not-object-specifier-pred-1 meta))
57+
58+
(enable-console-print!)
59+
60+
(sci/alter-var-root sci/print-fn (constantly *print-fn*))
61+
62+
(def print*
63+
;; All output from osascript by default goes to stderr. To get around this
64+
;; we use the Objective-C bridge to write directly to stdout.
65+
66+
;; `delay` ensures that this import happens only when needed, and only once.
67+
(let [import (delay (.import js/ObjC "Foundation"))]
68+
(fn [s]
69+
@import
70+
(-> (.dataUsingEncoding (js/ObjC.wrap s) js/$.NSUTF8StringEncoding)
71+
(js/$.NSFileHandle.fileHandleWithStandardOutput.writeData)))))
72+
73+
(defn prn
74+
"Like `clojure.core/prn`, but will not crash if called on an object specifier.
75+
Object specifiers are printed as their display string with the prefix
76+
#org.babashka.obb/object-specifier."
77+
[x]
78+
(let [object-specifier-tag "#org.babashka.obb/object-specifier"]
79+
(print*
80+
(if (object-specifier? x)
81+
(str object-specifier-tag " " (pr-str (display-string x)))
82+
(pr-str x))))
83+
(print* \newline))
84+
85+
(def ctx (atom (sci/init {:load-fn load-fn
86+
:classes {'js goog/global
87+
:allow :all}})))
88+
89+
(defn eval-string
90+
"Evaluates a string using `ctx` as the context."
91+
[s]
92+
(sci/eval-string* @ctx s))
93+
94+
(defn register-plugin! [_plug-in-name sci-opts]
95+
(swap! ctx sci/merge-opts sci-opts))

src/obb/impl/main.cljs

Lines changed: 10 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,26 @@
11
(ns obb.impl.main
2-
(:refer-clojure :exclude [prn slurp])
3-
(:require [clojure.core :as clojure]
4-
[clojure.tools.cli :as cli]
5-
[obb.impl.sci :as impl.sci]
6-
[sci.core :as sci]
7-
[sci.impl.interop :as interop]))
2+
(:require [clojure.tools.cli :as cli]
3+
[obb.impl.core :as impl.core :refer [slurp ctx prefix]]))
84

95
(def cli-options
106
[["-e" "--eval <expr>"]])
117

12-
(def app
13-
(delay (let [app (js/Application.currentApplication)]
14-
(set! (.-includeStandardAdditions app) true)
15-
app)))
16-
17-
(defn slurp
18-
"Like `clojure.core/slurp` but only accepts paths."
19-
[x]
20-
(.read @app (js/Path x #js {})))
21-
22-
(defn display-string
23-
"Returns the JXA display string for an object specifier."
24-
[os]
25-
(js/Automation.getDisplayString os))
26-
27-
(defn object-specifier?
28-
"Returns true if x is an object specifier."
29-
[x]
30-
(js/ObjectSpecifier.hasInstance x))
31-
32-
(defn not-object-specifier-pred-1 [f]
33-
(fn [x]
34-
(when-not (object-specifier? x)
35-
(f x))))
36-
37-
(defn not-object-specifier-pred-2 [f]
38-
(fn [x y]
39-
(when-not (object-specifier? x)
40-
(f x y))))
41-
42-
(set! interop/invoke-instance-method impl.sci/invoke-instance-method)
43-
44-
(set! interop/invoke-static-method impl.sci/invoke-static-method)
45-
46-
(set! clojure/map? (not-object-specifier-pred-1 map?))
47-
48-
(set! clojure/meta (not-object-specifier-pred-1 meta))
49-
50-
(enable-console-print!)
51-
52-
(sci/alter-var-root sci/print-fn (constantly *print-fn*))
53-
54-
(def ctx (sci/init {:classes {'js goog/global
55-
:allow :all}}))
56-
57-
(defn eval-string
58-
"Evaluates a string using `ctx` as the context."
59-
[s]
60-
(sci/eval-string* ctx s))
61-
62-
(def print*
63-
;; All output from osascript by default goes to stderr. To get around this
64-
;; we use the Objective-C bridge to write directly to stdout.
65-
66-
;; `delay` ensures that this import happens only when needed, and only once.
67-
(let [import (delay (.import js/ObjC "Foundation"))]
68-
(fn [s]
69-
@import
70-
(-> (.dataUsingEncoding (js/ObjC.wrap s) js/$.NSUTF8StringEncoding)
71-
(js/$.NSFileHandle.fileHandleWithStandardOutput.writeData)))))
72-
73-
(defn prn
74-
"Like `clojure.core/prn`, but will not crash if called on an object specifier.
75-
Object specifiers are printed as their display string with the prefix
76-
#org.babashka.obb/object-specifier."
77-
[x]
78-
(let [object-specifier-tag "#org.babashka.obb/object-specifier"]
79-
(print*
80-
(if (object-specifier? x)
81-
(str object-specifier-tag " " (pr-str (display-string x)))
82-
(pr-str x))))
83-
(print* \newline))
84-
858
(defn main [argv]
869
(let [args (js->clj argv)
8710
{:keys [arguments summary] {form :eval} :options} (cli/parse-opts args cli-options)]
8811
(cond (some? form)
89-
(prn (eval-string form))
12+
(impl.core/prn (impl.core/eval-string form))
9013

9114
(and (seq arguments)
9215
(= 1 (count arguments)))
9316
(let [form (slurp (first arguments))]
94-
(eval-string form))
17+
(impl.core/eval-string form))
9518

9619
:else
97-
(println summary))
20+
(let [src (slurp (str (prefix) "/obb_repl.js"))]
21+
(js/eval src)
22+
(let [env @(:env @ctx)
23+
foo (get-in env [:namespaces 'obb.repl 'foo])]
24+
(foo) ;; TODO: launch REPL instead of printing summary
25+
(println summary))))
9826
js/undefined)) ; suppress printing of return value

src/obb/impl/repl.cljs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(ns obb.impl.repl
2+
(:require [obb.impl.core :as impl.core]
3+
[sci.core :as sci]))
4+
5+
(defn foo []
6+
;; TODO: launch REPL
7+
)
8+
9+
(impl.core/register-plugin!
10+
::repl
11+
{:namespaces
12+
{'obb.repl {'foo (sci/copy-var foo
13+
(sci/create-ns 'obb.repl nil))}}})

0 commit comments

Comments
 (0)