-
Notifications
You must be signed in to change notification settings - Fork 6
/
template.clj
254 lines (229 loc) · 8.64 KB
/
template.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
;;;;
;;;; Functions to generate artifacts from an overarch model via templates
;;;;
(ns org.soulspace.overarch.application.template
(:require [clojure.java.io :as io]
[clojure.edn :as edn]
[org.soulspace.clj.namespace :as ns]
[org.soulspace.clj.java.file :as file]
[org.soulspace.overarch.application.model-repository :as repo]
[org.soulspace.overarch.domain.element :as el]
[org.soulspace.overarch.domain.model :as model]
; register multimethods
[org.soulspace.overarch.adapter.repository.file-model-repository :as fmr]
[clojure.spec.alpha :as s]))
;;;
;;; Generation config spec
;;;
(s/def :overarch.template/selection :overarch/selection-criteria)
(s/def :overarch.template/template string?)
(s/def :overarch.template/engine keyword?)
(s/def :overarch.template/encoding string?)
(s/def :overarch.template/per-element boolean?)
(s/def :overarch.template/path string?)
(s/def :overarch.template/subdir string?)
(s/def :overarch.template/namespace-prefix string?)
(s/def :overarch.template/base-namespace string?)
(s/def :overarch.template/namespace-suffix string?)
(s/def :overarch.template/prefix string?)
(s/def :overarch.template/base-name string?)
(s/def :overarch.template/file-name string?)
(s/def :overarch.template/suffix string?)
(s/def :overarch.template/extension string?)
(s/def :overarch.template/name-as-namespace boolean?)
(s/def :overarch.template/protected-area boolean?)
(s/def :overarch.template/generation-context
(s/keys :req-un [:overarch.template/template]
:opt-un [:overarch.template/selection
:overarch.template/engine
:overarch.template/encoding
:overarch.template/per-element
:overarch.template/path
:overarch.template/subdir
:overarch.template/namespace-prefix
:overarch.template/base-namespace
:overarch.template/namespace-suffix
:overarch.template/file-name
:overarch.template/prefix
:overarch.template/base-name
:overarch.template/suffix
:overarch.template/extension
:overarch.template/name-as-namespace
:overarch.template/protected-area]))
(s/def :overarch.template/generation-config
(s/coll-of :overarch.template/generation-context))
;;;
;;; Template engine functions
;;;
(defn repo-type
"Returns the repository type."
([rtype]
rtype)
([rtype & r]
rtype))
(defn engine-type
"Returns the template engine type."
([ttype]
ttype)
([ttype & r]
ttype))
; TODO really needed?
(defmulti read-template
"Reads and parses the `template`."
repo-type)
(defmulti apply-template
"Applies the `template` to the `data` and returns the output."
engine-type)
;;;
;;; Protected area handling
;;;
(defn begin-pattern
"Returns the regex pattern for the begin of a protected area based on the area marker."
[area-marker]
(re-pattern (str "^.*" area-marker "-BEGIN\\((.*)\\).*$")))
(defn end-pattern
"Returns the regex pattern for the end of a protected area based on the area marker and area id."
[area-marker area-id]
(re-pattern (str "^.*" area-marker "-END\\(" area-id "\\).*$")))
(defn read-lines
"Reads the file given and returns a non lazy sequence a of its lines."
([file]
(with-open [rdr (io/reader file)]
(doall (line-seq rdr)))))
(defn parse-protected-areas
"Parse the lines into a protected area map."
[area-marker lines]
; (println "Read PAs from" lines)
(let [begin-re (begin-pattern area-marker)]
(loop [remaining-lines lines area-id nil area-content "" area-map {}]
(if (seq remaining-lines)
(if (nil? area-id)
(if-let [begin-matches (re-seq begin-re (first remaining-lines))]
(recur (rest remaining-lines) (nth (first begin-matches) 1) "" area-map) ; line starting a protected area
(recur (rest remaining-lines) nil "" area-map)) ; line outside any protected areas
(if-let [end-match (re-matches (end-pattern area-marker area-id) (first remaining-lines))]
(recur (rest remaining-lines) nil "" (assoc area-map (keyword area-id) area-content)) ; line ending a protected area
(recur (rest remaining-lines) area-id (str area-content (first remaining-lines) "\n") area-map))) ; line inside a protected area
area-map)))) ; no more lines, return area map
(defn read-protected-areas
"Reads the given path and returns the proected areas as a map."
[ctx path]
(if (and (:protected-area ctx)
(file/exists? (io/as-file path)))
(let [area-marker (:protected-area ctx)]
(parse-protected-areas area-marker (read-lines path)))
{}))
;;;
;;; Artifact handling
;;;
(defn artifact-filename
"Returns the filename of the artifact given the generation context `ctx`
and optionally a model element `el`."
([ctx]
(str
(:prefix ctx)
(:base-name ctx)
(:suffix ctx)
"." (:extension ctx)))
([ctx el]
(str
(:prefix ctx)
(if-let [base-name (:base-name ctx)]
base-name
(:name el))
(:suffix ctx)
"." (:extension ctx))))
(defn artifact-path
"Returns the path for the artifact given the generation context `ctx`
and optionally a model element `el`."
([ctx]
(str
(when (:generation-dir ctx)
(str (:generation-dir ctx) "/"))
(when (:subdir ctx)
(str (:subdir ctx) "/"))
(when (:namespace-prefix ctx)
(str (ns/ns-to-path (:namespace-prefix ctx)) "/"))
(when (:base-namespace ctx)
(str (ns/ns-to-path (:base-namespace ctx)) "/"))
(when (:namespace-suffix ctx)
(str (ns/ns-to-path (:namespace-suffix ctx)) "/"))))
([ctx el]
(str
(when (:generation-dir ctx)
(str (:generation-dir ctx) "/"))
(when (:subdir ctx)
(str (:subdir ctx) "/"))
(when (:namespace-prefix ctx)
(str (ns/ns-to-path (:namespace-prefix ctx)) "/"))
(if (:id-as-namespace ctx)
(str (ns/ns-to-path (name (:id el))) "/")
(if (:base-namespace ctx)
(str (ns/ns-to-path (:base-namespace ctx)) "/")
(str (ns/ns-to-path (el/element-namespace el)) "/")))
(when (:namespace-suffix ctx)
(str (ns/ns-to-path (:namespace-suffix ctx)) "/")))))
(defn create-path
"Creates the path by creating all neccessary directories."
[pathname]
(let [file (io/as-file pathname)]
(when-not (file/exists? file)
(.mkdirs file))))
(defn write-artifact
"Write the generated artifact to file."
[pathname result]
(println "Writing artifact" pathname)
(let [file (io/as-file pathname)
parent (file/parent-dir file)]
; TODO check suppress-write
(when-not (file/exists? parent)
(file/create-dir parent))
; TODO add encoding from generator
; (with-open ((writer pathname)))
(spit pathname result)))
;;;
;;; Generation process
;;;
(def ctx-defaults
{:engine :comb
:per-element false
:encoding "UTF-8"
:id-as-namespace false})
(defn read-config
"Reads the generator configuration."
[options]
(if-let [generation-config (:generation-config options)]
(map (partial merge ctx-defaults {:generation-dir (:generation-dir options)
:backup-dir (:backup-dir options)})
(edn/read-string (slurp generation-config)))
[]))
; TODO generation-dir missing from path, comes from options
(defn generate-artifact
"Generates an artifact"
[template ctx model e]
; (println "Element" e)
(let [path (str (artifact-path ctx e) (artifact-filename ctx e))
protected-areas (read-protected-areas ctx path)
; _ (println "Protected Areas" protected-areas)
result (apply-template (:engine ctx) template {:ctx ctx :e e :model model :protected-areas protected-areas})
; _ (print "Result" result)
]
; write artifact for result
(write-artifact path result)))
(defn generate
"Generates artifacts for the generation specification `spec`."
[model options]
(doseq [ctx (read-config options)]
; (println "Context" ctx)
(let [template (io/as-file (str (:template-dir options) "/" (:template ctx)))]
(when-let [selection (into #{} (model/filter-xf model (:selection ctx)) (repo/model-elements))]
; (println "Selection" selection)
(if (:per-element ctx)
(doseq [e selection]
(generate-artifact template ctx model e))
(generate-artifact template ctx model selection))))))
(comment
(repo/read-models :file "models")
(apply-template :comb (io/as-file "templates/clojure/gitignore.cmb") {})
(into #{} (model/filter-xf (repo/model) {:el :container}) (repo/model-elements))
)