From 9cc0def4d53bc41a7c8ebab18a76ef637edcbb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Madeleine=20Sydney=20=C5=9Alaga?= Date: Tue, 24 Mar 2026 17:49:50 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20html=20=EC=BA=90=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/deertopia/doerg/default-config.edn | 6 +- .../resources/net/deertopia/doerg/favicon.ico | Bin 0 -> 67646 bytes .../net/deertopia/doerg/public/Temml-Plex.css | 1 + .../net/deertopia/doerg/public/deerstar.css | 1 + .../net/deertopia/doerg/public/tuftesque.css | 1 + doerg/src/net/deertopia/doerg/config.clj | 42 +++++---- doerg/src/net/deertopia/doerg/html.clj | 2 +- doerg/src/net/deertopia/doerg/render.clj | 2 +- doerg/src/net/deertopia/doerg/repl.clj | 22 ++--- publisher/.dir-locals.el | 21 ++--- publisher/deps.edn | 3 +- publisher/dev/user.clj | 24 ++++++ .../deertopia/publisher/default-config.edn | 6 +- .../net/deertopia/publisher/cached_file.clj | 1 + .../src/net/deertopia/publisher/config.clj | 12 +-- .../src/net/deertopia/publisher/roam.clj | 35 ++++---- .../src/net/deertopia/publisher/server.clj | 80 +++++++++++++++--- .../test/net/deertopia/publisher/roam-test.db | Bin 0 -> 53248 bytes .../roam-test/20260325083230-awesome_file.org | 7 ++ .../publisher/roam-test/fake-homepage.org | 6 ++ .../net/deertopia/publisher/server_test.clj | 75 ++++++++++++---- 21 files changed, 247 insertions(+), 100 deletions(-) create mode 100644 doerg/resources/net/deertopia/doerg/favicon.ico create mode 120000 doerg/resources/net/deertopia/doerg/public/Temml-Plex.css create mode 120000 doerg/resources/net/deertopia/doerg/public/deerstar.css create mode 120000 doerg/resources/net/deertopia/doerg/public/tuftesque.css create mode 100644 publisher/dev/user.clj create mode 100644 publisher/test/net/deertopia/publisher/roam-test.db create mode 100644 publisher/test/net/deertopia/publisher/roam-test/20260325083230-awesome_file.org create mode 100644 publisher/test/net/deertopia/publisher/roam-test/fake-homepage.org diff --git a/doerg/resources/net/deertopia/doerg/default-config.edn b/doerg/resources/net/deertopia/doerg/default-config.edn index 175b83a..0a8775b 100644 --- a/doerg/resources/net/deertopia/doerg/default-config.edn +++ b/doerg/resources/net/deertopia/doerg/default-config.edn @@ -4,10 +4,8 @@ :latex "xelatex" :dvisvgm "dvisvgm" :doerg-temml-worker - #profile {:dev-publisher "../doerg/doerg-temml-worker/index.js" - :dev-doerg "./doerg-temml-worker/index.js" + #profile {:dev #file "../../../../doerg-temml-worker/index.js" :default "doerg-temml-worker"} :doerg-parser - #profile {:dev-publisher "../doerg/doerg-parser/index.js" - :dev-doerg "./doerg-parser/index.js" + #profile {:dev #file "../../../../doerg-parser/index.js" :default "doerg-parser"}} diff --git a/doerg/resources/net/deertopia/doerg/favicon.ico b/doerg/resources/net/deertopia/doerg/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..10b829d26ccf198ba7fc2fdd1121df81089ffb80 GIT binary patch literal 67646 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 files - (filter identity) - (map aero/read-config) - (apply merge)) - conformed (s/conform spec r)] - (if-not (s/invalid? conformed) - conformed - (throw (IllegalArgumentException. - "bad config" - (ex-info "couldn't conform" - (s/explain-data spec r)))))))) +(defmethod aero/reader 'file + [{:keys [source]} tag value] + "Aero tag to reference a `java.io.File` relative to the config + file." + (-> (aero/relative-resolver source value) + fs/file)) -(def read-config (make-read-config ::d/config)) +(defn read-config [spec files & {:as opts}] + (let [r (->> files + (filter identity) + (map #(aero/read-config % opts)) + (apply merge)) + conformed (s/conform spec r)] + (if-not (s/invalid? conformed) + conformed + (throw (ex-info "Failed to conform config" + (s/explain-data spec r)))))) -(def default - (read-config - ;; Default config. +(defn load-config! [var spec files & {:as opts}] + (alter-var-root var (constantly (read-config spec files opts)))) + +(def sources + [;; Default config. (io/resource "net/deertopia/doerg/default-config.edn") ;; Defaults set at build time, if any. (io/resource "net/deertopia/doerg/extra-config.edn") ;; Config set at runtime. - (System/getenv "DOERG_CONFIG"))) + (System/getenv "DOERG_CONFIG")]) + +(def default (read-config ::d/config sources)) (def ^:dynamic *cfg* default) diff --git a/doerg/src/net/deertopia/doerg/html.clj b/doerg/src/net/deertopia/doerg/html.clj index d7d9cd3..9363a6b 100644 --- a/doerg/src/net/deertopia/doerg/html.clj +++ b/doerg/src/net/deertopia/doerg/html.clj @@ -27,7 +27,7 @@ [:meta {:charset "utf-8"}]) (defn external-stylesheet [href] - [:link {:rel "stylesheet" :type "text/css" :href href}]) + [:link {:rel "stylesheet" :type "text/css" :href (str "/resource/" href)}]) (def ibm-plex (concat diff --git a/doerg/src/net/deertopia/doerg/render.clj b/doerg/src/net/deertopia/doerg/render.clj index d97b2be..e7570b4 100644 --- a/doerg/src/net/deertopia/doerg/render.clj +++ b/doerg/src/net/deertopia/doerg/render.clj @@ -179,7 +179,7 @@ -(defn- render-pprint +(defn render-pprint "Render the argument inline as `clojure.pprint/pprint` output." [x & {:keys [text] :or {text "debug!"}}] diff --git a/doerg/src/net/deertopia/doerg/repl.clj b/doerg/src/net/deertopia/doerg/repl.clj index 9a76170..3352856 100644 --- a/doerg/src/net/deertopia/doerg/repl.clj +++ b/doerg/src/net/deertopia/doerg/repl.clj @@ -31,17 +31,17 @@ (defn render-html [& {:keys [src dest] :or {src some-org-file dest "/tmp/doerg-test"}}] - (fs/create-dirs dest) - (force-create-sym-link (fs/file dest "ibm-plex-web") - (-> cfg/*cfg* ::doerg/ibm-plex-web)) - (force-create-sym-link (fs/file dest "deerstar.css") - (io/resource "net/deertopia/doerg/deerstar.css")) - (force-create-sym-link (fs/file dest "tuftesque.css") - (io/resource "net/deertopia/doerg/tuftesque.css")) - (force-create-sym-link (fs/file dest "Temml-Plex.css") - (io/resource "net/deertopia/doerg/Temml-Plex.css")) - (fs/delete-if-exists (fs/file dest "index.html")) - (->> src render/to-html str (spit (fs/file dest "index.html")))) + (let [resource-dir (fs/file dest "resource")] + (fs/create-dirs dest) + (fs/create-dirs resource-dir) + (force-create-sym-link (fs/file resource-dir "ibm-plex-web") + (-> cfg/*cfg* ::doerg/ibm-plex-web)) + (doseq [x #{"Temml-Plex.css" "tuftesque.css" "deerstar.css"}] + (force-create-sym-link + (fs/file resource-dir x) + (io/resource (str "net/deertopia/doerg/" x)))) + (fs/delete-if-exists (fs/file dest "index.html")) + (->> src render/to-html str (spit (fs/file dest "index.html"))))) (defn render-edn [& {:keys [src dest] :or {src some-org-file diff --git a/publisher/.dir-locals.el b/publisher/.dir-locals.el index 5191489..129c890 100644 --- a/publisher/.dir-locals.el +++ b/publisher/.dir-locals.el @@ -1,11 +1,12 @@ ((nil - . ((cider-clojure-cli-aliases . ":dev") - (eval - . (progn - (defun start-deertopia-server () - (let ((n (cider-format-connection-params "%j" cider-launch-params))) - (when (equal n "net-deertopia") - (cider-interactive-eval - "(do (require '[net.deertopia.publisher.server :as server]) - (server/start!))")))) - (add-hook 'cider-connected-hook #'start-deertopia-server)))))) + . ((cider-clojure-cli-aliases . ":dev:test") + ;; (eval + ;; . (progn + ;; (defun start-deertopia-server () + ;; (let ((n (cider-format-connection-params "%j" cider-launch-params))) + ;; (when (equal n "net-deertopia") + ;; (cider-interactive-eval + ;; "(do (require '[net.deertopia.publisher.server :as server]) + ;; (server/start!))")))) + ;; (add-hook 'cider-connected-hook #'start-deertopia-server))) + ))) diff --git a/publisher/deps.edn b/publisher/deps.edn index 84b9907..e163969 100644 --- a/publisher/deps.edn +++ b/publisher/deps.edn @@ -17,4 +17,5 @@ :aliases {:test {:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}} :extra-paths ["test"] - :main-opts ["-m" "kaocha.runner"]}}} + :main-opts ["-m" "kaocha.runner"]} + :dev {:extra-paths ["dev"]}}} diff --git a/publisher/dev/user.clj b/publisher/dev/user.clj new file mode 100644 index 0000000..7bfe3fc --- /dev/null +++ b/publisher/dev/user.clj @@ -0,0 +1,24 @@ +(ns user + (:require [net.deertopia.publisher.server :as server] + [net.deertopia.doerg :as-alias doerg] + [net.deertopia.publisher :as-alias publisher] + [net.deertopia.doerg.config :as doerg-config] + [net.deertopia.publisher.config :as publisher-config] + [babashka.fs :as fs])) + +(doerg-config/load-config! #'doerg-config/*cfg* + ::doerg/config + doerg-config/sources + :profile :dev) + +(doerg-config/load-config! #'publisher-config/*cfg* + ::publisher/config + publisher-config/sources + :profile :dev) + +(when (not= :running (server/status)) + (server/start!)) + +(defn invalidate-html-cache! [] + (fs/delete-tree (server/html-dir)) + nil) diff --git a/publisher/resources/net/deertopia/publisher/default-config.edn b/publisher/resources/net/deertopia/publisher/default-config.edn index 52b0b3a..cb13ffc 100644 --- a/publisher/resources/net/deertopia/publisher/default-config.edn +++ b/publisher/resources/net/deertopia/publisher/default-config.edn @@ -2,5 +2,7 @@ {:state-directory #join [#or [#env XDG_STATE_HOME #envf ["%s/.local/share" HOME]] "/doerg-publisher"] - :org-roam-db-path #join [#env HOME "/.cache/emacs/org-roam.db"] - :port 8080} + :org-roam-db-path + #profile {:default #join [#env HOME "/.cache/emacs/org-roam.db"] + :test #file "../../../../test/net/deertopia/publisher/roam-test.db"} + :port #profile {:default 8080}} diff --git a/publisher/src/net/deertopia/publisher/cached_file.clj b/publisher/src/net/deertopia/publisher/cached_file.clj index 515271d..995b28e 100644 --- a/publisher/src/net/deertopia/publisher/cached_file.clj +++ b/publisher/src/net/deertopia/publisher/cached_file.clj @@ -20,5 +20,6 @@ (when (or (not *use-cache?*) stale?) (let [r (compute)] (assert (string? r)) + (fs/create-dirs (fs/parent file)) (spit file r))) file) diff --git a/publisher/src/net/deertopia/publisher/config.clj b/publisher/src/net/deertopia/publisher/config.clj index 5cc7ab5..bd2084e 100644 --- a/publisher/src/net/deertopia/publisher/config.clj +++ b/publisher/src/net/deertopia/publisher/config.clj @@ -14,15 +14,15 @@ (s/def ::p/state-directory ::doerg/file) (s/def ::p/org-roam-db-path ::doerg/file) -(def read-config (doerg-config/make-read-config ::p/config)) - -(def default - (read-config - ;; Default config. +(def sources + [;; Default config. (io/resource "net/deertopia/publisher/default-config.edn") ;; Defaults set at build time, if any. (io/resource "net/deertopia/publisher/extra-config.edn") ;; Config set at runtime. - (System/getenv "DOERG_PUBLISHER_CONFIG"))) + (System/getenv "DOERG_PUBLISHER_CONFIG")]) + +(def default + (doerg-config/read-config ::p/config sources)) (def ^:dynamic *cfg* default) diff --git a/publisher/src/net/deertopia/publisher/roam.clj b/publisher/src/net/deertopia/publisher/roam.clj index f34f650..c281d3c 100644 --- a/publisher/src/net/deertopia/publisher/roam.clj +++ b/publisher/src/net/deertopia/publisher/roam.clj @@ -12,13 +12,10 @@ (defonce ^:dynamic *use-db-cache?* true) -(def db-path (-> cfg/*cfg* ::publisher/org-roam-db-path - fs/expand-home str)) - -(def db {:dbtype "sqlite" - :dbname db-path}) - -(def ds (sql/get-datasource db)) +(defn- ds [] + (sql/get-datasource + {:dbtype "sqlite" + :dbname (-> cfg/*cfg* ::publisher/org-roam-db-path str)})) ;;; Elisp sexp (de)serialisation @@ -37,8 +34,14 @@ (defrecord Node [id cache]) +(defn- uuid-exists? [uuid] + (sql/execute-one! (ds) + ["select 1 from nodes where id = ? limit 1" + (-> uuid str elisp/print)])) + (defn make-node [uuid] - (->Node uuid (atom {}))) + (and (uuid-exists? uuid) + (->Node uuid (atom {})))) (defn- fetch-with-cache [node field fetch] (if *use-db-cache?* @@ -53,7 +56,7 @@ node :org-file (fn [node] (when-some [r (sql/execute-one! - ds + (ds) ["select file from nodes where id = ?" (-> node :id str elisp/print)])] (-> r :nodes/file elisp/read-string))))) @@ -84,7 +87,7 @@ (fetch-with-cache node :title #(do (println "fetch") - (sql/execute-one! ds ["select title from nodes where id = ?" + (sql/execute-one! (ds) ["select title from nodes where id = ?" (elisp/print (:id %))])))) @@ -94,7 +97,7 @@ (fetch-with-cache node :level #(-> (sql/execute-one! - ds ["select level from nodes where id = ?" + (ds) ["select level from nodes where id = ?" (print-id %)]) :nodes/level))) @@ -105,7 +108,7 @@ (fetch-with-cache node :file #(-> (sql/execute-one! - ds ["select file from nodes where id = ?" + (ds) ["select file from nodes where id = ?" (print-id %)]) :nodes/file elisp/read-string))) @@ -114,7 +117,7 @@ (fetch-with-cache node :properties #(-> (sql/execute-one! - ds ["select properties from nodes where id = ?" + (ds) ["select properties from nodes where id = ?" (print-id %)]) :nodes/properties elisp/read-alist))) @@ -130,7 +133,7 @@ "Returns a collection of maps {:id …, :title …}." [node] (for [{id :nodes/id, title :nodes/title} - (sql/execute! ds ["select distinct nodes.id, nodes.title from links + (sql/execute! (ds) ["select distinct nodes.id, nodes.title from links inner join nodes on nodes.id = links.source where links.dest = ?" @@ -150,9 +153,9 @@ (-> uuid parse-uuid get-node graph-visible?)) (defn get-graph [] - (let [nodes (sql/execute! ds ["select id, title from nodes"]) + (let [nodes (sql/execute! (ds) ["select id, title from nodes"]) links (sql/execute! - ds + (ds) ["select n1.id as source, nodes.id as target from ((nodes as n1) join links on n1.id = links.source) join (nodes as n2) on links.dest = nodes.id diff --git a/publisher/src/net/deertopia/publisher/server.clj b/publisher/src/net/deertopia/publisher/server.clj index e1012eb..6d1b215 100644 --- a/publisher/src/net/deertopia/publisher/server.clj +++ b/publisher/src/net/deertopia/publisher/server.clj @@ -22,11 +22,17 @@ [net.deertopia.doerg.render :as doerg-render] [net.deertopia.publisher.cached-file :as cached-file] [babashka.fs :as fs] - [aero.core :as aero])) + [aero.core :as aero] + [clojure.string :as str] + [net.deertopia.doerg :as-alias doerg] + [net.deertopia.doerg.config :as doerg-config])) ;;; Routes +(def homepage-slug "68XqhHerTWCbE--RYLEdHw") +(def not-found-slug "PGDHTvUzQ62Js1Y5db-A8g") + (defn hello [req] (-> (hiccup/html {} [:html @@ -42,11 +48,37 @@ response/response (response/content-type "text/html"))) -(defn node-by-slug [{{{:keys [slug]} :path} :parameters}] - (let [html (-> slug slug/from-string roam/get-node - roam/org-file doerg-render/to-html)] - (-> html response/response - (response/content-type "text/html")))) +(defn html-dir [] + (-> cfg/*cfg* ::publisher/state-directory (fs/file "html"))) + +(defn not-found [req] + (response/not-found "not found")) + +(defn org-file->html-file [org-file] + (fs/file (html-dir) + (-> org-file + fs/file-name + (fs/strip-ext {:ext "org"}) + (str ".html")))) + +(defmethod doerg-render/org-link "id" + [{:keys [path raw-link children]}] + [:span.org-link + [:a {:href (str "/n/" (slug/from-uuid path))} + (or (seq children) raw-link)]]) + +(defn node-by-slug [{{:keys [slug]} :path-params :as req}] + (if-some [node (some-> slug slug/from-string roam/get-node)] + (let [org-file (roam/org-file node) + html-file (org-file->html-file org-file)] + (cached-file/cached-file + :file html-file + :stale? (cached-file/newer-than? org-file html-file) + :compute #(doerg-render/to-html org-file)) + (-> (str html-file) + response/file-response + (response/content-type "text/html"))) + (not-found req))) (defn node-by-id [req] (hello req)) @@ -60,17 +92,39 @@ (l/error e "error in fucking somwhere dude") (handler e request))}))) +(defn handle-homepage [req] + (-> req + (assoc-in [:path-params :slug] homepage-slug) + node-by-slug)) + +(def doerg-resources + #{"Temml-Plex.css" "tuftesque.css" "deerstar.css"}) + +(defn handle-resource [{:keys [uri]}] + (if-some [[_ resource] (re-matches #"^/resource/ibm-plex-web/(.*)" uri)] + (-> resource + (response/file-response + {:root (-> doerg-config/*cfg* ::doerg/ibm-plex-web str)})) + (-> uri + (str/replace-first #"^/resource/" "") + (response/resource-response + {:root "net/deertopia/doerg/public" + :allow-symlinks? true})))) + +(defn handle-favicon [_] + (response/resource-response "net/deertopia/doerg/favicon.ico")) + (def router (reitit.ring/router - #{["/" {:get hello}] - ["/n/:slug" - {:get {:handler #'node-by-slug - :parameters - {:path {:slug ::slug/slug}}}}] - ["/id/:id" {:get #'node-by-id}]} + #{["/" #'handle-homepage] + ["/n/:slug" #'node-by-slug] + ["/id/:id" #'node-by-id] + ["/resource/*" #'handle-resource] + ["/myreq" #'hello] + ["/favicon.ico" #'handle-favicon]} {:validate reitit.spec/validate :exception reitit.dev.pretty/exception - :spec (s/merge :reitit.spec/default-data) + :spec :reitit.spec/default-data :data {:coercion reitit.coercion.spec/coercion :middleware [exception-middleware diff --git a/publisher/test/net/deertopia/publisher/roam-test.db b/publisher/test/net/deertopia/publisher/roam-test.db new file mode 100644 index 0000000000000000000000000000000000000000..5138bb9f2189244987138fe56991976ab47e5ec1 GIT binary patch literal 53248 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCU|?ooVBlpy04@dw1{MUDfsuiMK?I49 zjmgZQ*ZYf?{|5sPmlgwmApcoDO@1}rxjavKe7VnZ3vlh@D&f+?rg2nkGz3ONU^E0q zLtr!nMnhmU1V%$(phLiehfQ2nlrc3kFD132Br&}>J}*BdH9j*1#O8Gla&-)GRq*t4 zag9&_iNItP{Qbau1r4Zj1x*ibB$J9#)8Hn7*gPmEfkco^0`ZYd3gBWBSC?kY1-mIR zCo>Ul6qu7*%#C6iSP*~MUj3u;n`qmz%T z0$4~vqaeRn!OuTL!7tRuM@OMJzqBYh6-gi^wYUU{TT)qoEL%{NUyxc~>HrfG{VD%RwRt{~{b;l9;Zbqkv*P$gX&hO~l&YD9R3YKoc+60iehN zVGa}rfJCr4peQvBcUV#AE>kvkad~;hCQGopk~2#ZOEUBGis1})6c@sUu{kq2vm`Y> zJGBzYt*EI0DOxEGPfk{LacODB#!Rrgp{W&0v7xvbDu~U+U@6Muwu%{?F`6M61C%z3 z!2}D6JHSGq^a&3ig@7PWU&o+GP?FYBNXyJY&I38AWvMxEr8)|zL0OWYlCPknP*9Ya zUzAx=si31!oSc!GQks*RqEM1rQKF-elA4&3lbM$a<(6cY5QQycX|k}1+ln(XfXkWU!W>WIwNG;MY zNi8k`N$O*hEXq&J)dj0aOUzEy1sPe8n4YSaUzDz-1lORKms+9-1xiXrX6B}bX69z* zX(nk#CI+cV=82}Jsi}r$W~OF|iHS+(=7|<2Nk(Q$8b;;@=9UVU<`yOjrY5FlmKF*Y zW|js92AT*NBL!1aQ)5Fj1xr(~Ok)xgdtM& zoS&N-59+!?ybBcp4<{%o8KhWPCZ?DeTP7x_rKP4>ni{29T9_o78ylLNn3^P|7^kI} znOdfq!+dFGVrr~lX>M+8YOY`m_oKOjp{c2bfr62tg}Jejg1NZ`NQ!}h;XcVxKXf#3 z@y}#n;@i!@ugtf5=(u{+rK2G*8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd70QC=5la?WHej+8I+8SQ&UaMOj2}{42@HD zP0UOzbuEm{(sa#?j8Y6zQ&Q86Oj4Do-5n&hC>p`R3ymM5ya5Uly2THQ zQ*p-#7ymT|CjJHn{?+^qpsY5EM?+vV1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ON zU^E1%9|Bs;k&MI*f-r-X5;Y12T0B7Hsd}LKe+B{S`)kz1(GVC7fzc2c4S~@R7!85Z z5Eu=C(GVC7fzc2c4S~@R7!85J69S|C|G^U%qb?Z@fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7!3hhg}`Y4pH?9|YVK$VjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kin zXb23R5E$+M51zOfb;)Q5jE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb8|M1V;P+ zv> (format "http://localhost:%d" + (::publisher/port publisher-cfg/*cfg*)) + slurp + string?))))) -(t/deftest can-get-real-server-root - ;; 서버가 벌써 시작한 다음에 이 테스트 하면 잘못됩니다. - (assert (not= :running (server/status))) - (server/start!) - (t/is (->> (format "http://localhost:%d" (::publisher/port cfg/*cfg*)) - slurp - string?)) - (server/stop!)) +(comment + ;; 테스트 데이터베이스를 아직 안 준비한다. + (t/deftest get-nonexistent-node + (let [slug "3Lxvxnb0QrivoU3DX-l_5w"] + (assert (nil? (roam/make-node slug))) + (t/is (= 404 + (-> (str "/n/" slug) + get-sut :status)))))) + +(comment + ;; 테스트 데이터베이스를 아직 안 준비한다. + (t/deftest get-homepage + (let [resp (-> (str "/n/" sut/homepage-slug) + get-sut)] + (t/is (= 200 (:status resp))) + (t/is (= (-> "/" get-sut :body) + (-> resp :body))))))