-
Notifications
You must be signed in to change notification settings - Fork 16
/
core.clj
200 lines (183 loc) · 7.39 KB
/
core.clj
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
(ns sass4clj.core
(:require
[clojure.java.io :as io]
[clojure.string :as string]
[sass4clj.util :as util]
[sass4clj.webjars :as webjars]
[cheshire.core :as json])
(:import
[java.net URI]
[java.util Collection Collections]
[io.bit3.jsass CompilationException Options Output OutputStyle Sass2ScssOptions]
[io.bit3.jsass.importer Import Importer]))
(defn find-local-file [names current-dir]
(some
(fn [name]
(let [f (io/file current-dir name)]
(if (.exists f)
[(.getPath f) f])))
names))
(defn normalize-url
"Simple URL normalization logic for import paths. Can normalize
relative paths."
[url-string]
(loop [result nil
parts (string/split url-string #"/")]
(if (seq parts)
(let [part (first parts)]
(case part
;; Skip empty
"" (recur result (rest parts))
;; Skip "."
"." (recur result (rest parts))
;; Remove previous part, if there are previous non ".." parts
".." (if (and (seq result) (not= ".." (first result)))
(recur (rest result) (rest parts))
(recur (conj result part) (rest parts)))
(recur (conj result part) (rest parts))))
(string/join "/" (reverse result)))))
(defn join-url [& parts]
(normalize-url (string/join "/" parts)))
(defn find-resource [names]
(some (fn [name]
(if-let [url (io/resource name)]
(case (.getProtocol url)
"file"
[(.toString url) url]
"jar"
(let [jar-url (.openConnection url)
entry (.getEntryName jar-url)]
; (util/dbug "Found %s from resources\n" url)
[entry url]))))
names))
(defn find-webjars [ctx names]
(some (fn [name]
(when-let [path (get (:asset-map ctx) name)]
(find-resource [path])))
names))
(defn with-underscore [url]
(let [parts (string/split url #"/")]
(cond-> [url]
(not (.startsWith (last parts) "_")) (conj (string/join "/" (conj (vec (butlast parts)) (str "_" (last parts))))))))
(defn remove-tilde-start [names]
(mapcat (fn [name]
(if (.startsWith name "~")
[name (subs name 1)]
[name]))
names))
(defn possible-names [name]
(let [scss? (.endsWith name ".scss")
sass? (.endsWith name ".sass")
css? (.endsWith name ".css")
has-ext? (or scss? sass? css?)]
(remove-tilde-start
(cond-> []
(or (not has-ext?) scss?) (into (with-underscore (if scss? name (str name ".scss"))))
(or (not has-ext?) sass?) (into (with-underscore (if sass? name (str name ".sass"))))
(or (not has-ext?) css?) (conj (if css? name (str name ".css")))))))
(defn sass2scss [source]
(io.bit3.jsass.Compiler/sass2scss source (bit-xor Sass2ScssOptions/PRETTIFY2
Sass2ScssOptions/KEEP_COMMENT)))
(defn custom-sass-importer [ctx]
(reify
Importer
(^Collection apply [this ^String import-url ^Import prev]
(let [;; Generates different possibilies of names with _ and extensions added
names (possible-names import-url)
[_ parent] (re-find #"(.*)/([^/]*)$" (str (.getAbsoluteUri prev)))]
; (util/info "Names: %s\n" names)
(when-let [[found-absolute-uri uri]
(or (find-local-file names parent)
(some (fn [source-path]
(find-local-file names source-path))
(:source-paths ctx))
(find-resource names)
(find-resource (map #(join-url parent %) names))
(find-webjars ctx names))]
(util/dbug "Import: %s (from %s), result: %s\n"
import-url
(.getAbsoluteUri prev)
uri)
; jsass doesn't know how to read content from other than files?
;; FIXME: If extension is sass, should convert the content to scss
(Collections/singletonList
(Import. import-url
found-absolute-uri
(cond-> (slurp uri)
(.endsWith found-absolute-uri ".sass") (sass2scss)))))))))
(def ^:private output-styles
{:nested OutputStyle/NESTED
:compact OutputStyle/COMPACT
:expanded OutputStyle/EXPANDED
:compressed OutputStyle/COMPRESSED})
(defn- build-options
[{:keys [source-paths output-style source-map precision set-indented-syntax-src]}]
(let [opts (Options.)
include-paths (.getIncludePaths opts)]
;; Hardcode to use Unix newlines, mostly because that's what the tests use
(.setLinefeed opts "\n")
(doseq [source-path source-paths]
(.add include-paths (io/file source-path)))
(when output-style
(.setOutputStyle opts (get output-styles output-style)))
(when source-map
;; we manually append source-map uri in sass-compile-to-file
(.setOmitSourceMapUrl opts true)
;; would be hard to deal with adding all the source-files to output...
;; or at least harder than just one file.
(.setSourceMapContents opts true)
(.setSourceMapFile opts (URI. "placeholder.css.map")))
(when precision
(.setPrecision opts precision))
(.setIsIndentedSyntaxSrc opts (true? set-indented-syntax-src))
opts))
(defn sass-compile
"Input can be:
- String
- File
Options:
- :source-map-path - Enables source-maps and uses this URL for
sourceMappingURL. Relative to css file."
[input {:keys [verbosity source-map source-paths]
:or {verbosity 1}
:as options}]
(binding [util/*verbosity* verbosity]
(try
(let [ctx {:asset-map (webjars/asset-map)
:source-paths source-paths}
compiler (io.bit3.jsass.Compiler.)
opts (build-options options)
_ (doto (.getImporters opts)
(.add (custom-sass-importer ctx)))
output (if (string? input)
(.compileString compiler input opts)
(.compileFile compiler (.toURI input) nil opts))]
{:output (.getCss output)
:source-map (if source-map (.getSourceMap output))})
(catch CompilationException e
(throw (ex-info (.getMessage e) (assoc (json/parse-string (.getErrorJson e) true)
:type ::error)))))))
(defn sass-compile-to-file
"Arguments:
- input-path - Path to the input file
- output-path - Path to the output file, possible to source map will be
written to same path with `.map` appended
- options
Options:
- :source-map - Enables source-maps and sets URI using output-path."
[input-path output-path {:keys [source-map] :as options}]
(let [input-file (io/file input-path)
output-file (io/file output-path)
source-map-name (if source-map (str output-path ".map"))
source-map-output (io/file (str output-path ".map"))
{:keys [output source-map] :as result} (sass-compile input-file options)
source-map-url (str "/*# sourceMappingURL=" (.getName source-map-output) " */") ]
(when output
(io/make-parents output-file)
(spit output-file output)
(when source-map
(spit output-file source-map-url :append true)
(spit source-map-output source-map)))
(if source-map
(assoc result :output (str output source-map-url))
result)))