Merge branch 'feature/5' into develop
This commit is contained in:
commit
6ba1ad60c9
|
@ -5,7 +5,7 @@
|
||||||
:license {:name "GNU Lesser General Public License, version 3.0 or (at your option) any later version"
|
:license {:name "GNU Lesser General Public License, version 3.0 or (at your option) any later version"
|
||||||
:url "https://www.gnu.org/licenses/lgpl-3.0.en.html"}
|
:url "https://www.gnu.org/licenses/lgpl-3.0.en.html"}
|
||||||
|
|
||||||
:dependencies [[adl-support "0.1.3"]
|
:dependencies [[adl-support "0.1.4-SNAPSHOT"]
|
||||||
[bouncer "1.0.1"]
|
[bouncer "1.0.1"]
|
||||||
[clojure-saxon "0.9.4"]
|
[clojure-saxon "0.9.4"]
|
||||||
[environ "1.1.0"]
|
[environ "1.1.0"]
|
||||||
|
@ -19,7 +19,10 @@
|
||||||
:main adl.main
|
:main adl.main
|
||||||
|
|
||||||
:plugins [[lein-codox "0.10.3"]
|
:plugins [[lein-codox "0.10.3"]
|
||||||
[lein-release "1.0.5"]]
|
[lein-kibit "0.1.6"]
|
||||||
|
[lein-release "1.0.5"]
|
||||||
|
;; [uncomplexor "0.1.0-SNAPSHOT"]
|
||||||
|
]
|
||||||
|
|
||||||
;; :lein-release {:scm :git
|
;; :lein-release {:scm :git
|
||||||
;; :deploy-via :clojars} ;; :deploy-via :clojars fails - with an scp error.
|
;; :deploy-via :clojars} ;; :deploy-via :clojars fails - with an scp error.
|
||||||
|
|
|
@ -92,7 +92,6 @@
|
||||||
(defn process
|
(defn process
|
||||||
"Process these parsed `options`."
|
"Process these parsed `options`."
|
||||||
[options]
|
[options]
|
||||||
(do
|
|
||||||
(let [p (:path (:options options))
|
(let [p (:path (:options options))
|
||||||
op (if (.endsWith p "/") p (str p "/"))]
|
op (if (.endsWith p "/") p (str p "/"))]
|
||||||
(binding [*output-path* op
|
(binding [*output-path* op
|
||||||
|
@ -110,7 +109,7 @@
|
||||||
(s/to-selmer-routes application)
|
(s/to-selmer-routes application)
|
||||||
(t/to-selmer-templates application))
|
(t/to-selmer-templates application))
|
||||||
(*warn* (str "ERROR: File not found: " %)))
|
(*warn* (str "ERROR: File not found: " %)))
|
||||||
(-> options :arguments)))))))
|
(:arguments options))))))
|
||||||
|
|
||||||
|
|
||||||
(defn -main
|
(defn -main
|
||||||
|
@ -121,7 +120,7 @@
|
||||||
(cond
|
(cond
|
||||||
(empty? args)
|
(empty? args)
|
||||||
(usage options)
|
(usage options)
|
||||||
(not (empty? (:errors options)))
|
(seq (:errors options))
|
||||||
(do
|
(do
|
||||||
(doall
|
(doall
|
||||||
(map
|
(map
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
(ns ^{:doc "Application Description Language - generate HUGSQL queries file."
|
(ns ^{:doc "Application Description Language - generate HUGSQL queries file."
|
||||||
:author "Simon Brooke"}
|
:author "Simon Brooke"}
|
||||||
adl.to-hugsql-queries
|
adl.to-hugsql-queries
|
||||||
(:require [adl-support.core :refer [*warn*]]
|
(:require [adl-support.core :refer :all]
|
||||||
[adl-support.utils :refer :all]
|
[adl-support.utils :refer :all]
|
||||||
[clojure.java.io :refer [file make-parents]]
|
[clojure.java.io :refer [file make-parents]]
|
||||||
[clojure.math.combinatorics :refer [combinations]]
|
[clojure.math.combinatorics :refer [combinations]]
|
||||||
|
@ -46,8 +46,7 @@
|
||||||
(let
|
(let
|
||||||
[entity-name (:name (:attrs entity))
|
[entity-name (:name (:attrs entity))
|
||||||
property-names (map #(:name (:attrs %)) properties)]
|
property-names (map #(:name (:attrs %)) properties)]
|
||||||
(if
|
(if-not (empty? property-names)
|
||||||
(not (empty? property-names))
|
|
||||||
(str
|
(str
|
||||||
"WHERE "
|
"WHERE "
|
||||||
(s/join
|
(s/join
|
||||||
|
@ -213,8 +212,8 @@
|
||||||
(defn select-query
|
(defn select-query
|
||||||
"Generate an appropriate `select` query for this `entity`"
|
"Generate an appropriate `select` query for this `entity`"
|
||||||
([entity properties]
|
([entity properties]
|
||||||
(if
|
(if-not
|
||||||
(not (empty? properties))
|
(empty? properties)
|
||||||
(let [entity-name (safe-name (:name (:attrs entity)) :sql)
|
(let [entity-name (safe-name (:name (:attrs entity)) :sql)
|
||||||
pretty-name (singularise entity-name)
|
pretty-name (singularise entity-name)
|
||||||
query-name (if (= properties (key-properties entity))
|
query-name (if (= properties (key-properties entity))
|
||||||
|
@ -384,7 +383,8 @@
|
||||||
[application]
|
[application]
|
||||||
(let [filepath (str *output-path* "resources/sql/queries.auto.sql")]
|
(let [filepath (str *output-path* "resources/sql/queries.auto.sql")]
|
||||||
(make-parents filepath)
|
(make-parents filepath)
|
||||||
(try
|
(do-or-warn
|
||||||
|
(do
|
||||||
(spit
|
(spit
|
||||||
filepath
|
filepath
|
||||||
(s/join
|
(s/join
|
||||||
|
@ -396,20 +396,11 @@
|
||||||
(str "autogenerated by adl.to-hugsql-queries at " (t/now))
|
(str "autogenerated by adl.to-hugsql-queries at " (t/now))
|
||||||
"See [Application Description Language](https://github.com/simon-brooke/adl).")
|
"See [Application Description Language](https://github.com/simon-brooke/adl).")
|
||||||
(map
|
(map
|
||||||
#(:query %)
|
:query
|
||||||
(sort
|
(sort
|
||||||
#(compare (:name %1) (:name %2))
|
#(compare (:name %1) (:name %2))
|
||||||
(vals
|
(vals
|
||||||
(queries application)))))))
|
(queries application)))))))
|
||||||
(if (> *verbosity* 0)
|
(if (pos? *verbosity*)
|
||||||
(*warn* (str "\tGenerated " filepath)))
|
(*warn* (str "\tGenerated " filepath)))))))
|
||||||
(catch
|
|
||||||
Exception any
|
|
||||||
(*warn*
|
|
||||||
(str
|
|
||||||
"ERROR: Exception "
|
|
||||||
(.getName (.getClass any))
|
|
||||||
(.getMessage any)
|
|
||||||
" while printing "
|
|
||||||
filepath))))))
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
(ns ^{:doc "Application Description Language: generate RING routes for REST requests."
|
(ns ^{:doc "Application Description Language: generate RING routes for REST requests."
|
||||||
:author "Simon Brooke"}
|
:author "Simon Brooke"}
|
||||||
adl.to-json-routes
|
adl.to-json-routes
|
||||||
(:require [adl-support.core :refer [*warn*]]
|
(:require [adl-support.core :refer :all]
|
||||||
[adl-support.utils :refer :all]
|
[adl-support.utils :refer :all]
|
||||||
[adl.to-hugsql-queries :refer [queries]]
|
[adl.to-hugsql-queries :refer [queries]]
|
||||||
[clj-time.core :as t]
|
[clj-time.core :as t]
|
||||||
|
@ -53,7 +53,8 @@
|
||||||
(f/unparse (f/formatters :basic-date-time) (t/now)))
|
(f/unparse (f/formatters :basic-date-time) (t/now)))
|
||||||
(list
|
(list
|
||||||
:require
|
:require
|
||||||
'[adl-support.core :as support]
|
'[adl-support.core :refer :all]
|
||||||
|
'[adl-support.rest-support :refer :all]
|
||||||
'[clojure.core.memoize :as memo]
|
'[clojure.core.memoize :as memo]
|
||||||
'[clojure.java.io :as io]
|
'[clojure.java.io :as io]
|
||||||
'[clojure.tools.logging :as log]
|
'[clojure.tools.logging :as log]
|
||||||
|
@ -72,28 +73,38 @@
|
||||||
(defn generate-handler-body
|
(defn generate-handler-body
|
||||||
"Generate and return the function body for the handler for this `query`."
|
"Generate and return the function body for the handler for this `query`."
|
||||||
[query]
|
[query]
|
||||||
(let [action (list
|
|
||||||
(symbol (str "db/" (:name query)))
|
|
||||||
'db/*db*
|
|
||||||
(list 'support/massage-params
|
|
||||||
'params
|
|
||||||
'form-params
|
|
||||||
(key-names (:entity query))))]
|
|
||||||
(list
|
(list
|
||||||
[{:keys ['params 'form-params]}]
|
['request]
|
||||||
(case
|
|
||||||
(:type query)
|
|
||||||
(:delete-1 :update-1)
|
|
||||||
(list
|
|
||||||
action
|
|
||||||
`(log/debug (str ~(:name query) " called with params " ~'params "."))
|
|
||||||
'(response/found "/"))
|
|
||||||
(list
|
(list
|
||||||
'let
|
'let
|
||||||
(vector 'result action)
|
['params '(massage-params request)]
|
||||||
`(log/debug (~(symbol (str "db/" (:name query) "-sqlvec")) ~'params))
|
(list
|
||||||
`(log/debug (str ~(str "'" (:name query) "' with params ") ~'params " returned " (count ~'result) " records."))
|
'valid-user-or-forbid
|
||||||
(list 'response/ok 'result))))))
|
(list
|
||||||
|
'with-params-or-error
|
||||||
|
(list
|
||||||
|
'do-or-server-fail
|
||||||
|
(list
|
||||||
|
(symbol (str "db/" (:name query)))
|
||||||
|
'db/*db*
|
||||||
|
'params)
|
||||||
|
(case (:type query)
|
||||||
|
:insert-1 201 ;; created
|
||||||
|
:delete-1 204 ;; no content
|
||||||
|
;; default
|
||||||
|
200)) ;; OK
|
||||||
|
'params
|
||||||
|
(set
|
||||||
|
(map
|
||||||
|
#(keyword (:name (:attrs %)))
|
||||||
|
(case (:type query)
|
||||||
|
(:insert-1 :update-1)
|
||||||
|
(-> query :entity insertable-properties)
|
||||||
|
(:select-1 :delete-1)
|
||||||
|
(-> query :entity key-properties)
|
||||||
|
;; default
|
||||||
|
nil))))
|
||||||
|
'request))))
|
||||||
|
|
||||||
|
|
||||||
(defn generate-handler-src
|
(defn generate-handler-src
|
||||||
|
@ -262,7 +273,7 @@
|
||||||
(let [handlers-map (make-handlers-map application)
|
(let [handlers-map (make-handlers-map application)
|
||||||
filepath (str *output-path* "src/clj/" (:name (:attrs application)) "/routes/auto_json.clj")]
|
filepath (str *output-path* "src/clj/" (:name (:attrs application)) "/routes/auto_json.clj")]
|
||||||
(make-parents filepath)
|
(make-parents filepath)
|
||||||
(try
|
(do-or-warn
|
||||||
(with-open [output (writer filepath)]
|
(with-open [output (writer filepath)]
|
||||||
(binding [*out* output]
|
(binding [*out* output]
|
||||||
(pprint (file-header application))
|
(pprint (file-header application))
|
||||||
|
@ -275,16 +286,7 @@
|
||||||
h)
|
h)
|
||||||
(sort (keys handlers-map))))
|
(sort (keys handlers-map))))
|
||||||
(pprint (defroutes handlers-map))))
|
(pprint (defroutes handlers-map))))
|
||||||
(if (> *verbosity* 0)
|
(if (pos? *verbosity*)
|
||||||
(*warn* (str "\tGenerated " filepath)))
|
(*warn* (str "\tGenerated " filepath))))))
|
||||||
(catch
|
|
||||||
Exception any
|
|
||||||
(*warn*
|
|
||||||
(str
|
|
||||||
"ERROR: Exception "
|
|
||||||
(.getName (.getClass any))
|
|
||||||
(.getMessage any)
|
|
||||||
" while printing "
|
|
||||||
filepath))))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
(ns ^{:doc "Application Description Language: generate Postgres database definition."
|
(ns ^{:doc "Application Description Language: generate Postgres database definition."
|
||||||
:author "Simon Brooke"}
|
:author "Simon Brooke"}
|
||||||
adl.to-psql
|
adl.to-psql
|
||||||
(:require [adl-support.core :refer [*warn*]]
|
(:require [adl-support.core :refer :all]
|
||||||
[adl-support.utils :refer :all]
|
[adl-support.utils :refer :all]
|
||||||
[adl.to-hugsql-queries :refer [queries]]
|
[adl.to-hugsql-queries :refer [queries]]
|
||||||
[clojure.java.io :refer [file make-parents writer]]
|
[clojure.java.io :refer [file make-parents writer]]
|
||||||
|
@ -111,7 +111,12 @@
|
||||||
(defn emit-field-type
|
(defn emit-field-type
|
||||||
[property entity application key?]
|
[property entity application key?]
|
||||||
(case (:type (:attrs property))
|
(case (:type (:attrs property))
|
||||||
"integer" (if key? "SERIAL" "INTEGER")
|
"integer" (if
|
||||||
|
(and
|
||||||
|
key?
|
||||||
|
(system-generated? property))
|
||||||
|
"SERIAL"
|
||||||
|
"INTEGER")
|
||||||
"real" "DOUBLE PRECISION"
|
"real" "DOUBLE PRECISION"
|
||||||
("string" "image" "uploadable")
|
("string" "image" "uploadable")
|
||||||
(str "VARCHAR(" (:size (:attrs property)) ")")
|
(str "VARCHAR(" (:size (:attrs property)) ")")
|
||||||
|
@ -150,8 +155,8 @@
|
||||||
#(if (selector (:permission (:attrs %)))
|
#(if (selector (:permission (:attrs %)))
|
||||||
(safe-name (:group (:attrs %)) :sql))
|
(safe-name (:group (:attrs %)) :sql))
|
||||||
permissions)))]
|
permissions)))]
|
||||||
(if
|
(if-not
|
||||||
(not (empty? group-names))
|
(empty? group-names)
|
||||||
(s/join
|
(s/join
|
||||||
" "
|
" "
|
||||||
(list
|
(list
|
||||||
|
@ -318,12 +323,12 @@
|
||||||
(str (safe-name entity) "." (field-name %)))
|
(str (safe-name entity) "." (field-name %)))
|
||||||
(str (safe-name entity) "." (field-name %)))
|
(str (safe-name entity) "." (field-name %)))
|
||||||
(filter
|
(filter
|
||||||
#(not (= (:type (:attrs %)) "link"))
|
#(not= (:type (:attrs %)) "link")
|
||||||
(all-properties entity) )))))
|
(all-properties entity) )))))
|
||||||
(str
|
(str
|
||||||
"FROM " (s/join ", " (set (compose-convenience-view-select-list entity application true))))
|
"FROM " (s/join ", " (set (compose-convenience-view-select-list entity application true))))
|
||||||
(if
|
(if-not
|
||||||
(not (empty? entity-fields))
|
(empty? entity-fields)
|
||||||
(str
|
(str
|
||||||
"WHERE "
|
"WHERE "
|
||||||
(s/join
|
(s/join
|
||||||
|
@ -408,7 +413,7 @@
|
||||||
(list
|
(list
|
||||||
doc-comment
|
doc-comment
|
||||||
(map
|
(map
|
||||||
#(:content %)
|
:content
|
||||||
(children-with-tag entity :documentation))))
|
(children-with-tag entity :documentation))))
|
||||||
(s/join
|
(s/join
|
||||||
" "
|
" "
|
||||||
|
@ -427,7 +432,7 @@
|
||||||
(map
|
(map
|
||||||
#(emit-property % entity application false)
|
#(emit-property % entity application false)
|
||||||
(filter
|
(filter
|
||||||
#(not (= (:type (:attrs %)) "link"))
|
#(not= (:type (:attrs %)) "link")
|
||||||
(children-with-tag entity :property)))))))
|
(children-with-tag entity :property)))))))
|
||||||
"\n);")
|
"\n);")
|
||||||
(map
|
(map
|
||||||
|
@ -532,7 +537,7 @@
|
||||||
(str "(https://github.com/simon-brooke/adl) at "
|
(str "(https://github.com/simon-brooke/adl) at "
|
||||||
(f/unparse (f/formatters :basic-date-time) (t/now)))
|
(f/unparse (f/formatters :basic-date-time) (t/now)))
|
||||||
(map
|
(map
|
||||||
#(:content %)
|
:content
|
||||||
(children-with-tag application :documentation))))
|
(children-with-tag application :documentation))))
|
||||||
|
|
||||||
|
|
||||||
|
@ -568,18 +573,10 @@
|
||||||
(:name (:attrs application))
|
(:name (:attrs application))
|
||||||
".postgres.sql")]
|
".postgres.sql")]
|
||||||
(make-parents filepath)
|
(make-parents filepath)
|
||||||
(try
|
(do-or-warn
|
||||||
(spit filepath (emit-application application))
|
(spit filepath (emit-application application))
|
||||||
(if (> *verbosity* 0)
|
(if
|
||||||
(*warn* (str "\tGenerated " filepath)))
|
(pos? *verbosity*)
|
||||||
(catch
|
(*warn* (str "\tGenerated " filepath))))))
|
||||||
Exception any
|
|
||||||
(*warn*
|
|
||||||
(str
|
|
||||||
"ERROR: Exception "
|
|
||||||
(.getName (.getClass any))
|
|
||||||
(.getMessage any)
|
|
||||||
" while printing "
|
|
||||||
filepath))))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
(ns ^{:doc "Application Description Language: generate routes for user interface requests."
|
(ns ^{:doc "Application Description Language: generate routes for user interface requests."
|
||||||
:author "Simon Brooke"}
|
:author "Simon Brooke"}
|
||||||
adl.to-selmer-routes
|
adl.to-selmer-routes
|
||||||
(:require [adl-support.core :refer [*warn*]]
|
(:require [adl-support.core :refer :all]
|
||||||
[adl-support.utils :refer :all]
|
[adl-support.utils :refer :all]
|
||||||
|
[adl-support.forms-support :refer :all]
|
||||||
[clj-time.core :as t]
|
[clj-time.core :as t]
|
||||||
[clj-time.format :as f]
|
[clj-time.format :as f]
|
||||||
[clojure.java.io :refer [file make-parents writer]]
|
[clojure.java.io :refer [file make-parents writer]]
|
||||||
|
@ -34,11 +35,8 @@
|
||||||
;;;;
|
;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
;;; Generally. there's one route in the generated file for each Selmer
|
;;; Generally. there are two routes - one for GET, one for POST - in the
|
||||||
;;; template which has been generated.
|
;;; generated file for each Selmer template which has been generated.
|
||||||
|
|
||||||
;;; TODO: there must be some more idiomatic way of generating all these
|
|
||||||
;;; functions.
|
|
||||||
|
|
||||||
(defn file-header
|
(defn file-header
|
||||||
[application]
|
[application]
|
||||||
|
@ -51,6 +49,8 @@
|
||||||
(list
|
(list
|
||||||
:require
|
:require
|
||||||
'[adl-support.core :as support]
|
'[adl-support.core :as support]
|
||||||
|
'[adl-support.forms-support :refer :all]
|
||||||
|
'[adl-support.rest-support :refer :all]
|
||||||
'[clojure.java.io :as io]
|
'[clojure.java.io :as io]
|
||||||
'[clojure.set :refer [subset?]]
|
'[clojure.set :refer [subset?]]
|
||||||
'[clojure.tools.logging :as log]
|
'[clojure.tools.logging :as log]
|
||||||
|
@ -65,23 +65,17 @@
|
||||||
(vector (symbol (str (:name (:attrs application)) ".routes.manual")) :as 'm))))
|
(vector (symbol (str (:name (:attrs application)) ".routes.manual")) :as 'm))))
|
||||||
|
|
||||||
|
|
||||||
(defn make-form-handler-content
|
(defn make-form-get-handler-content
|
||||||
[f e a n]
|
[f e a n]
|
||||||
(let [warning (list 'str (str "Error while fetching " (singularise (:name (:attrs e))) " record ") 'params)]
|
(let [entity-name (singularise (:name (:attrs e)))]
|
||||||
;; TODO: as yet makes no attempt to save the record
|
;; TODO: as yet makes no attempt to save the record
|
||||||
(list 'let
|
(list 'let
|
||||||
(vector
|
(vector
|
||||||
'record (list
|
'record (list
|
||||||
'support/do-or-log-error
|
'get-current-value
|
||||||
;;(list 'if (list 'subset? (key-names e) (list 'set (list 'keys 'params)))
|
(symbol (str "db/get-" entity-name))
|
||||||
(list
|
'params
|
||||||
(symbol
|
entity-name))
|
||||||
(str "db/get-" (singularise (:name (:attrs e)))))
|
|
||||||
(symbol "db/*db*")
|
|
||||||
'params)
|
|
||||||
;;)
|
|
||||||
:message warning
|
|
||||||
:error-return {:warnings [warning]}))
|
|
||||||
(reduce
|
(reduce
|
||||||
merge
|
merge
|
||||||
{:error (list :warnings 'record)
|
{:error (list :warnings 'record)
|
||||||
|
@ -99,28 +93,15 @@
|
||||||
'list
|
'list
|
||||||
;; Get the current value of the property, if it's an entity
|
;; Get the current value of the property, if it's an entity
|
||||||
(if (= (-> property :attrs :type) "entity")
|
(if (= (-> property :attrs :type) "entity")
|
||||||
(list 'support/do-or-log-error
|
(list 'get-menu-options
|
||||||
(list
|
(-> e :attrs :name)
|
||||||
(symbol
|
(-> property :attrs :farkey)
|
||||||
(str "db/get-" (singularise (:entity (:attrs property)))))
|
(list (keyword (-> property :attrs :name)) 'params))))))))
|
||||||
(symbol "db/*db*")
|
|
||||||
(hash-map (keyword (-> property :attrs :farkey))
|
|
||||||
(list (keyword (-> property :attrs :name)) 'record)))
|
|
||||||
:message (str "Error while fetching "
|
|
||||||
(singularise (:entity (:attrs property)))
|
|
||||||
" record " (hash-map (keyword (-> property :attrs :farkey))
|
|
||||||
(list (keyword (-> property :attrs :name)) 'record)))))
|
|
||||||
;;; and the potential values of the property
|
|
||||||
(list 'support/do-or-log-error
|
|
||||||
(list (symbol (str "db/list-" (:entity (:attrs property)))) (symbol "db/*db*"))
|
|
||||||
:message (str "Error while fetching "
|
|
||||||
(singularise (:entity (:attrs property)))
|
|
||||||
" list")))))))
|
|
||||||
(filter #(:entity (:attrs %))
|
(filter #(:entity (:attrs %))
|
||||||
(descendants-with-tag e :property)))))))
|
(descendants-with-tag e :property)))))))
|
||||||
|
|
||||||
|
|
||||||
(defn make-page-handler-content
|
(defn make-page-get-handler-content
|
||||||
[f e a n]
|
[f e a n]
|
||||||
(let [warning (str "Error while fetching " (singularise (:name (:attrs e))) " record")]
|
(let [warning (str "Error while fetching " (singularise (:name (:attrs e))) " record")]
|
||||||
(list 'let
|
(list 'let
|
||||||
|
@ -138,7 +119,7 @@
|
||||||
:record (list 'assoc 'record :warnings nil)})))
|
:record (list 'assoc 'record :warnings nil)})))
|
||||||
|
|
||||||
|
|
||||||
(defn make-list-handler-content
|
(defn make-list-get-handler-content
|
||||||
[f e a n]
|
[f e a n]
|
||||||
(list
|
(list
|
||||||
'let
|
'let
|
||||||
|
@ -190,57 +171,137 @@
|
||||||
{:records 'records})))
|
{:records 'records})))
|
||||||
|
|
||||||
|
|
||||||
(defn make-handler
|
(defn handler-name
|
||||||
|
"Generate the name of the appropriate handler function for form `f` of
|
||||||
|
entity `e` of application `a` for method `m`, where `f`, `e`, and `a`
|
||||||
|
are expected to be elements and `m` is expected to be one of the keywords
|
||||||
|
`:put` `:get`."
|
||||||
|
[f e a m]
|
||||||
|
(str (s/lower-case (name m)) "-" (path-part f e a)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-get-handler
|
||||||
[f e a]
|
[f e a]
|
||||||
(let [n (path-part f e a)]
|
(let [n (handler-name f e a :get)]
|
||||||
(list
|
(list
|
||||||
'defn
|
'defn
|
||||||
(symbol n)
|
(symbol n)
|
||||||
(vector 'request)
|
(vector 'request)
|
||||||
(list 'let (vector
|
(list 'let (vector
|
||||||
'params
|
'params
|
||||||
(list 'support/massage-params
|
(list 'support/massage-params 'request))
|
||||||
(list 'keywordize-keys (list :params 'request))
|
|
||||||
(list 'keywordize-keys (list :form-params 'request))
|
|
||||||
(key-names e true)))
|
|
||||||
(list
|
(list
|
||||||
'l/render
|
'l/render
|
||||||
(list 'support/resolve-template (str n ".html"))
|
(list 'support/resolve-template (str (path-part f e a) ".html"))
|
||||||
(list 'merge
|
(list 'merge
|
||||||
{:title (capitalise (:name (:attrs f)))
|
{:title (capitalise (:name (:attrs f)))
|
||||||
:params 'params}
|
:params 'params}
|
||||||
(case (:tag f)
|
(case (:tag f)
|
||||||
:form (make-form-handler-content f e a n)
|
:form (make-form-get-handler-content f e a n)
|
||||||
:page (make-page-handler-content f e a n)
|
:page (make-page-get-handler-content f e a n)
|
||||||
:list (make-list-handler-content f e a n))))))))
|
:list (make-list-get-handler-content f e a n))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-form-post-handler-content
|
||||||
|
;; Literally the only thing the post handler has to do is to
|
||||||
|
;; generate the database store operation. Then it can hand off
|
||||||
|
;; to the get handler.
|
||||||
|
[f e a n]
|
||||||
|
(let
|
||||||
|
[create-name (query-name e :create)
|
||||||
|
update-name (query-name e :update)]
|
||||||
|
(list
|
||||||
|
'let
|
||||||
|
(vector
|
||||||
|
'result
|
||||||
|
(list
|
||||||
|
'valid-user-or-forbid
|
||||||
|
(list
|
||||||
|
'with-params-or-error
|
||||||
|
(list
|
||||||
|
'do-or-server-fail
|
||||||
|
(list
|
||||||
|
'if
|
||||||
|
(list 'all-keys-present? 'params (key-names e true))
|
||||||
|
(list
|
||||||
|
update-name
|
||||||
|
'db/*db*
|
||||||
|
'params)
|
||||||
|
(list
|
||||||
|
create-name
|
||||||
|
'db/*db*
|
||||||
|
'params))
|
||||||
|
200) ;; OK
|
||||||
|
'params
|
||||||
|
(set
|
||||||
|
(map
|
||||||
|
#(keyword (:name (:attrs %)))
|
||||||
|
(insertable-properties e))))
|
||||||
|
'request))
|
||||||
|
(list
|
||||||
|
'if
|
||||||
|
(list
|
||||||
|
(set [200 400])
|
||||||
|
(list :status 'result))
|
||||||
|
(list
|
||||||
|
(symbol (handler-name f e a :get))
|
||||||
|
(list
|
||||||
|
'assoc
|
||||||
|
'request
|
||||||
|
:params
|
||||||
|
(list
|
||||||
|
'merge
|
||||||
|
'params
|
||||||
|
'result)))
|
||||||
|
'result))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn make-post-handler
|
||||||
|
[f e a]
|
||||||
|
(let [n (handler-name f e a :post)]
|
||||||
|
(list
|
||||||
|
'defn
|
||||||
|
(symbol n)
|
||||||
|
(vector 'request)
|
||||||
|
(case
|
||||||
|
(:tag f)
|
||||||
|
(:page :list) (list (symbol (handler-name f e a :get)) 'request)
|
||||||
|
:form (list
|
||||||
|
'let
|
||||||
|
(vector
|
||||||
|
'params
|
||||||
|
(list 'support/massage-params 'request))
|
||||||
|
(make-form-post-handler-content f e a n))))))
|
||||||
|
|
||||||
|
|
||||||
;; (def a (x/parse "../youyesyet/youyesyet.canonical.adl.xml"))
|
;; (def a (x/parse "../youyesyet/youyesyet.canonical.adl.xml"))
|
||||||
;; (def e (child-with-tag a :entity))
|
;; (def e (child-with-tag a :entity))
|
||||||
;; (def f (child-with-tag e :form))
|
;; (def f (child-with-tag e :form))
|
||||||
;; (def n (path-part f e a))
|
;; (def n (handler-name f e a :post))
|
||||||
;; (make-handler f e a)
|
;; (make-post-handler f e a)
|
||||||
;; (vector
|
;; (vector
|
||||||
;; 'p
|
;; 'p
|
||||||
;; (list 'merge
|
;; (list 'merge
|
||||||
;; {:offset 0 :limit 25}
|
;; {:offset 0 :limit 25}
|
||||||
;; (list 'support/massage-params (list :params 'r))))
|
;; (list 'support/massage-params (list :params 'r))))
|
||||||
;; (make-handler f e a)
|
;; (make-get-handler f e a)
|
||||||
|
|
||||||
|
|
||||||
(defn make-route
|
(defn make-route
|
||||||
"Make a route for method `m` to request the resource with name `n`."
|
"Make a route for method `m` to request the resource with name `n`."
|
||||||
[m n]
|
[m n]
|
||||||
(list
|
(list
|
||||||
m
|
(symbol (s/upper-case (name m)))
|
||||||
(str "/" n)
|
(str "/" n)
|
||||||
'request
|
'request
|
||||||
(list
|
(list
|
||||||
'route/restricted
|
'route/restricted
|
||||||
(list
|
(list
|
||||||
'apply
|
'apply
|
||||||
(list 'resolve-handler n)
|
(list 'resolve-handler (str (s/lower-case (name m)) "-" n))
|
||||||
(list 'list 'request)))))
|
(list 'list 'request)))))
|
||||||
|
|
||||||
|
|
||||||
(defn make-defroutes
|
(defn make-defroutes
|
||||||
[application]
|
[application]
|
||||||
(let [routes (flatten
|
(let [routes (flatten
|
||||||
|
@ -263,10 +324,10 @@
|
||||||
(apply (resolve-handler "index") (list request))))
|
(apply (resolve-handler "index") (list request))))
|
||||||
(interleave
|
(interleave
|
||||||
(map
|
(map
|
||||||
(fn [r] (make-route 'GET r))
|
(fn [r] (make-route :get r))
|
||||||
(sort routes))
|
(sort routes))
|
||||||
(map
|
(map
|
||||||
(fn [r] (make-route 'POST r))
|
(fn [r] (make-route :post r))
|
||||||
(sort routes))))))))
|
(sort routes))))))))
|
||||||
|
|
||||||
|
|
||||||
|
@ -294,16 +355,27 @@
|
||||||
(doall
|
(doall
|
||||||
(map
|
(map
|
||||||
(fn [c]
|
(fn [c]
|
||||||
(pprint (make-handler c e application))
|
;; do all get handlers before post handlers, so that the post
|
||||||
(println))
|
;; handlers can call the get handlers.
|
||||||
|
(pprint (make-get-handler c e application))
|
||||||
|
(println "\n")
|
||||||
|
(pprint (make-post-handler c e application))
|
||||||
|
(println "\n"))
|
||||||
(filter (fn [c] (#{:form :list :page} (:tag c))) (children e)))))
|
(filter (fn [c] (#{:form :list :page} (:tag c))) (children e)))))
|
||||||
|
|
||||||
|
|
||||||
(defn to-selmer-routes
|
(defn to-selmer-routes
|
||||||
[application]
|
[application]
|
||||||
(let [filepath (str *output-path* "src/clj/" (:name (:attrs application)) "/routes/auto.clj")]
|
(let [filepath (str
|
||||||
|
*output-path*
|
||||||
|
"src/clj/"
|
||||||
|
(:name (:attrs application))
|
||||||
|
"/routes/auto.clj")
|
||||||
|
entities (sort
|
||||||
|
#(compare (:name (:attrs %1))(:name (:attrs %2)))
|
||||||
|
(children-with-tag application :entity))]
|
||||||
(make-parents filepath)
|
(make-parents filepath)
|
||||||
(try
|
(do-or-warn
|
||||||
(with-open [output (writer filepath)]
|
(with-open [output (writer filepath)]
|
||||||
(binding [*out* output]
|
(binding [*out* output]
|
||||||
(pprint (file-header application))
|
(pprint (file-header application))
|
||||||
|
@ -319,9 +391,7 @@
|
||||||
(doall
|
(doall
|
||||||
(map
|
(map
|
||||||
#(make-handlers % application)
|
#(make-handlers % application)
|
||||||
(sort
|
entities))
|
||||||
#(compare (:name (:attrs %1))(:name (:attrs %2)))
|
|
||||||
(children-with-tag application :entity))))
|
|
||||||
(pprint
|
(pprint
|
||||||
(generate-handler-resolver application))
|
(generate-handler-resolver application))
|
||||||
(println)
|
(println)
|
||||||
|
@ -330,15 +400,7 @@
|
||||||
(println)
|
(println)
|
||||||
(pprint (make-defroutes application))
|
(pprint (make-defroutes application))
|
||||||
(println)))
|
(println)))
|
||||||
(if (> *verbosity* 0)
|
(if
|
||||||
(*warn* (str "\tGenerated " filepath)))
|
(pos? *verbosity*)
|
||||||
(catch
|
(*warn* (str "\tGenerated " filepath))))))
|
||||||
Exception any
|
|
||||||
(*warn*
|
|
||||||
(str
|
|
||||||
"ERROR: Exception "
|
|
||||||
(.getName (.getClass any))
|
|
||||||
(.getMessage any)
|
|
||||||
" while printing "
|
|
||||||
filepath))))))
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
(ns ^{:doc "Application Description Language - generate Selmer templates for the HTML pages implied by an ADL file."
|
(ns ^{:doc "Application Description Language - generate Selmer templates for
|
||||||
|
the HTML pages implied by an ADL file."
|
||||||
:author "Simon Brooke"}
|
:author "Simon Brooke"}
|
||||||
adl.to-selmer-templates
|
adl.to-selmer-templates
|
||||||
(:require [adl-support.core :refer [*warn*]]
|
(:require [adl-support.core :refer :all]
|
||||||
[adl.to-hugsql-queries :refer [expanded-token]]
|
[adl.to-hugsql-queries :refer [expanded-token]]
|
||||||
[adl-support.utils :refer :all]
|
[adl-support.utils :refer :all]
|
||||||
[clojure.java.io :refer [file make-parents resource]]
|
[clojure.java.io :refer [file make-parents resource]]
|
||||||
|
@ -41,7 +42,9 @@
|
||||||
{:tag :div
|
{:tag :div
|
||||||
:attrs {:class "big-link-container"}
|
:attrs {:class "big-link-container"}
|
||||||
:content
|
:content
|
||||||
[{:tag :a :attrs {:href (str "{{servlet-context}}/" url) :class "big-link"}
|
[{:tag :a
|
||||||
|
:attrs {:href (str "{{servlet-context}}/" url)
|
||||||
|
:class "big-link"}
|
||||||
:content (if
|
:content (if
|
||||||
(vector? content)
|
(vector? content)
|
||||||
content
|
content
|
||||||
|
@ -62,7 +65,7 @@
|
||||||
|
|
||||||
(defn emit-content
|
(defn emit-content
|
||||||
([content]
|
([content]
|
||||||
(try
|
(do-or-warn
|
||||||
(cond
|
(cond
|
||||||
(nil? content)
|
(nil? content)
|
||||||
nil
|
nil
|
||||||
|
@ -75,15 +78,7 @@
|
||||||
(map emit-content (remove nil? content))
|
(map emit-content (remove nil? content))
|
||||||
true
|
true
|
||||||
(str "<!-- don't know what to do with '" content "' -->"))
|
(str "<!-- don't know what to do with '" content "' -->"))
|
||||||
(catch Exception any
|
(str "Failed while writing " content)))
|
||||||
(str
|
|
||||||
"<!-- failed while trying to emit \n'"
|
|
||||||
(with-out-str (p/pprint content))
|
|
||||||
"';\n"
|
|
||||||
(-> any .getClass .getName)
|
|
||||||
": "
|
|
||||||
(-> any .getMessage)
|
|
||||||
" -->"))))
|
|
||||||
([filename application k]
|
([filename application k]
|
||||||
(emit-content filename nil nil application k))
|
(emit-content filename nil nil application k))
|
||||||
([filename spec entity application k]
|
([filename spec entity application k]
|
||||||
|
@ -143,7 +138,8 @@
|
||||||
|
|
||||||
|
|
||||||
(defn csrf-widget
|
(defn csrf-widget
|
||||||
"For the present, just return the standard cross site scripting protection field statement"
|
"For the present, just return the standard cross site scripting protection
|
||||||
|
field statement"
|
||||||
[]
|
[]
|
||||||
"{% csrf-field %}")
|
"{% csrf-field %}")
|
||||||
|
|
||||||
|
@ -179,16 +175,20 @@
|
||||||
|
|
||||||
|
|
||||||
(defn save-widget
|
(defn save-widget
|
||||||
"Return an appropriate 'save' widget for this `form` operating on this `entity` taken
|
"Return an appropriate 'save' widget for this `form` operating on this
|
||||||
from this `application`.
|
`entity` taken from this `application`.
|
||||||
TODO: should be suppressed unless a member of a group which can insert or edit."
|
TODO: should be suppressed unless a member of a group which can insert
|
||||||
|
or edit."
|
||||||
[form entity application]
|
[form entity application]
|
||||||
(wrap-in-if-member-of
|
(wrap-in-if-member-of
|
||||||
{:tag :p
|
{:tag :p
|
||||||
:attrs {:class "widget action-safe"}
|
:attrs {:class "widget action-safe"}
|
||||||
:content [{:tag :label
|
:content [{:tag :label
|
||||||
:attrs {:for "save-button" :class "action-safe"}
|
:attrs {:for "save-button" :class "action-safe"}
|
||||||
:content [(str "To save this " (:name (:attrs entity)) " record")]}
|
:content [(str
|
||||||
|
"To save this "
|
||||||
|
(:name (:attrs entity))
|
||||||
|
" record")]}
|
||||||
{:tag :input
|
{:tag :input
|
||||||
:attrs {:id "save-button"
|
:attrs {:id "save-button"
|
||||||
:name "save-button"
|
:name "save-button"
|
||||||
|
@ -201,16 +201,20 @@
|
||||||
|
|
||||||
|
|
||||||
(defn delete-widget
|
(defn delete-widget
|
||||||
"Return an appropriate 'save' widget for this `form` operating on this `entity` taken
|
"Return an appropriate 'save' widget for this `form` operating on this
|
||||||
from this `application`.
|
`entity` taken from this `application`.
|
||||||
TODO: should be suppressed unless member of a group which can delete."
|
TODO: should be suppressed unless member of a group which can delete."
|
||||||
[form entity application]
|
[form entity application]
|
||||||
(wrap-in-if-member-of
|
(wrap-in-if-member-of
|
||||||
{:tag :p
|
{:tag :p
|
||||||
:attrs {:class "widget action-dangerous"}
|
:attrs {:class "widget action-dangerous"}
|
||||||
:content [{:tag :label
|
:content [{:tag :label
|
||||||
:attrs {:for "delete-button" :class "action-dangerous"}
|
:attrs {:for "delete-button"
|
||||||
:content [(str "To delete this " (:name (:attrs entity)) " record")]}
|
:class "action-dangerous"}
|
||||||
|
:content [(str
|
||||||
|
"To delete this "
|
||||||
|
(:name (:attrs entity))
|
||||||
|
" record")]}
|
||||||
{:tag :input
|
{:tag :input
|
||||||
:attrs {:id "delete-button"
|
:attrs {:id "delete-button"
|
||||||
:name "delete-button"
|
:name "delete-button"
|
||||||
|
@ -223,7 +227,8 @@
|
||||||
|
|
||||||
|
|
||||||
(defn select-property
|
(defn select-property
|
||||||
"Return the property on which we will by default do a user search on this `entity`."
|
"Return the property on which we will by default do a user search on this
|
||||||
|
`entity`."
|
||||||
[entity]
|
[entity]
|
||||||
(descendant-with-tag
|
(descendant-with-tag
|
||||||
entity
|
entity
|
||||||
|
@ -241,8 +246,8 @@
|
||||||
|
|
||||||
|
|
||||||
(defn get-options
|
(defn get-options
|
||||||
"Produce template code to get options for this `property` of this `entity` taken from
|
"Produce template code to get options for this `property` of this `entity`
|
||||||
this `application`."
|
taken from this `application`."
|
||||||
[property form entity application]
|
[property form entity application]
|
||||||
(let
|
(let
|
||||||
[type (:type (:attrs property))
|
[type (:type (:attrs property))
|
||||||
|
@ -258,9 +263,9 @@
|
||||||
(:farkey (:attrs property))
|
(:farkey (:attrs property))
|
||||||
(first (key-names farside))
|
(first (key-names farside))
|
||||||
"id")]
|
"id")]
|
||||||
;; Yes, I know it looks BONKERS generating this as an HTML string. But there is a
|
;; Yes, I know it looks BONKERS generating this as an HTML string. But
|
||||||
;; reason. We don't know whether the `selected` attribute should be present or
|
;; there is a reason. We don't know whether the `selected` attribute
|
||||||
;; absent until rendering.
|
;; should be present or absent until rendering.
|
||||||
[(str "{% for option in " (-> property :attrs :name)
|
[(str "{% for option in " (-> property :attrs :name)
|
||||||
" %}<option value='{{option."
|
" %}<option value='{{option."
|
||||||
farkey
|
farkey
|
||||||
|
@ -281,7 +286,9 @@
|
||||||
(:type (:attrs typedef))
|
(:type (:attrs typedef))
|
||||||
(:type (:attrs property)))]
|
(:type (:attrs property)))]
|
||||||
(if
|
(if
|
||||||
(and (= (-> property :attrs :distinct) "system") (= (-> property :attrs :immutable) "true"))
|
(and
|
||||||
|
(= (-> property :attrs :distinct) "system")
|
||||||
|
(= (-> property :attrs :immutable) "true"))
|
||||||
"hidden"
|
"hidden"
|
||||||
(case t
|
(case t
|
||||||
("integer" "real" "money") "number"
|
("integer" "real" "money") "number"
|
||||||
|
@ -298,8 +305,13 @@
|
||||||
(defn select-widget
|
(defn select-widget
|
||||||
[property form entity application]
|
[property form entity application]
|
||||||
(let [farname (:entity (:attrs property))
|
(let [farname (:entity (:attrs property))
|
||||||
farside (first (children application #(= (:name (:attrs %)) farname)))
|
farside (first
|
||||||
magnitude (try (read-string (:magnitude (:attrs farside))) (catch Exception _ 7))
|
(children
|
||||||
|
application
|
||||||
|
#(= (:name (:attrs %)) farname)))
|
||||||
|
magnitude (try
|
||||||
|
(read-string (:magnitude (:attrs farside)))
|
||||||
|
(catch Exception _ 7))
|
||||||
async? (and (number? magnitude) (> magnitude 1))
|
async? (and (number? magnitude) (> magnitude 1))
|
||||||
widget-name (safe-name (:name (:attrs property)) :sql)]
|
widget-name (safe-name (:name (:attrs property)) :sql)]
|
||||||
{:tag :select
|
{:tag :select
|
||||||
|
@ -309,7 +321,9 @@
|
||||||
(if
|
(if
|
||||||
(= (:type (:attrs property)) "link")
|
(= (:type (:attrs property)) "link")
|
||||||
{:multiple "multiple"}))
|
{:multiple "multiple"}))
|
||||||
:content (apply vector (get-options property form entity application))}))
|
:content (apply
|
||||||
|
vector
|
||||||
|
(get-options property form entity application))}))
|
||||||
|
|
||||||
|
|
||||||
(defn compose-readable-or-not-authorised
|
(defn compose-readable-or-not-authorised
|
||||||
|
@ -326,7 +340,11 @@
|
||||||
:attrs {:id w
|
:attrs {:id w
|
||||||
:name w
|
:name w
|
||||||
:class "pseudo-widget not-authorised"}
|
:class "pseudo-widget not-authorised"}
|
||||||
:content [(str "You are not permitted to view " w " of " (:name (:attrs e)))]}
|
:content [(str
|
||||||
|
"You are not permitted to view "
|
||||||
|
w
|
||||||
|
" of "
|
||||||
|
(:name (:attrs e)))]}
|
||||||
"{% endifmemberof %}"))
|
"{% endifmemberof %}"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,46 +374,14 @@
|
||||||
"{% endif %}")))})
|
"{% endif %}")))})
|
||||||
|
|
||||||
|
|
||||||
(defn widget
|
(defn compose-input-widget-para
|
||||||
"Generate a widget for this `field-or-property` of this `form` for this `entity`
|
"Generate an input widget for this `field-or-property` of this `form` for
|
||||||
taken from within this `application`."
|
this `entity` taken from within this `application`, in context of a para
|
||||||
[field-or-property form entity application]
|
also containing its label."
|
||||||
|
[property form entity application widget-name]
|
||||||
(let
|
(let
|
||||||
[widget-name (safe-name
|
[typedef (typedef property application)
|
||||||
(if (= (:tag field-or-property) :property)
|
w-type (widget-type property application typedef)]
|
||||||
(:name (:attrs field-or-property))
|
|
||||||
(:property (:attrs field-or-property))) :sql)
|
|
||||||
property (case
|
|
||||||
(:tag field-or-property)
|
|
||||||
:property field-or-property
|
|
||||||
:field (property-for-field field-or-property entity)
|
|
||||||
;; default
|
|
||||||
nil)
|
|
||||||
permissions (find-permissions field-or-property property form entity application)
|
|
||||||
typedef (typedef property application)
|
|
||||||
w-type (widget-type property application typedef)
|
|
||||||
visible-to (visible-to permissions)
|
|
||||||
;; if the form isn't actually a form, no widget is writeable.
|
|
||||||
writeable-by (if (= (:tag form) :form) (writeable-by permissions))]
|
|
||||||
(if
|
|
||||||
property
|
|
||||||
(case w-type
|
|
||||||
"hidden"
|
|
||||||
{:tag :input
|
|
||||||
:attrs {:id widget-name
|
|
||||||
:name widget-name
|
|
||||||
:type "hidden"
|
|
||||||
:value (str "{{record." widget-name "}}")}}
|
|
||||||
"select"
|
|
||||||
(compose-widget-para property form entity application widget-name
|
|
||||||
(select-widget property form entity application))
|
|
||||||
"text-area"
|
|
||||||
(compose-widget-para
|
|
||||||
property form entity application widget-name
|
|
||||||
{:tag :textarea
|
|
||||||
:attrs {:rows "8" :cols "60" :id widget-name :name widget-name}
|
|
||||||
:content [(str "{{record." widget-name "}}")]})
|
|
||||||
;; all others
|
|
||||||
(compose-widget-para
|
(compose-widget-para
|
||||||
property form entity application widget-name
|
property form entity application widget-name
|
||||||
{:tag :input
|
{:tag :input
|
||||||
|
@ -415,18 +401,70 @@
|
||||||
"60"
|
"60"
|
||||||
true
|
true
|
||||||
(:size (:attrs property)))}
|
(:size (:attrs property)))}
|
||||||
|
;; TODO: should match pattern from typedef
|
||||||
(if
|
(if
|
||||||
(:minimum (:attrs typedef))
|
(:minimum (:attrs typedef))
|
||||||
{:min (:minimum (:attrs typedef))})
|
{:min (:minimum (:attrs typedef))})
|
||||||
(if
|
(if
|
||||||
(:maximum (:attrs typedef))
|
(:maximum (:attrs typedef))
|
||||||
{:max (:maximum (:attrs typedef))}))})))))
|
{:max (:maximum (:attrs typedef))}))})))
|
||||||
|
|
||||||
|
|
||||||
|
(defn widget
|
||||||
|
"Generate a widget for this `field-or-property` of this `form` for this
|
||||||
|
`entity` taken from within this `application`, in context of a para also
|
||||||
|
containing its label."
|
||||||
|
[field-or-property form entity application]
|
||||||
|
(let
|
||||||
|
[widget-name (safe-name
|
||||||
|
(if (= (:tag field-or-property) :property)
|
||||||
|
(:name (:attrs field-or-property))
|
||||||
|
(:property (:attrs field-or-property))) :sql)
|
||||||
|
property (case
|
||||||
|
(:tag field-or-property)
|
||||||
|
:property field-or-property
|
||||||
|
:field (property-for-field field-or-property entity)
|
||||||
|
;; default
|
||||||
|
nil)
|
||||||
|
typedef (typedef property application)
|
||||||
|
w-type (widget-type property application typedef)]
|
||||||
|
(if
|
||||||
|
property
|
||||||
|
(case w-type
|
||||||
|
"hidden"
|
||||||
|
{:tag :input
|
||||||
|
:attrs {:id widget-name
|
||||||
|
:name widget-name
|
||||||
|
:type "hidden"
|
||||||
|
:value (str "{{record." widget-name "}}")}}
|
||||||
|
"select"
|
||||||
|
(compose-widget-para
|
||||||
|
property
|
||||||
|
form
|
||||||
|
entity
|
||||||
|
application
|
||||||
|
widget-name
|
||||||
|
(select-widget property form entity application))
|
||||||
|
"text-area"
|
||||||
|
(compose-widget-para
|
||||||
|
property form entity application widget-name
|
||||||
|
{:tag :textarea
|
||||||
|
:attrs {:rows "8" :cols "60" :id widget-name :name widget-name}
|
||||||
|
:content [(str "{{record." widget-name "}}")]})
|
||||||
|
;; all others
|
||||||
|
(compose-input-widget-para
|
||||||
|
property
|
||||||
|
form
|
||||||
|
entity
|
||||||
|
application
|
||||||
|
widget-name)))))
|
||||||
|
|
||||||
|
|
||||||
(defn embed-script-fragment
|
(defn embed-script-fragment
|
||||||
"Return the content of the file at `resource-path`, with these `substitutions`
|
"Return the content of the file at `resource-path`, with these
|
||||||
made into it in order. Substitutions should be pairss [`pattern` `value`],
|
`substitutions` made into it in order. Substitutions should be pairs
|
||||||
where `pattern` is a string, a char, or a regular expression."
|
[`pattern` `value`], where `pattern` is a string, a char, or a regular
|
||||||
|
expression."
|
||||||
([resource-path substitutions]
|
([resource-path substitutions]
|
||||||
(let [v (slurp (resource resource-path))]
|
(let [v (slurp (resource resource-path))]
|
||||||
(reduce
|
(reduce
|
||||||
|
@ -455,8 +493,8 @@
|
||||||
|
|
||||||
|
|
||||||
(defn list-tbody
|
(defn list-tbody
|
||||||
"Return a table body element for the list view for this `list-spec` of this `entity` within
|
"Return a table body element for the list view for this `list-spec` of
|
||||||
this `application`, using data from this source."
|
this `entity` within this `application`, using data from this source."
|
||||||
[source list-spec entity application]
|
[source list-spec entity application]
|
||||||
{:tag :tbody
|
{:tag :tbody
|
||||||
:content
|
:content
|
||||||
|
@ -470,7 +508,12 @@
|
||||||
(fn [field]
|
(fn [field]
|
||||||
{:tag :td :content
|
{:tag :td :content
|
||||||
(let
|
(let
|
||||||
[p (first (filter #(= (:name (:attrs %)) (:property (:attrs field))) (all-properties entity)))
|
[p (first
|
||||||
|
(filter
|
||||||
|
#(=
|
||||||
|
(:name (:attrs %))
|
||||||
|
(:property (:attrs field)))
|
||||||
|
(all-properties entity)))
|
||||||
s (safe-name (:name (:attrs p)) :sql)
|
s (safe-name (:name (:attrs p)) :sql)
|
||||||
e (first
|
e (first
|
||||||
(filter
|
(filter
|
||||||
|
@ -480,7 +523,10 @@
|
||||||
(if
|
(if
|
||||||
(= (:type (:attrs p)) "entity")
|
(= (:type (:attrs p)) "entity")
|
||||||
[{:tag :a
|
[{:tag :a
|
||||||
:attrs {:href (edit-link e application (list (:name (:attrs p))))}
|
:attrs {:href (edit-link
|
||||||
|
e
|
||||||
|
application
|
||||||
|
(list (:name (:attrs p))))}
|
||||||
:content [(str "{{ record." s "_expanded }}")]}]
|
:content [(str "{{ record." s "_expanded }}")]}]
|
||||||
[c]))})
|
[c]))})
|
||||||
(children-with-tag list-spec :field))
|
(children-with-tag list-spec :field))
|
||||||
|
@ -498,11 +544,15 @@
|
||||||
(let [property (child-with-tag
|
(let [property (child-with-tag
|
||||||
entity
|
entity
|
||||||
:property
|
:property
|
||||||
#(= (-> % :attrs :name) (-> auxlist :attrs :property)))
|
#(=
|
||||||
|
(-> % :attrs :name)
|
||||||
|
(-> auxlist :attrs :property)))
|
||||||
farside (child-with-tag
|
farside (child-with-tag
|
||||||
application
|
application
|
||||||
:entity
|
:entity
|
||||||
#(= (-> % :attrs :name)(-> property :attrs :entity)))]
|
#(=
|
||||||
|
(-> % :attrs :name)
|
||||||
|
(-> property :attrs :entity)))]
|
||||||
(if
|
(if
|
||||||
(and property farside)
|
(and property farside)
|
||||||
{:tag :div
|
{:tag :div
|
||||||
|
@ -526,7 +576,11 @@
|
||||||
:content [(prompt % form entity application)])
|
:content [(prompt % form entity application)])
|
||||||
(children-with-tag auxlist :field))
|
(children-with-tag auxlist :field))
|
||||||
{:tag :th :content [" "]})))}]}
|
{:tag :th :content [" "]})))}]}
|
||||||
(list-tbody (-> property :attrs :name) auxlist farside application)]}]})))
|
(list-tbody
|
||||||
|
(-> property :attrs :name)
|
||||||
|
auxlist
|
||||||
|
farside
|
||||||
|
application)]}]})))
|
||||||
|
|
||||||
|
|
||||||
(defn compose-form-auxlists
|
(defn compose-form-auxlists
|
||||||
|
@ -548,7 +602,9 @@
|
||||||
vector
|
vector
|
||||||
(cons
|
(cons
|
||||||
{:tag :form
|
{:tag :form
|
||||||
:attrs {:action (str "{{servlet-context}}/" (editor-name entity application))
|
:attrs {:action (str
|
||||||
|
"{{servlet-context}}/"
|
||||||
|
(editor-name entity application))
|
||||||
:method "POST"}
|
:method "POST"}
|
||||||
:content (apply
|
:content (apply
|
||||||
vector
|
vector
|
||||||
|
@ -559,13 +615,18 @@
|
||||||
(csrf-widget)
|
(csrf-widget)
|
||||||
(map
|
(map
|
||||||
#(widget % form entity application)
|
#(widget % form entity application)
|
||||||
(children-with-tag (child-with-tag entity :key) :property))
|
(children-with-tag
|
||||||
|
(child-with-tag entity :key)
|
||||||
|
:property))
|
||||||
(map
|
(map
|
||||||
#(widget % form entity application)
|
#(widget % form entity application)
|
||||||
(remove
|
(remove
|
||||||
#(let
|
#(let
|
||||||
[property (filter
|
[property
|
||||||
(fn [p] (= (:name (:attrs p)) (:property (:attrs %))))
|
(filter
|
||||||
|
(fn
|
||||||
|
[p]
|
||||||
|
(= (:name (:attrs p)) (:property (:attrs %))))
|
||||||
(descendants-with-tag entity :property))]
|
(descendants-with-tag entity :property))]
|
||||||
(= (:distict (:attrs property)) :system))
|
(= (:distict (:attrs property)) :system))
|
||||||
(children-with-tag form :field)))
|
(children-with-tag form :field)))
|
||||||
|
@ -586,7 +647,9 @@
|
||||||
(child-with-tag
|
(child-with-tag
|
||||||
form
|
form
|
||||||
:field
|
:field
|
||||||
#(= "text-area" (widget-type (property-for-field % entity) application)))
|
#(=
|
||||||
|
"text-area"
|
||||||
|
(widget-type (property-for-field % entity) application)))
|
||||||
"
|
"
|
||||||
{% script \"/js/lib/node_modules/simplemde/dist/simplemde.min.js\" %}
|
{% script \"/js/lib/node_modules/simplemde/dist/simplemde.min.js\" %}
|
||||||
{% style \"/js/lib/node_modules/simplemde/dist/simplemde.min.css\" %}")
|
{% style \"/js/lib/node_modules/simplemde/dist/simplemde.min.css\" %}")
|
||||||
|
@ -594,7 +657,9 @@
|
||||||
(child-with-tag
|
(child-with-tag
|
||||||
form
|
form
|
||||||
:field
|
:field
|
||||||
#(= "select" (widget-type (property-for-field % entity) application)))
|
#(=
|
||||||
|
"select"
|
||||||
|
(widget-type (property-for-field % entity) application)))
|
||||||
"
|
"
|
||||||
{% script \"/js/lib/node_modules/selectize/dist/js/standalone/selectize.min.js\" %}
|
{% script \"/js/lib/node_modules/selectize/dist/js/standalone/selectize.min.js\" %}
|
||||||
{% style \"/js/lib/node_modules/selectize/dist/css/selectize.css\" %}"))))})
|
{% style \"/js/lib/node_modules/selectize/dist/css/selectize.css\" %}"))))})
|
||||||
|
@ -614,29 +679,45 @@
|
||||||
(map
|
(map
|
||||||
(fn [field]
|
(fn [field]
|
||||||
(let
|
(let
|
||||||
[property (child-with-tag entity :property #(=
|
[property (child-with-tag
|
||||||
|
entity
|
||||||
|
:property
|
||||||
|
#(=
|
||||||
(-> field :attrs :property)
|
(-> field :attrs :property)
|
||||||
(-> % :attrs :name)))
|
(-> % :attrs :name)))
|
||||||
farname (:entity (:attrs property))
|
farname (:entity (:attrs property))
|
||||||
farside (first (children application #(= (:name (:attrs %)) farname)))
|
farside (first
|
||||||
magnitude (try (read-string (:magnitude (:attrs farside))) (catch Exception _ 7))]
|
(children
|
||||||
|
application
|
||||||
|
#(= (:name (:attrs %)) farname)))
|
||||||
|
magnitude (try
|
||||||
|
(read-string
|
||||||
|
(:magnitude
|
||||||
|
(:attrs farside)))
|
||||||
|
(catch Exception _ 7))]
|
||||||
(if
|
(if
|
||||||
(> magnitude 2)
|
(> magnitude 2)
|
||||||
(embed-script-fragment
|
(embed-script-fragment
|
||||||
"js/selectize-one.js"
|
"js/selectize-one.js"
|
||||||
[["{{widget_id}}" (-> property :attrs :name)]
|
[["{{widget_id}}" (-> property :attrs :name)]
|
||||||
["{{widget_value}}" (str "{{record." (-> property :attrs :name) "}}")]
|
["{{widget_value}}"
|
||||||
|
(str "{{record." (-> property :attrs :name) "}}")]
|
||||||
["{{entity}}" farname]
|
["{{entity}}" farname]
|
||||||
["{{field}}" (select-field-name farside)]
|
["{{field}}" (select-field-name farside)]
|
||||||
["{{key}}" (first (key-names farside))]]))))
|
["{{key}}" (first (key-names farside))]]))))
|
||||||
(children-with-tag
|
(children-with-tag
|
||||||
form :field
|
form :field
|
||||||
#(= "select" (widget-type (property-for-field % entity) application))))
|
#(=
|
||||||
|
"select"
|
||||||
|
(widget-type (property-for-field % entity) application))))
|
||||||
(if
|
(if
|
||||||
(child-with-tag
|
(child-with-tag
|
||||||
form :field
|
form :field
|
||||||
#(= "text-area" (widget-type (property-for-field % entity) application)))
|
#(=
|
||||||
(embed-script-fragment "js/text-area-md-support.js"
|
"text-area"
|
||||||
|
(widget-type (property-for-field % entity) application)))
|
||||||
|
(embed-script-fragment
|
||||||
|
"js/text-area-md-support.js"
|
||||||
[["{{page}}" (-> form :attrs :name)]]))))))}})
|
[["{{page}}" (-> form :attrs :name)]]))))))}})
|
||||||
|
|
||||||
|
|
||||||
|
@ -666,7 +747,8 @@
|
||||||
(children
|
(children
|
||||||
entity
|
entity
|
||||||
(fn [p] (and (= (:tag p) :property)
|
(fn [p] (and (= (:tag p) :property)
|
||||||
(= (:name (:attrs p)) (:property (:attrs field)))))))
|
(= (:name (:attrs p))
|
||||||
|
(:property (:attrs field)))))))
|
||||||
input-type (case (:type (:attrs property))
|
input-type (case (:type (:attrs property))
|
||||||
("integer" "real" "money") "number"
|
("integer" "real" "money") "number"
|
||||||
("date" "timestamp") "date"
|
("date" "timestamp") "date"
|
||||||
|
@ -686,8 +768,8 @@
|
||||||
|
|
||||||
|
|
||||||
(defn- list-thead
|
(defn- list-thead
|
||||||
"Return a table head element for the list view for this `list-spec` of this `entity` within
|
"Return a table head element for the list view for this `list-spec` of
|
||||||
this `application`."
|
this `entity` within this `application`."
|
||||||
[list-spec entity application]
|
[list-spec entity application]
|
||||||
{:tag :thead
|
{:tag :thead
|
||||||
:content
|
:content
|
||||||
|
@ -734,7 +816,8 @@
|
||||||
:content
|
:content
|
||||||
[{:tag :div :attrs {:class "back-link-container"}
|
[{:tag :div :attrs {:class "back-link-container"}
|
||||||
:content
|
:content
|
||||||
[{:tag :a :attrs {:id "prev-selector" :class "back-link"}
|
[{:tag :a
|
||||||
|
:attrs {:id "prev-selector" :class "back-link"}
|
||||||
:content ["Previous"]}]}]}
|
:content ["Previous"]}]}]}
|
||||||
:big-links
|
:big-links
|
||||||
{:tag :div
|
{:tag :div
|
||||||
|
@ -747,10 +830,16 @@
|
||||||
(list
|
(list
|
||||||
{:tag :div :attrs {:class "big-link-container"}
|
{:tag :div :attrs {:class "big-link-container"}
|
||||||
:content
|
:content
|
||||||
[{:tag :a :attrs {:id "next-selector" :role "button" :class "big-link"}
|
[{:tag :a
|
||||||
|
:attrs {:id "next-selector"
|
||||||
|
:role "button"
|
||||||
|
:class "big-link"}
|
||||||
:content ["Next"]}]}
|
:content ["Next"]}]}
|
||||||
(wrap-in-if-member-of
|
(wrap-in-if-member-of
|
||||||
(big-link (str "Add a new " (pretty-name entity)) (editor-name entity application))
|
(big-link (str
|
||||||
|
"Add a new "
|
||||||
|
(pretty-name entity))
|
||||||
|
(editor-name entity application))
|
||||||
:writeable
|
:writeable
|
||||||
entity
|
entity
|
||||||
application)))))}
|
application)))))}
|
||||||
|
@ -761,8 +850,12 @@
|
||||||
:method "POST"}
|
:method "POST"}
|
||||||
:content
|
:content
|
||||||
[(csrf-widget)
|
[(csrf-widget)
|
||||||
{:tag :input :attrs {:id "offset" :name "offset" :type "hidden" :value "{{params.offset|default:0}}"}}
|
{:tag :input
|
||||||
{:tag :input :attrs {:id "limit" :name "limit" :type "hidden" :value "{{params.limit|default:50}}"}}
|
:attrs {:id "offset" :name "offset" :type "hidden"
|
||||||
|
:value "{{params.offset|default:0}}"}}
|
||||||
|
{:tag :input
|
||||||
|
:attrs {:id "limit" :name "limit" :type "hidden"
|
||||||
|
:value "{{params.limit|default:50}}"}}
|
||||||
{:tag :table
|
{:tag :table
|
||||||
:attrs {:caption (:name (:attrs entity))}
|
:attrs {:caption (:name (:attrs entity))}
|
||||||
:content
|
:content
|
||||||
|
@ -813,21 +906,33 @@
|
||||||
(merge
|
(merge
|
||||||
(if
|
(if
|
||||||
forms
|
forms
|
||||||
(apply merge (map #(assoc {} (keyword (path-part % entity application))
|
(apply
|
||||||
|
merge
|
||||||
|
(map #(assoc
|
||||||
|
{}
|
||||||
|
(keyword (path-part % entity application))
|
||||||
(form-to-template % entity application))
|
(form-to-template % entity application))
|
||||||
forms))
|
forms))
|
||||||
{(keyword (str "form-" (:name (:attrs entity))))
|
{(keyword (str "form-" (:name (:attrs entity))))
|
||||||
(form-to-template nil entity application)})
|
(form-to-template nil entity application)})
|
||||||
(if
|
(if
|
||||||
pages
|
pages
|
||||||
(apply merge (map #(assoc {} (keyword (path-part % entity application))
|
(apply
|
||||||
|
merge
|
||||||
|
(map #(assoc
|
||||||
|
{}
|
||||||
|
(keyword (path-part % entity application))
|
||||||
(page-to-template % entity application))
|
(page-to-template % entity application))
|
||||||
pages))
|
pages))
|
||||||
{(keyword (str "page-" (:name (:attrs entity))))
|
{(keyword (str "page-" (:name (:attrs entity))))
|
||||||
(page-to-template nil entity application)})
|
(page-to-template nil entity application)})
|
||||||
(if
|
(if
|
||||||
lists
|
lists
|
||||||
(apply merge (map #(assoc {} (keyword (path-part % entity application))
|
(apply
|
||||||
|
merge
|
||||||
|
(map #(assoc
|
||||||
|
{}
|
||||||
|
(keyword (path-part % entity application))
|
||||||
(list-to-template % entity application))
|
(list-to-template % entity application))
|
||||||
lists))
|
lists))
|
||||||
{(keyword (str "list-" (:name (:attrs entity))))
|
{(keyword (str "list-" (:name (:attrs entity))))
|
||||||
|
@ -840,7 +945,9 @@
|
||||||
{:tag :dt
|
{:tag :dt
|
||||||
:content
|
:content
|
||||||
[{:tag :a
|
[{:tag :a
|
||||||
:attrs {:href (str "{{servlet-context}}/" (path-part :list entity application))}
|
:attrs {:href (str
|
||||||
|
"{{servlet-context}}/"
|
||||||
|
(path-part :list entity application))}
|
||||||
:content [(pretty-name entity)]}]}
|
:content [(pretty-name entity)]}]}
|
||||||
:readable
|
:readable
|
||||||
entity
|
entity
|
||||||
|
@ -895,10 +1002,13 @@
|
||||||
|
|
||||||
(defn write-template-file
|
(defn write-template-file
|
||||||
[filename template application]
|
[filename template application]
|
||||||
(let [filepath (str *output-path* "resources/templates/auto/" filename)]
|
(let [filepath (str
|
||||||
|
*output-path*
|
||||||
|
"resources/templates/auto/"
|
||||||
|
filename)]
|
||||||
(if
|
(if
|
||||||
template
|
template
|
||||||
(try
|
(do-or-warn
|
||||||
(do
|
(do
|
||||||
(make-parents filepath)
|
(make-parents filepath)
|
||||||
(spit
|
(spit
|
||||||
|
@ -917,24 +1027,10 @@
|
||||||
"{% endblock %}"))
|
"{% endblock %}"))
|
||||||
(keys template)))
|
(keys template)))
|
||||||
(file-footer filename application)))))
|
(file-footer filename application)))))
|
||||||
(if (> *verbosity* 0) (*warn* "\tGenerated " filepath)))
|
(if
|
||||||
(catch Exception any
|
(pos? *verbosity*)
|
||||||
(let [report (str
|
(*warn* "\tGenerated " filepath))
|
||||||
"ERROR: Exception "
|
(str filepath))))))
|
||||||
(.getName (.getClass any))
|
|
||||||
(.getMessage any)
|
|
||||||
" while printing "
|
|
||||||
filepath)]
|
|
||||||
(try
|
|
||||||
(spit
|
|
||||||
filepath
|
|
||||||
(with-out-str
|
|
||||||
(*warn* (str "<!-- " report "-->"))
|
|
||||||
(p/pprint template)))
|
|
||||||
(catch Exception _ nil))
|
|
||||||
(*warn* report)
|
|
||||||
(throw any)))))
|
|
||||||
(str filepath)))
|
|
||||||
|
|
||||||
|
|
||||||
;; (def a (x/parse "../youyesyet/youyesyet.canonical.adl.xml"))
|
;; (def a (x/parse "../youyesyet/youyesyet.canonical.adl.xml"))
|
||||||
|
@ -948,7 +1044,8 @@
|
||||||
|
|
||||||
|
|
||||||
(defn to-selmer-templates
|
(defn to-selmer-templates
|
||||||
"Generate all [Selmer](https://github.com/yogthos/Selmer) templates implied by this ADL `application` spec."
|
"Generate all [Selmer](https://github.com/yogthos/Selmer) templates implied
|
||||||
|
by this ADL `application` spec."
|
||||||
[application]
|
[application]
|
||||||
(let
|
(let
|
||||||
[templates-map (reduce
|
[templates-map (reduce
|
||||||
|
@ -962,17 +1059,11 @@
|
||||||
#(if
|
#(if
|
||||||
(templates-map %)
|
(templates-map %)
|
||||||
(let [filename (str (name %) ".html")]
|
(let [filename (str (name %) ".html")]
|
||||||
(try
|
(do-or-warn
|
||||||
(write-template-file filename (templates-map %) application)
|
(write-template-file
|
||||||
(catch Exception any
|
filename
|
||||||
(*warn*
|
(templates-map %)
|
||||||
(str
|
application))))
|
||||||
"ERROR: Exception "
|
|
||||||
(.getName (.getClass any))
|
|
||||||
" "
|
|
||||||
(.getMessage any)
|
|
||||||
" while writing "
|
|
||||||
filename))))))
|
|
||||||
(keys templates-map)))))
|
(keys templates-map)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,141 +19,10 @@
|
||||||
(= a b)))
|
(= a b)))
|
||||||
|
|
||||||
(deftest entity-tests
|
(deftest entity-tests
|
||||||
(let [xml {:tag :entity,
|
(let [application {:tag :application,
|
||||||
:attrs {:name "address"},
|
:attrs {:version "0.1.1", :name "test-app"},
|
||||||
:content
|
:content
|
||||||
[{:tag :key,
|
[{:tag :entity,
|
||||||
:attrs nil,
|
|
||||||
:content
|
|
||||||
[{:tag :property,
|
|
||||||
:attrs
|
|
||||||
{:immutable "true",
|
|
||||||
:required "true",
|
|
||||||
:distinct "system",
|
|
||||||
:type "integer",
|
|
||||||
:name "id"},
|
|
||||||
:content
|
|
||||||
[{:tag :generator, :attrs {:action "native"}, :content nil}]}]}
|
|
||||||
{:tag :property,
|
|
||||||
:attrs
|
|
||||||
{:distinct "user", :size "128", :type "string", :name "street"},
|
|
||||||
:content nil}
|
|
||||||
{:tag :property,
|
|
||||||
:attrs {:size "64", :type "string", :name "town"},
|
|
||||||
:content nil}
|
|
||||||
{:tag :property,
|
|
||||||
:attrs
|
|
||||||
{:distinct "user", :size "12", :type "string", :name "postcode"},
|
|
||||||
:content nil}]}]
|
|
||||||
(testing "user distinct properties should provide the default ordering"
|
|
||||||
(let [expected
|
|
||||||
"ORDER BY address.street,
|
|
||||||
address.postcode,
|
|
||||||
address.id"
|
|
||||||
actual (order-by-clause xml)]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "keys name extraction"
|
|
||||||
(let [expected '("id")
|
|
||||||
actual (key-names xml)]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "primary key test"
|
|
||||||
(let [expected true
|
|
||||||
actual (has-primary-key? xml)]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "non-key properties test"
|
|
||||||
(let [expected true
|
|
||||||
actual (has-non-key-properties? xml)]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "insert query generation"
|
|
||||||
(let [expected "-- :name create-addres! :! :n
|
|
||||||
-- :doc creates a new addres record
|
|
||||||
INSERT INTO address (street,
|
|
||||||
town,
|
|
||||||
postcode)
|
|
||||||
VALUES (':street',
|
|
||||||
':town',
|
|
||||||
':postcode')
|
|
||||||
returning id\n\n"
|
|
||||||
actual (:query (first (vals (insert-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "insert query signature"
|
|
||||||
(let [expected ":! :n"
|
|
||||||
actual (:signature (first (vals (insert-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "update query generation"
|
|
||||||
(let [expected "-- :name update-addres! :! :n
|
|
||||||
-- :doc updates an existing addres record
|
|
||||||
UPDATE address
|
|
||||||
SET street = :street,
|
|
||||||
town = :town,
|
|
||||||
postcode = :postcode
|
|
||||||
WHERE address.id = :id\n\n"
|
|
||||||
actual (:query (first (vals (update-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "update query signature"
|
|
||||||
(let [expected ":! :n"
|
|
||||||
actual (:signature (first (vals (update-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "search query generation"
|
|
||||||
(let [expected "-- :name search-strings-addres :? :1
|
|
||||||
-- :doc selects existing address records having any string field matching `:pattern` by substring match
|
|
||||||
SELECT * FROM address
|
|
||||||
WHERE street LIKE '%:pattern%'
|
|
||||||
OR town LIKE '%:pattern%'
|
|
||||||
OR postcode LIKE '%:pattern%'
|
|
||||||
ORDER BY address.street,
|
|
||||||
address.postcode,
|
|
||||||
address.id
|
|
||||||
--~ (if (:offset params) \"OFFSET :offset \")
|
|
||||||
--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
|
|
||||||
actual (:query (first (vals (search-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "search query signature"
|
|
||||||
(let [expected ":? :1"
|
|
||||||
actual (:signature (first (vals (search-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "select query generation"
|
|
||||||
(let [expected "-- :name get-addres :? :1
|
|
||||||
-- :doc selects an existing addres record
|
|
||||||
SELECT * FROM address
|
|
||||||
WHERE address.id = :id\n\n"
|
|
||||||
actual (:query (first (vals (select-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "select query signature"
|
|
||||||
(let [expected ":? :1"
|
|
||||||
actual (:signature (first (vals (select-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "list query generation"
|
|
||||||
(let [expected "-- :name list-address :? :*
|
|
||||||
-- :doc lists all existing addres records
|
|
||||||
SELECT * FROM address
|
|
||||||
ORDER BY address.street,
|
|
||||||
address.postcode,
|
|
||||||
address.id
|
|
||||||
--~ (if (:offset params) \"OFFSET :offset \")
|
|
||||||
--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
|
|
||||||
actual (:query (first (vals (list-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "list query signature"
|
|
||||||
(let [expected ":? :*"
|
|
||||||
actual (:signature (first (vals (list-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "delete query generation"
|
|
||||||
(let [expected "-- :name delete-addres! :! :n
|
|
||||||
-- :doc updates an existing addres record
|
|
||||||
DELETE FROM address
|
|
||||||
WHERE address.id = :id\n\n"
|
|
||||||
actual (:query (first (vals (delete-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
(testing "delete query signature"
|
|
||||||
(let [expected ":! :n"
|
|
||||||
actual (:signature (first (vals (delete-query xml))))]
|
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
|
||||||
|
|
||||||
))
|
|
||||||
|
|
||||||
(deftest complex-key-tests
|
|
||||||
(let [xml {:tag :entity,
|
|
||||||
:attrs {:name "address"},
|
:attrs {:name "address"},
|
||||||
:content
|
:content
|
||||||
[{:tag :key,
|
[{:tag :key,
|
||||||
|
@ -187,20 +56,168 @@
|
||||||
{:tag :property,
|
{:tag :property,
|
||||||
:attrs {:size "64", :type "string", :name "town"},
|
:attrs {:size "64", :type "string", :name "town"},
|
||||||
:content nil}
|
:content nil}
|
||||||
]}]
|
]}]}
|
||||||
|
entity (child-with-tag application :entity)]
|
||||||
|
(testing "user distinct properties should provide the default ordering"
|
||||||
|
(let [expected
|
||||||
|
"ORDER BY address.street,
|
||||||
|
address.postcode,
|
||||||
|
address.id"
|
||||||
|
actual (order-by-clause entity)]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "keys name extraction"
|
||||||
|
(let [expected #{"id"}
|
||||||
|
actual (key-names entity)]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "primary key test"
|
||||||
|
(let [expected true
|
||||||
|
actual (has-primary-key? entity)]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "non-key properties test"
|
||||||
|
(let [expected true
|
||||||
|
actual (has-non-key-properties? entity)]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "insert query generation"
|
||||||
|
(let [expected "-- :name create-address! :! :n
|
||||||
|
-- :doc creates a new address record
|
||||||
|
INSERT INTO address (street,
|
||||||
|
town,
|
||||||
|
postcode)
|
||||||
|
VALUES (':street',
|
||||||
|
':town',
|
||||||
|
':postcode')
|
||||||
|
returning id\n\n"
|
||||||
|
actual (:query (first (vals (insert-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "insert query signature"
|
||||||
|
(let [expected ":! :n"
|
||||||
|
actual (:signature (first (vals (insert-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "update query generation"
|
||||||
|
(let [expected "-- :name update-address! :! :n
|
||||||
|
-- :doc updates an existing address record
|
||||||
|
UPDATE address
|
||||||
|
SET street = :street,
|
||||||
|
town = :town,
|
||||||
|
postcode = :postcode
|
||||||
|
WHERE address.id = :id\n\n"
|
||||||
|
actual (:query (first (vals (update-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "update query signature"
|
||||||
|
(let [expected ":! :n"
|
||||||
|
actual (:signature (first (vals (update-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "search query generation"
|
||||||
|
(let [expected "-- :name search-strings-addres :? :1
|
||||||
|
-- :doc selects existing address records having any string field matching `:pattern` by substring match
|
||||||
|
SELECT * FROM address
|
||||||
|
WHERE street LIKE '%:pattern%'
|
||||||
|
OR town LIKE '%:pattern%'
|
||||||
|
OR postcode LIKE '%:pattern%'
|
||||||
|
ORDER BY address.street,
|
||||||
|
address.postcode,
|
||||||
|
address.id
|
||||||
|
--~ (if (:offset params) \"OFFSET :offset \")
|
||||||
|
--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
|
||||||
|
actual (:query (first (vals (search-query entity application))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "search query signature"
|
||||||
|
(let [expected ":? :1"
|
||||||
|
actual (:signature (first (vals (search-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "select query generation"
|
||||||
|
(let [expected "-- :name get-addres :? :1
|
||||||
|
-- :doc selects an existing addres record
|
||||||
|
SELECT * FROM address
|
||||||
|
WHERE address.id = :id\n\n"
|
||||||
|
actual (:query (first (vals (select-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "select query signature"
|
||||||
|
(let [expected ":? :1"
|
||||||
|
actual (:signature (first (vals (select-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "list query generation"
|
||||||
|
(let [expected "-- :name list-address :? :*
|
||||||
|
-- :doc lists all existing addres records
|
||||||
|
SELECT * FROM address
|
||||||
|
ORDER BY address.street,
|
||||||
|
address.postcode,
|
||||||
|
address.id
|
||||||
|
--~ (if (:offset params) \"OFFSET :offset \")
|
||||||
|
--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
|
||||||
|
actual (:query (first (vals (list-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "list query signature"
|
||||||
|
(let [expected ":? :*"
|
||||||
|
actual (:signature (first (vals (list-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "delete query generation"
|
||||||
|
(let [expected "-- :name delete-addres! :! :n
|
||||||
|
-- :doc updates an existing addres record
|
||||||
|
DELETE FROM address
|
||||||
|
WHERE address.id = :id\n\n"
|
||||||
|
actual (:query (first (vals (delete-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
(testing "delete query signature"
|
||||||
|
(let [expected ":! :n"
|
||||||
|
actual (:signature (first (vals (delete-query entity))))]
|
||||||
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
|
|
||||||
|
))
|
||||||
|
|
||||||
|
(deftest complex-key-tests
|
||||||
|
(let [application {:tag :application,
|
||||||
|
:attrs {:version "0.1.1", :name "test-app"},
|
||||||
|
:content
|
||||||
|
[{:tag :entity,
|
||||||
|
:attrs {:name "address"},
|
||||||
|
:content
|
||||||
|
[{:tag :key,
|
||||||
|
:attrs nil,
|
||||||
|
:content
|
||||||
|
[{:tag :property,
|
||||||
|
:attrs
|
||||||
|
{:immutable "true",
|
||||||
|
:required "true",
|
||||||
|
:distinct "system",
|
||||||
|
:type "integer",
|
||||||
|
:name "id"},
|
||||||
|
:content
|
||||||
|
[{:tag :generator, :attrs {:action "native"}, :content nil}]}
|
||||||
|
{:tag :property,
|
||||||
|
:attrs
|
||||||
|
{:immutable "true",
|
||||||
|
:required "true",
|
||||||
|
:distinct "all",
|
||||||
|
:generator "assigned"
|
||||||
|
:type "string",
|
||||||
|
:size "12"
|
||||||
|
:name "postcode"},
|
||||||
|
:content
|
||||||
|
[{:tag :generator, :attrs {:action "native"}, :content nil}]}
|
||||||
|
]}
|
||||||
|
{:tag :property,
|
||||||
|
:attrs
|
||||||
|
{:distinct "user", :size "128", :type "string", :name "street"},
|
||||||
|
:content nil}
|
||||||
|
{:tag :property,
|
||||||
|
:attrs {:size "64", :type "string", :name "town"},
|
||||||
|
:content nil}
|
||||||
|
]}]}
|
||||||
|
entity (child-with-tag application :entity)]
|
||||||
(testing "user distinct properties should provide the default ordering"
|
(testing "user distinct properties should provide the default ordering"
|
||||||
(let [expected "ORDER BY address.street,
|
(let [expected "ORDER BY address.street,
|
||||||
address.postcode,
|
address.postcode,
|
||||||
address.id"
|
address.id"
|
||||||
actual (order-by-clause xml)]
|
actual (order-by-clause entity)]
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
(testing "keys name extraction"
|
(testing "keys name extraction"
|
||||||
(let [expected '("id" "postcode")
|
(let [expected #{"id" "postcode"}
|
||||||
actual (key-names xml)]
|
actual (key-names entity)]
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
(testing "insert query generation - compound key, non system generated field in key"
|
(testing "insert query generation - compound key, non system generated field in key"
|
||||||
(let [expected "-- :name create-addres! :! :n
|
(let [expected "-- :name create-address! :! :n
|
||||||
-- :doc creates a new addres record
|
-- :doc creates a new address record
|
||||||
INSERT INTO address (street,
|
INSERT INTO address (street,
|
||||||
town,
|
town,
|
||||||
postcode)
|
postcode)
|
||||||
|
@ -209,17 +226,17 @@
|
||||||
':postcode')
|
':postcode')
|
||||||
returning id,
|
returning id,
|
||||||
postcode\n\n"
|
postcode\n\n"
|
||||||
actual (:query (first (vals (insert-query xml))))]
|
actual (:query (first (vals (insert-query entity))))]
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
(testing "update query generation - compound key"
|
(testing "update query generation - compound key"
|
||||||
(let [expected "-- :name update-addres! :! :n
|
(let [expected "-- :name update-address! :! :n
|
||||||
-- :doc updates an existing addres record
|
-- :doc updates an existing address record
|
||||||
UPDATE address
|
UPDATE address
|
||||||
SET street = :street,
|
SET street = :street,
|
||||||
town = :town
|
town = :town
|
||||||
WHERE address.id = :id
|
WHERE address.id = :id
|
||||||
AND address.postcode = ':postcode'\n\n"
|
AND address.postcode = ':postcode'\n\n"
|
||||||
actual (:query (first (vals (update-query xml))))]
|
actual (:query (first (vals (update-query entity))))]
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
(testing "search query generation - user-distinct field in key"
|
(testing "search query generation - user-distinct field in key"
|
||||||
(let [expected "-- :name search-strings-addres :? :1
|
(let [expected "-- :name search-strings-addres :? :1
|
||||||
|
@ -233,7 +250,7 @@
|
||||||
address.id
|
address.id
|
||||||
--~ (if (:offset params) \"OFFSET :offset \")
|
--~ (if (:offset params) \"OFFSET :offset \")
|
||||||
--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
|
--~ (if (:limit params) \"LIMIT :limit\" \"LIMIT 100\")\n\n"
|
||||||
actual (:query (first (vals (search-query xml))))]
|
actual (:query (first (vals (search-query entity application))))]
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))
|
(is (string-equal-ignore-whitespace? actual expected))))
|
||||||
(testing "delete query generation - compound key"
|
(testing "delete query generation - compound key"
|
||||||
(let [expected "-- :name delete-addres! :! :n
|
(let [expected "-- :name delete-addres! :! :n
|
||||||
|
@ -241,6 +258,6 @@
|
||||||
DELETE FROM address
|
DELETE FROM address
|
||||||
WHERE address.id = :id
|
WHERE address.id = :id
|
||||||
AND address.postcode = ':postcode'\n\n"
|
AND address.postcode = ':postcode'\n\n"
|
||||||
actual (:query (first (vals (delete-query xml))))]
|
actual (:query (first (vals (delete-query entity))))]
|
||||||
(is (string-equal-ignore-whitespace? actual expected))))))
|
(is (string-equal-ignore-whitespace? actual expected))))))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue