From a8dd292699f00f78611efe8cb198e03ee09dbf15 Mon Sep 17 00:00:00 2001 From: Soukev Date: Tue, 26 Aug 2025 18:51:05 +0200 Subject: [PATCH] Initial template --- .gitignore | 30 ++++++++ README.md | 2 + build.clj | 73 +++++++++++++++++++ deps.edn | 13 ++++ .../clj/new/http_server_template/build.clj | 43 +++++++++++ .../clj/new/http_server_template/core.clj | 26 +++++++ .../clj/new/http_server_template/deps.edn | 19 +++++ .../clj/new/http_server_template/gitignore | 30 ++++++++ .../clj/new/http_server_template/handler.clj | 26 +++++++ .../new/http_server_template/public/main.css | 7 ++ .../clj/new/http_server_template/response.clj | 22 ++++++ .../http_server_template/response_test.clj | 12 +++ .../clj/new/http_server_template/test.clj | 7 ++ .../new/http_server_template/views/base.clj | 8 ++ .../views/components/page.clj | 16 ++++ src/clj/new/http_server_template.clj | 22 ++++++ 16 files changed, 356 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.clj create mode 100644 deps.edn create mode 100644 resources/clj/new/http_server_template/build.clj create mode 100644 resources/clj/new/http_server_template/core.clj create mode 100644 resources/clj/new/http_server_template/deps.edn create mode 100644 resources/clj/new/http_server_template/gitignore create mode 100644 resources/clj/new/http_server_template/handler.clj create mode 100644 resources/clj/new/http_server_template/public/main.css create mode 100644 resources/clj/new/http_server_template/response.clj create mode 100644 resources/clj/new/http_server_template/response_test.clj create mode 100644 resources/clj/new/http_server_template/test.clj create mode 100644 resources/clj/new/http_server_template/views/base.clj create mode 100644 resources/clj/new/http_server_template/views/components/page.clj create mode 100644 src/clj/new/http_server_template.clj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa1f1da --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +.calva/output-window/ +.calva/repl.calva-repl +.classpath +.clj-kondo/.cache +.cpcache +.DS_Store +.eastwood +.factorypath +.hg/ +.hgignore +.java-version +.lein-* +.lsp/.cache +.lsp/sqlite.db +.nrepl-history +.nrepl-port +.portal +.project +.rebel_readline_history +.settings +.socket-repl-port +.sw* +.vscode +*.class +*.jar +*.swp +*~ +/checkouts +/classes +/target diff --git a/README.md b/README.md new file mode 100644 index 0000000..2a889aa --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# http-server-template + diff --git a/build.clj b/build.clj new file mode 100644 index 0000000..6d3ab52 --- /dev/null +++ b/build.clj @@ -0,0 +1,73 @@ +(ns build + (:refer-clojure :exclude [test]) + (:require [clojure.tools.deps :as t] + [clojure.tools.build.api :as b] + [deps-deploy.deps-deploy :as dd])) + +(def lib 'net.clojars.soukev/clj-template.http-server-template) +(def version "0.1.0-SNAPSHOT") +#_; alternatively, use MAJOR.MINOR.COMMITS: +(def version (format "1.0.%s" (b/git-count-revs nil))) +(def class-dir "target/classes") + +(defn test "Run all the tests." [opts] + (println "\nRunning tests...") + (let [basis (b/create-basis {:aliases [:test]}) + combined (t/combine-aliases basis [:test]) + cmds (b/java-command + {:basis basis + :java-opts (:jvm-opts combined) + :main 'clojure.main + :main-args ["-m" "cognitect.test-runner"]}) + {:keys [exit]} (b/process cmds)] + (when-not (zero? exit) (throw (ex-info "Tests failed" {})))) + opts) + +(defn- pom-template [version] + [[:description "FIXME: my new template."] + [:url "https://github.com/soukev/http-server-template"] + [:licenses + [:license + [:name "Eclipse Public License"] + [:url "http://www.eclipse.org/legal/epl-v10.html"]]] + [:developers + [:developer + [:name "Soukev"]]] + [:scm + [:url "https://github.com/soukev/http-server-template"] + [:connection "scm:git:https://github.com/soukev/http-server-template.git"] + [:developerConnection "scm:git:ssh:git@github.com:soukev/http-server-template.git"] + [:tag (str "v" version)]]]) + +(defn- jar-opts [opts] + (assoc opts + :lib lib :version version + :jar-file (format "target/%s-%s.jar" lib version) + :basis (b/create-basis {}) + :class-dir class-dir + :target "target" + :src-dirs ["src"] + :pom-data (pom-template version))) + +(defn ci "Run the CI pipeline of tests (and build the JAR)." [opts] + (test opts) + (b/delete {:path "target"}) + (let [opts (jar-opts opts)] + (println "\nWriting pom.xml...") + (b/write-pom opts) + (println "\nCopying source...") + (b/copy-dir {:src-dirs ["resources" "src"] :target-dir class-dir}) + (println "\nBuilding JAR...") + (b/jar opts)) + opts) + +(defn install "Install the JAR locally." [opts] + (let [opts (jar-opts opts)] + (b/install opts)) + opts) + +(defn deploy "Deploy the JAR to Clojars." [opts] + (let [{:keys [jar-file] :as opts} (jar-opts opts)] + (dd/deploy {:installer :remote :artifact (b/resolve-path jar-file) + :pom-file (b/pom-path (select-keys opts [:lib :class-dir]))})) + opts) diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000..5634ab3 --- /dev/null +++ b/deps.edn @@ -0,0 +1,13 @@ +{:paths ["src" "resources"] + :deps {org.clojure/clojure {:mvn/version "1.12.0"} + com.github.seancorfield/clj-new {:mvn/version "1.3.415"}} + :aliases + {:clj-new {:exec-fn clj-new/create + :exec-args {:template "http-server-template"}} + :build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.5"} + slipset/deps-deploy {:mvn/version "0.2.2"}} + :ns-default build} + :test {:extra-paths ["test"] + :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} + io.github.cognitect-labs/test-runner + {:git/tag "v0.5.1" :git/sha "dfb30dd"}}}}} diff --git a/resources/clj/new/http_server_template/build.clj b/resources/clj/new/http_server_template/build.clj new file mode 100644 index 0000000..a1c22e0 --- /dev/null +++ b/resources/clj/new/http_server_template/build.clj @@ -0,0 +1,43 @@ +(ns build + (:refer-clojure :exclude [test]) + (:require [clojure.tools.deps :as t] + [clojure.tools.build.api :as b])) + +(def lib '{{group}}/{{artifact}}) +(def version "{{version}}") +(def main '{{namespace}}) +(def class-dir "target/classes") + +(defn test "Run all the tests." [opts] + (println "\nRunning tests...") + (let [basis (b/create-basis {:aliases [:test]}) + combined (t/combine-aliases basis [:test]) + cmds (b/java-command + {:basis basis + :java-opts (:jvm-opts combined) + :main 'clojure.main + :main-args ["-m" "cognitect.test-runner"]}) + {:keys [exit]} (b/process cmds)] + (when-not (zero? exit) (throw (ex-info "Tests failed" {})))) + opts) + +(defn- uber-opts [opts] + (assoc opts + :lib lib :main main + :uber-file (format "target/%s-%s.jar" lib version) + :basis (b/create-basis {}) + :class-dir class-dir + :src-dirs ["src"] + :ns-compile [main])) + +(defn ci "Run the CI pipeline of tests (and build the uberjar)." [opts] + (test opts) + (b/delete {:path "target"}) + (let [opts (uber-opts opts)] + (println "\nCopying source...") + (b/copy-dir {:src-dirs ["resources" "src"] :target-dir class-dir}) + (println (str "\nCompiling " main "...")) + (b/compile-clj opts) + (println "\nBuilding JAR...") + (b/uber opts)) + opts) diff --git a/resources/clj/new/http_server_template/core.clj b/resources/clj/new/http_server_template/core.clj new file mode 100644 index 0000000..3365572 --- /dev/null +++ b/resources/clj/new/http_server_template/core.clj @@ -0,0 +1,26 @@ +(ns {{namespace}} + (:require [ring.adapter.jetty :as jetty] + [{{namespace}}.handler :as handler]) + (:gen-class)) + +(defonce server (atom nil)) + +(defn start-server [] + (reset! server + (jetty/run-jetty (fn [req] (handler/app req)) + {:port 3333 + :join? false}))) + +(defn stop-server [] + (when-some [s @server] + (.stop s) + (reset! server nil))) + +(defn start-dev-server [] + (reset! server + (jetty/run-jetty #'handler/reloadable-app + {:port 3333 + :join? false}))) + +(defn -main [] + (start-server)) diff --git a/resources/clj/new/http_server_template/deps.edn b/resources/clj/new/http_server_template/deps.edn new file mode 100644 index 0000000..e475213 --- /dev/null +++ b/resources/clj/new/http_server_template/deps.edn @@ -0,0 +1,19 @@ +{:paths ["src" "resources"] + :deps {org.clojure/clojure {:mvn/version "1.12.0"} + ring/ring-core {:mvn/version "1.12.2"} + ring/ring-jetty-adapter {:mvn/version "1.12.2"} + ring/ring-devel {:mvn/version "1.12.2"} + compojure/compojure {:mvn/version "1.7.1"} + hiccup/hiccup {:mvn/version "2.0.0-RC3"}} + :aliases + {:run-m {:main-opts ["-m" "{{namespace}}"]} + :run-x {:ns-default {{namespace}} + :exec-fn start-server + :exec-args {}} + :build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.5"} + slipset/deps-deploy {:mvn/version "0.2.2"}} + :ns-default build} + :test {:extra-paths ["test"] + :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"} + io.github.cognitect-labs/test-runner + {:git/tag "v0.5.1" :git/sha "dfb30dd"}}}}} diff --git a/resources/clj/new/http_server_template/gitignore b/resources/clj/new/http_server_template/gitignore new file mode 100644 index 0000000..aa1f1da --- /dev/null +++ b/resources/clj/new/http_server_template/gitignore @@ -0,0 +1,30 @@ +.calva/output-window/ +.calva/repl.calva-repl +.classpath +.clj-kondo/.cache +.cpcache +.DS_Store +.eastwood +.factorypath +.hg/ +.hgignore +.java-version +.lein-* +.lsp/.cache +.lsp/sqlite.db +.nrepl-history +.nrepl-port +.portal +.project +.rebel_readline_history +.settings +.socket-repl-port +.sw* +.vscode +*.class +*.jar +*.swp +*~ +/checkouts +/classes +/target diff --git a/resources/clj/new/http_server_template/handler.clj b/resources/clj/new/http_server_template/handler.clj new file mode 100644 index 0000000..69fef6b --- /dev/null +++ b/resources/clj/new/http_server_template/handler.clj @@ -0,0 +1,26 @@ +(ns {{namespace}}.handler + (:require [compojure.core :as comp] + [compojure.route :as route] + [ring.middleware.params :refer [wrap-params]] + [ring.middleware.reload :refer [wrap-reload]] + [ring.middleware.resource :refer [wrap-resource]] + [ring.middleware.session :refer [wrap-session]] + [ring.middleware.cookies :refer [wrap-cookies]] + [{{namespace}}.response :as r] + [{{namespace}}.views.base :as v])) + +(def routes + (comp/routes + (comp/GET "/" [:as req] (-> (r/response :ok :html "

Hello World!

") + (assoc :session (req :session)))) + (route/not-found (r/response :not-found :plain "Not found.")))) + +(def app (-> routes + (wrap-resource "public") + wrap-cookies + wrap-session + wrap-params)) + +(def reloadable-app + (-> app + wrap-reload)) diff --git a/resources/clj/new/http_server_template/public/main.css b/resources/clj/new/http_server_template/public/main.css new file mode 100644 index 0000000..83f4fd3 --- /dev/null +++ b/resources/clj/new/http_server_template/public/main.css @@ -0,0 +1,7 @@ +:root { + --bg-color: #EBECF0; +} + +body { + font-family: "Bricolage Grotesque" +} diff --git a/resources/clj/new/http_server_template/response.clj b/resources/clj/new/http_server_template/response.clj new file mode 100644 index 0000000..2f7d7a3 --- /dev/null +++ b/resources/clj/new/http_server_template/response.clj @@ -0,0 +1,22 @@ +(ns {{namespace}}.response) + +(defn- to-status + [status] + (case status + :ok 200 + :not-found 404 + :internal-error 500 + 500)) + +(defn- to-content-type + [content-type] + (case content-type + :html "text/html" + :plain "text/plain" + "text/plain")) + +(defn response + [status content-type body] + {:status (to-status status) + :headers {"Content-Type" (to-content-type content-type)} + :body body}) diff --git a/resources/clj/new/http_server_template/response_test.clj b/resources/clj/new/http_server_template/response_test.clj new file mode 100644 index 0000000..3554f42 --- /dev/null +++ b/resources/clj/new/http_server_template/response_test.clj @@ -0,0 +1,12 @@ +(ns {{namespace}}.response-test + (:require [{{namespace}}.response :as sut] + [clojure.test :as t])) + +(t/deftest response-invalid-status-returns-500-test + (t/testing "Invalid status value returns 500 response." + (t/is (= 500 (:status (sut/response :non-existent-status :plain "")))))) + +(t/deftest response-invalid-content-type-returns-plain-test + (t/testing "Invalid content type returns text/plain response." + (t/is (= {"Content-Type" "text/plain"} (-> (sut/response :ok :non-existent-type "") + :headers))))) diff --git a/resources/clj/new/http_server_template/test.clj b/resources/clj/new/http_server_template/test.clj new file mode 100644 index 0000000..7d41914 --- /dev/null +++ b/resources/clj/new/http_server_template/test.clj @@ -0,0 +1,7 @@ +(ns {{namespace}}-test + (:require [clojure.test :refer :all] + [{{namespace}} :refer :all])) + +(deftest a-test + (testing "FIXME, I fail." + (is (= 0 1)))) diff --git a/resources/clj/new/http_server_template/views/base.clj b/resources/clj/new/http_server_template/views/base.clj new file mode 100644 index 0000000..2d9feb0 --- /dev/null +++ b/resources/clj/new/http_server_template/views/base.clj @@ -0,0 +1,8 @@ +(ns {{namespace}}.views.base + (:require [{{namespace}}.views.components.page :as page])) + +(defn base + [req] + (page/page + "Hello world!" + req)) diff --git a/resources/clj/new/http_server_template/views/components/page.clj b/resources/clj/new/http_server_template/views/components/page.clj new file mode 100644 index 0000000..e8bf042 --- /dev/null +++ b/resources/clj/new/http_server_template/views/components/page.clj @@ -0,0 +1,16 @@ +(ns {{namespace}}.views.components.page + (:require + [hiccup.page :refer [html5 include-css include-js]])) + +(defn page + [title & body] + (html5 + [:head + [:meta {:charset "UTF-8"}] + [:meta {:content "width=device-width, initial-scale=1", :name "viewport"}] + (include-css "/css/main.css") + (include-css "https://fonts.googleapis.com/css?family=Bricolage+Grotesque") + (include-js "https://unpkg.com/htmx.org@2.0.4") + [:title title]] + [:body + body])) diff --git a/src/clj/new/http_server_template.clj b/src/clj/new/http_server_template.clj new file mode 100644 index 0000000..58525db --- /dev/null +++ b/src/clj/new/http_server_template.clj @@ -0,0 +1,22 @@ +(ns clj.new.http-server-template + (:require [clj.new.templates :refer [renderer project-data ->files]])) + +(defn http-server-template + "Http server template configuration." + [name] + (let [render (renderer "http-server-template") + data (project-data name)] + (println "Generating fresh 'clj new' http-server-template project.") + (->files data + ["deps.edn" (render "deps.edn" data)] + ["build.clj" (render "build.clj" data)] + [".gitignore" (render "gitignore" data)] + ["src/{{nested-dirs}}.clj" (render "core.clj" data)] + ["src/{{nested-dirs}}/handler.clj" (render "handler.clj" data)] + ["src/{{nested-dirs}}/response.clj" (render "response.clj" data)] + ["src/{{nested-dirs}}/views/base.clj" (render "views/base.clj" data)] + ["src/{{nested-dirs}}/views/components/page.clj" (render "views/components/page.clj" data)] + ["test/{{nested-dirs}}_test.clj" (render "test.clj" data)] + ["test/{{nested-dirs}}/response_test.clj" (render "response_test.clj" data)] + ["resources/public/main.css" (render "public/main.css")] + )))