diff --git a/lein-template/src/duct/duct_template.clj b/lein-template/src/duct/duct_template.clj new file mode 100644 index 0000000..278f2e9 --- /dev/null +++ b/lein-template/src/duct/duct_template.clj @@ -0,0 +1,71 @@ +(ns duct.duct-template + (:require [clojure.java.io :as io])) + +(defn resource [name] + (io/resource (str "leiningen/new/duct/" name))) + +(def ^:private web-directories + ["resources/{{dirs}}/public" + "src/{{dirs}}/handler" + "test/{{dirs}}/handler"]) + +(defn example-profile [{:keys [profiles]}] + {:vars {:example? true} + :templates + (cond + (profiles :site) + {"src/{{dirs}}/handler/example.clj" (resource "example/handler.clj") + "test/{{dirs}}/handler/example_test.clj" (resource "example/handler_test.clj") + "resources/{{dirs}}/handler/example/example.html" (resource "example/example.html")} + + (profiles :api) + {"src/{{dirs}}/handler/example.clj" (resource "example/handler.clj") + "test/{{dirs}}/handler/example_test.clj" (resource "example/handler_test.clj")} + + :else + {"src/{{dirs}}/service/example.clj" (resource "example/service.clj") + "test/{{dirs}}/service/example_test.clj" (resource "example/service_test.clj")})}) + +(defn api-profile [_] + {:deps '[[duct/module.web "0.7.0"]] + :dev-deps '[[kerodon "0.9.0"]] + :vars {:api? true} + :dirs web-directories}) + +(defn site-profile [_] + {:deps '[[duct/module.web "0.7.0"]] + :dev-deps '[[kerodon "0.9.0"]] + :vars {:site? true} + :dirs web-directories}) + +(defn cljs-profile [_] + {:deps '[[duct/module.web "0.7.0"] + [duct/module.cljs "0.4.0"]] + :dev-deps '[[kerodon "0.9.0"]] + :vars {:cljs? true} + :dirs web-directories + :templates {"src/{{dirs}}/client.cljs" (resource "cljs/client.cljs")}}) + +(defn heroku-profile [{:keys [project-name]}] + {:vars {:uberjar-name (str project-name "-standalone.jar")} + :templates {"Procfile" (resource "heroku/Procfile")}}) + +(defn postgres-profile [_] + {:deps '[[duct/module.sql "0.5.0"] + [org.postgresql/postgresql "42.2.5"]] + :vars {:jdbc? true + :postgres? true + :dev-database "jdbc:postgresql://localhost/postgres"}}) + +(defn sqlite-profile [_] + {:deps '[[duct/module.sql "0.5.0"] + [org.xerial/sqlite-jdbc "3.25.2"]] + :dirs ["db"] + :vars {:jdbc? true + :sqlite? true + :dev-database "jdbc:sqlite:db/dev.sqlite"}}) + +(defn ataraxy-profile [_] + {:deps '[[duct/module.ataraxy "0.3.0"]] + :vars {:ataraxy? true, :web? true} + :dirs web-directories}) diff --git a/lein-template/src/leiningen/new/duct.clj b/lein-template/src/leiningen/new/duct.clj index b82e874..a7bc570 100644 --- a/lein-template/src/leiningen/new/duct.clj +++ b/lein-template/src/leiningen/new/duct.clj @@ -1,110 +1,84 @@ (ns leiningen.new.duct (:require [clojure.java.io :as io] [leiningen.core.main :as main] - [leiningen.new.templates :refer [renderer year project-name - ->files sanitize-ns name-to-path]])) - -(def render (renderer "duct")) + [leiningen.new.templates :as templates])) (defn resource [name] - (io/input-stream (io/resource (str "leiningen/new/duct/" name)))) - -(defmulti profile-data (fn [module name] module)) -(defmulti profile-files (fn [module data] module)) - -(defmethod profile-data :default [_ _] {}) -(defmethod profile-files :default [_ _] []) - -(defmethod profile-data :base [_ name] - (let [main-ns (sanitize-ns name)] - {:raw-name name - :name (project-name name) - :namespace main-ns - :dirs (name-to-path main-ns) - :year (year) - :web-module :duct.module/web})) - -(defmethod profile-files :base [_ data] - [["project.clj" (render "base/project.clj" data)] - ["README.md" (render "base/README.md" data)] - [".gitignore" (render "base/gitignore" data)] - ["dev/src/user.clj" (render "base/user.clj" data)] - ["dev/src/dev.clj" (render "base/dev.clj" data)] - ["dev/resources/dev.edn" (render "base/dev.edn" data)] - ["resources/{{dirs}}/config.edn" (render "base/config.edn" data)] - ["src/{{dirs}}/main.clj" (render "base/main.clj" data)] - ["src/duct_hierarchy.edn" (render "base/duct_hierarchy.edn" data)] - "test/{{dirs}}"]) - -(def ^:private web-directories - ["resources/{{dirs}}/public" - "src/{{dirs}}/handler" - "test/{{dirs}}/handler"]) - -(defmethod profile-data :example [_ _] - {:example? true}) - -(defmethod profile-files :example [_ data] - (if (:web? data) - (concat - [["src/{{dirs}}/handler/example.clj" (render "example/handler.clj" data)] - ["test/{{dirs}}/handler/example_test.clj" (render "example/handler_test.clj" data)]] - (if (:site? data) - [["resources/{{dirs}}/handler/example/example.html" - (render "example/example.html" data)]])) - [["src/{{dirs}}/service/example.clj" (render "example/service.clj" data)] - ["test/{{dirs}}/service/example_test.clj" (render "example/service_test.clj" data)]])) - -(defmethod profile-data :api [_ _] - {:api? true, :web? true}) - -(defmethod profile-files :api [_ _] - web-directories) - -(defmethod profile-data :site [_ _] - {:site? true, :web? true}) - -(defmethod profile-files :site [_ data] - web-directories) - -(defmethod profile-data :cljs [_ _] - {:cljs? true, :site? true, :web? true}) - -(defmethod profile-files :cljs [_ data] - (conj web-directories - ["src/{{dirs}}/client.cljs" (render "cljs/client.cljs" data)])) - -(defmethod profile-data :heroku [_ name] - {:heroku? true - :uberjar-name (str (project-name name) "-standalone.jar")}) - -(defmethod profile-files :heroku [_ data] - [["Procfile" (render "heroku/Procfile" data)]]) - -(defmethod profile-data :postgres [_ name] - {:jdbc? true - :postgres? true - :dev-database "jdbc:postgresql://localhost/postgres"}) - -(defmethod profile-files :postgres [_ name] []) - -(defmethod profile-data :sqlite [_ _] - {:jdbc? true - :sqlite? true - :dev-database "jdbc:sqlite:db/dev.sqlite"}) - -(defmethod profile-files :sqlite [_ _] ["db"]) - -(defmethod profile-data :ataraxy [_ _] - {:ataraxy? true, :web? true}) - -(defmethod profile-files :ataraxy [_ _] - web-directories) - -(defn profiles [hints] + (io/resource (str "leiningen/new/duct/" name))) + +(defn project-data [raw-name profiles] + (let [main-ns (templates/sanitize-ns raw-name)] + {:raw-name raw-name + :project-ns main-ns + :project-name (templates/project-name raw-name) + :project-path (templates/name-to-path main-ns) + :profiles (set profiles)})) + +(defn base-profile [{:keys [project-name project-ns project-path raw-name]}] + {:vars + {:raw-name raw-name + :name project-name + :namespace project-ns + :dirs project-path + :year (templates/year)} + :dirs + ["test/{{dirs}}"] + :templates + {"project.clj" (resource "base/project.clj") + "README.md" (resource "base/README.md") + ".gitignore" (resource "base/gitignore") + "dev/src/user.clj" (resource "base/user.clj") + "dev/src/dev.clj" (resource "base/dev.clj") + "dev/resources/dev.edn" (resource "base/dev.edn") + "resources/{{dirs}}/config.edn" (resource "base/config.edn") + "src/{{dirs}}/main.clj" (resource "base/main.clj") + "src/duct_hierarchy.edn" (resource "base/duct_hierarchy.edn")}}) + +(defn profile-names [hints] (for [hint hints :when (re-matches #"\+[A-Za-z0-9-]+" hint)] (keyword (subs hint 1)))) +(defn profile-function-symbol [profile-kw] + (let [ns (or (namespace profile-kw) "duct")] + (symbol (str ns ".duct-template") + (str (name profile-kw) "-profile")))) + +(defn profile-function [profile-kw] + (let [sym (profile-function-symbol profile-kw)] + (require (symbol (namespace sym))) + (var-get (resolve sym)))) + +(defn merge-deps [a b] + (-> {} (into a) (into b) vec)) + +(defn merge-profiles [a b] + {:vars (merge (:vars a) (:vars b)) + :dirs (into (set (:dirs a)) (:dirs b)) + :deps (merge-deps (:deps a) (:deps b)) + :dev-deps (merge-deps (:dev-deps a) (:dev-deps b)) + :templates (into (:templates a) (:templates b))}) + +(defn project-template [name hints] + (let [profiles (profile-names hints) + data (project-data name profiles)] + (->> profiles + (map profile-function) + (map #(% data)) + (reduce merge-profiles (base-profile data))))) + +(defn render-resource [data template] + (templates/render-text (slurp template) data)) + +(defn render-templates [data templates] + (->> templates + (sort-by key) + (map (fn [[path temp]] [path (render-resource data temp)])))) + +(defn generate-project [{:keys [vars templates] :as profile}] + (let [data (merge vars (select-keys profile [:deps :dev-deps])) + files (render-templates data templates)] + (apply templates/->files data files))) + (defn duct "Create a new Duct web application. @@ -121,8 +95,5 @@ Accepts the following profile hints: (when (.startsWith name "+") (main/abort "Failed to create project: no project name specified.")) (main/info (str "Generating a new Duct project named " name "...")) - (let [mods (cons :base (profiles hints)) - data (reduce into {} (map #(profile-data % name) mods)) - files (reduce into [] (map #(profile-files % data) mods))] - (apply ->files data files)) + (generate-project (project-template name hints)) (main/info "Run 'lein duct setup' in the project directory to create local config files."))