blob: 7e4c5784e40f260f45122834d013edcb7c6011f6 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
#!/usr/bin/env bb
;; vi: ft=clojure
(ns script.fennel
(:require [clojure.string :as str]
[clojure.set :as set]
[babashka.cli :as cli]
[babashka.fs :as fs]
[babashka.process :as proc]))
(def cli-opts
{:help {:alias :h
:desc "Prints this help"}
:compile {:alias :c
:desc "Compile all changed Fennel files into their Lua counterparts (\"changed\" means newer modified time than their Lua counterpart)"}
:dry {:alias :d
:desc "When true, it won't actually modify any files"}
:notify {:alias :n
:desc "Use notify-send to display errors alongside printing to stderr"}
:files {:alias :f
:desc "Prints the Fennel files it would operate on and exits, useful for piping into entr for \"compile on change\" support"}
:root {:alias :r
:desc "Root directory to perform all operations under"
:default "."}
:prune {:alias :p
:desc "Delete Lua files that lack a Fennel counterpart, prevents orphan Lua files from hanging around after a Fennel file deletion or rename, takes a list of globs that should not be included in the pruning (like `vendor/**.lua,my-cool.lua`)"}})
(defn compile-fennel-file
"Compiles the given Fennel file into a Lua file. Can return an anomaly."
[{:keys [src-path]}]
(try
(if src-path
(let [{:keys [exit out err]}
(proc/shell
{:out :string
:err :string
:continue true}
"lua" "script/fennel.lua" "--compile" src-path)]
(if (zero? exit)
out
{:cognitect.anomalies/category :cognitect.anomalies/fault
:cognitect.anomalies/message "Fennel process exited with a non-zero status code"
::exit exit
::err err}))
{:cognitect.anomalies/category :cognitect.anomalies/incorrect
:cognitect.anomalies/message "src-path must be a string"})
(catch Exception e
{:cognitect.anomalies/category :cognitect.anomalies/fault
:cognitect.anomalies/message "Uncaught exception"
::exeption-map (Throwable->map e)})))
(comment
(compile-fennel-file {:src-path "init.fnl"}))
(defn display-help! []
(let [file-name (fs/relativize (fs/cwd) *file*)]
(println (str "Usage: " file-name " [FLAG]"))
(println)
(println "Compile changed Fennel files into Lua, intended for use with Neovim configuration.")
(println)
(println "To run whenever you change a file you can combine this tool with entr https://eradman.com/entrproject/")
(println)
(println (str "Example: " file-name " --files | entr " file-name " --compile"))
(println)
(println (cli/format-opts {:spec cli-opts}))))
(comment
(display-help!))
(defn notify! [summary msg]
(proc/shell "notify-send" summary msg))
(defn last-modified-time-ms-safe
"Will ALWAYS return a number. If there's an error it'll return a negative number."
[path]
(try
(.toMillis (fs/last-modified-time path))
(catch Exception _e
-1)))
(defn fnl->lua-path [fnl-path]
(str/replace (str (fs/strip-ext fnl-path) ".lua") #"^fnl/" "lua/"))
(defn compile-changed-fennel-files!
"Find changed Fennel files that are newer than their Lua counterpart and compile them."
[{:keys [root prune dry notify] :or {root "."}}]
(let [glob #(map str (fs/glob root %))
fnl-paths (glob "**.fnl")
lua-paths (glob "**.lua")]
(when prune
(run!
(fn [lua-path]
(println "[delete]" lua-path)
(when (not dry)
(fs/delete lua-path)))
(set/difference
(set lua-paths)
(set (map fnl->lua-path fnl-paths))
(set (mapcat glob prune)))))
(run!
(fn [fnl-path]
(let [lua-path (fnl->lua-path fnl-path)]
(when (> (last-modified-time-ms-safe fnl-path)
(last-modified-time-ms-safe lua-path))
(println "[compile]" fnl-path)
(let [lua (compile-fennel-file {:src-path fnl-path})]
(if-let [category (:cognitect.anomalies/category lua)]
(binding [*out* *err*]
(if (= :cognitect.anomalies/fault category)
(do
(println (::err lua))
(when notify
(notify! (str fnl-path " compilation failed") (::err lua))))
(println "[error]" lua)))
(when (not dry)
(spit lua-path (str "-- [nfnl] " fnl-path "\n" lua))))))))
fnl-paths)))
(defn print-fennel-file-paths! [{:keys [root] :or {root "."}}]
(run!
println
(map str (fs/glob root "**.fnl"))))
(defn main
"Parse the input args and work out what to do."
[args]
(let [opts (update (cli/parse-opts args cli-opts) :prune
(fn [prune]
(cond
(string? prune) (str/split prune #",")
(true? prune) []
(false? prune) nil
:else prune)))]
(cond
(:help opts) (display-help!)
(:files opts) (print-fennel-file-paths! opts)
(:compile opts) (compile-changed-fennel-files! opts)
:else (display-help!))))
(comment
;; Intended to be invoked through your editor REPL during development.
(main ["--help"]))
;; Default entrypoint of the script.
(main *command-line-args*)
|