From de61e45637e86ffceef1c98c3d5ad0b588614bf2 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 | 8 +- .../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 ++ .../net/deertopia/publisher/server_test.clj | 68 +++++++++++---- 20 files changed, 222 insertions(+), 85 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 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 zcmZQzU}RuqXlMY@3Je+?j0|E73=A3!3=9nn3?M-UMg|2CYZQ-$z-S1JhQMeDjE2By z2+%PE!YVE@hE-goqm3iSDrnephgDou3ahxN99D5rG_2wx(?EwnSj9z=u!@V-VHFp* zhE-fV6;^TaU|7Y)#bFf}Q^G1P@(i^5hJ*cK6&KZ@;rlhL;^H4D{vB3v@oHGb#h`)8 zb72)1*}^I=UJk3c_#X!b$$^^b!yy`I<_A#xg;iW+4y(Ax3dJm-ybTp+3ahwy8b|no z!u?BF#YH}vIS!A(p!^+Haq$}-w}IT(hsTH!FBMjC(Il+m;+C+Ai;u%9F8&CsxcDoq z;^O_h=7FFNt+48|2dGlfpFhow-XdDVHFpT;R(Ng zVHFom!zwP$!J`Hg|5Bv*H88r1DKNTAIWW2_ATYWsAuzftJ}|n=IWW45KQOwB6r&iz zDlXcERb2dw$9<1LWgAI`ju6Gr@_h-Oumk1y0BC#f6ApEt@|T_1APbD{5(td$+7lSv z^(Qd8>pvQP8yMZ?N~~ERbT7;qk+0{9{HtTm%}o*tm5KH9O35+(hf~0 zo3N;P2htBxLktd#?h+4-?)r=;4g3y_?jotr26fwhEN%nE|9w)*Cu00C9MyzXT#UdG zemOAzhE-ggh(!&kzd&^V4^*}XMtAk%iT}TW(Ovp5JBg*kDlTrs;x(Sf)AbuSwW`We=iT}MI z`B6+b1i~sV+F*&_kDxI?knXUGi!#u$e^8!B#w8$mQgC2&7js~A*8?17zyeat0;$Cl z|LZ~WqnL0AgjHM=fR6tn$1P}l4?MmbR&kLXM?ZTKVbf7nKWO<;7_KP+kB7igRq zq=py_b@yQ`?gpjd3B;H&ss<7QVHFo=VhKm&KHwTG@=rl!1H=U4$-w9?;lSuF(0C81 z&4-LZX~KY5lc4Dm)XxLOH#%-3)|^pw450b63M}yp>RW?@C#>S)6fE+fF(6*B0ul&N z8IPw87)-2Dp#Di%#YJ4>zu@KB#F{nI)rD1DjKUJX$bG;nEb^c+Aazp14%!CXho$@n zl?CO*ng=R-!YVF;#`)0mK8RmWtU06V!1*6EpAHH)WDHsl!U_tXu!@VRSmGa~))6F6 zDh`b9nujC)XAx@_H2xpra2LsaQ)2BMj_SfHE^1&|PXX!!N`m|wR&miEM>xlV-_{wb{DB50luln&AHX#OWE6d{VjDlYPeRa^wE?L-bo(A=;INCk9^ z2w4usCc3-^nFGUt(OsE1;{O@3^T5!#Y0x@Em|KwPMwnfrG~p0{=A{o<@;GcBSO-h_ z01EFZgiXg(5g6SSfg}DwW5YzvD}_~DR40UkJ!btQea2i?O&BDlRU< zA`c4hL!j~jqy`@jjPBwIjP5#*C+=|itVHFow z;fUW*P*{XjT%3SI{w-)M2BZcT2CeG|jP9CAAnrlyKR9uj0TKtLGw42VT=5PP2lWX^ zTAK(md!%C=^*<;)D?nirR&lWoNBo24f$)ym1V(rH;92(v%J-oCAF?2O@ZqqEi@rF< zp+N40;klr6g3p{$Im{5i6aV0~G+`AN^KisJDBQy^O@xR9Mt5-sMt9vNkoQ63!4M5N z$gqlwVqp~*@wNFt?f{iV;5iE%#*7N%2mu`ZKTw#0*4Tm82qfT%fB0GvWaFXj{W1b^ z584+bfUFOl4efv8s-Hk^gkjJ;HM;guJ`o`hR&jA3j`+O=4gWwK@}RKC+TIF`?h+xe z=L1ysgX(>p>&T$({d7EIJs|r*7&Jb`PlQ)Tm0$@09OHkWa0Q(Y!53C>5ws^9#K(p~ z?Q!s00hrOyypM0c2Pp1Acp6L(Hae{0qCr^2#m{)s9?U(Ub1SeJF)BtN1VHVju!@VI zu|8N>BGaHgAZUFEzVaWWrXISE2((tfF0^2uO<+vl=fLQ0P#ojKRk)lNR&h}fdL{|7 zd$6%VZBUZ;58|?Sq=`f0AJQm|o;` zfD!-DIv?NoKDr$%Kzc@E+)=~N(7qL@?}Q$%G~t8FgrJm#urz=Y|DbpbtGEa{e*`p! zORW8)>%GWK2+;YRZM4q&nC4&jACkB8e^C6CT4bk(Ra}%0tGM_AQ$5b}{y=FY1=);I zHgO>U&1;}`8z>A12w%7zR&h}e>K4$NF3?$?q`D=DI3JIyLJonjii<8d*7cAY{*0cf31XgrmixFA>0Pa?+lahXl@z9GUPGyGLR`+F-%tYdM7 z{a_GJ9R9IJJs(2n(LiH1gCXvy;f5mWh0gFc2|BKFei-F_Q2J>e-mykK--T6NbS7dh z1r(-32~Vb82n}D8pna_Q!z}NE(&_x+8*9|_8T2flm%}jrNj^J@dZ9XWOd_yn0+f%2 zDjto0B9dWP#YIBvriW_$kCy+$#Xr6?^M`8OgWNrl2=9+*CD1uuT<7J0!f?pq%2{_d z9)9&-_wcL#B%L8Yk`e^yJkPL-i@5Id7_xB>YRk^r_v-)QSO5PWe)a#>;aC4t4!`<8 zx)ua2afel0l*X}#e@MqYDE_PG-u=J%=66sU0O5a!U;STo_|<=cdx+73XjpThcYY10 zF<+E)8(Dqn|CH@7{vUkt50nN#xc=~~|HC>?N%34*#YJ~eUK#GVZN;PihhBoy!283m z{*!bz0V&}*)YOJmTnruVaS!v~!lUm%`5%OF?n@eK@kgn9!zwP~TfdD`mcqhusL}@;@m4+bIpV;i5mR;$qBDjZZx8J{wkX5p-@5X#GDt4M$X6`oI0|Pf+}W&#xIS zaY%_j!YVF;)>py92v3*}6*Zug6=8?tz!eNBZRws+3Am<_#eFo0-9*TDlQrg zvw2_8ndR_(gJBgHLH9wz=eB0=dku>Jl~C`GcnW&PCulDpJdcoB27vmIpz;<(Q)(8u z`ayTRqwMvB&TWUm))3Cz^AZ&QqvyawQ$Cf(f7gXoTr>--xTqFZanU-g;$nPQ#l^0$ zii^9#DlUS~iUOUzL2jH9tM5Tr#YGjUw@@hPy2h!XbUl0jYtVS0HHv}}&Izly*aHe1 zGBGI6Yhw#SXx?Lip2-MmXM)ZW0^J`E(nD;#5~B{ZM-hB45jJ;##6V}!gjZfnUv~Oq z+~HUM;q?PZZlq&qo+Y>32F0ZoF=2?59t^`OE`sis*bCjmO|IRbyQTbmjInH;T2YK(KxK)q7bOf zLYZ$zb)$y>^h^cNek)LZCKZGF+E;6rTqxWGtDha;n`pJMO#(^@CCH?ESBaiDz7DKjD2W_+|YNuk(LgFk(N%Hk(SOszmVwvMJ*Hl zm$Xm*pIO=RKR7Dse?VC5|JckTjQF3h`57nQn2oe_?i%ap{qIcofVha*TL%ea!&IKgenmT?3`28O!GGy8rnVbPo(h8d!Vj z%m382YdGW8HDSenULLXk-0WQc#pTTZ`{y6TsTM6=JPE6~*aY2IJi7M^6d?nEp<};n zhhP0SIQ;5=<>6QVukCsK=l`nnpZ>S5c=R8%9uh6y5b;}n;lGl82srLR@ejf_!IKd3 z6?o5GgsB6~*IkF+5y3M6K`?41QV2}n^@25~?lNevH^DPhL(9+q7Zg@OiGM?v3Rt|7 zLxa}mfc6@U&JiHR3B8%nIabh`fBb8*$}jv^F$hPAf1BXR_z5P9E zA0h;x>n_&fEc>BlyI1;_|I#W>|M_^t|7%-k{SPiVg&da{Y|!}8#4XSNuetd7|N1Lm z{?9-B_J7r!yZ^&0G3pA?`R$|oFcDEgHx@Mi+e6n~VZ_4k6(A>BMaY7saDpl2$-0p-c;-W&g;YjOAe(9;4wep<&h z{N6DlUTVYB~i9`)RwtdmzB^wBX2F8#IQr z?+Id_5f<{)ML~Gleaww*Dc8Ljg1W&D?ln+T0rTQMnN&+jD?=-b^sK< zy&Imw>tj&a+q?cLIPO7mpgJ5WE^k##+{;ZUuqr0*Rj-)1_cu&m`GkG{V{6aB%><>X z$=hDQ+Zov69wY{;i;(7@M(=4Q6!1jKgYpq{Kh^)T8F&64c=i_@|Dd?<+lYvJP@I9{ z4jPaDDkknF>b{hUiF+9I|Ndux|F^Gv1kaEn7B6)st;~PLHFVR z=xHFQ@5cW%m%f10A1KeGW01QtI^p9&*FgJpQ7jtekQ4&Yav$8!UvvyI9=rASkN+jp zZo}g~y88V8BGkAC#UZKjFI+Kk?-%qmP}pJ}IV)dx*W9{k^U{TtSH0n)w$ zPhaztpBve~{{XP@dmVF>&u-XdHstb7I8V1W{Enac==sFUTwm zoYQd>=a>>`3_5SZ&HteOBB-4J;)C1`J&Py_)P{lBGD;E~0?>OKL1id%-UF@2Ka|#d zvc6*C-d{-Zb+2OLUPEZU2gMyI{zZtj3#6`M;$C+o^FU@SteCj>IkFg}EI0#N4@Sxv zL!dKbM%Rsj0+|FHR&miOtl}aloI&e*ZiZD{oDf!V(X^!R0C&a2y%&+=rDEdV1Ww__@;v!dA#YK&#GB9(4YSH2!BtHu@b^y{lifJDLps=o( zxOX*{cvu9oy<*~Cd9?gX(tW9D=?4_Q>pw!@6w@{YDkknV zMvGri9D(p=kj)hn_hzES(LPX`0m%`AD<ZNRcxR<$N;$921{5uDhe+iinlCPM!Hythg zfa2E{DhFzJfXaK2*rSSxd!Hbw1&KR>)Qw{5hX5!JD<x;| zR9#fX#J#&KChnbDF>$XTDXyuQxYrZOOi(<7;#C)923p?|)c16*n7H>Rl6p{ofEA>6 z6jL_@K=D{Haql~%u%2Hrac=+;A5<=IP--hQ4Q+&uA%gmkGhyXATKwOImNzv>ZUCj1 zBud>dsvk21DkkpDK?-M3o&>c6k=tXSu|9E3ourF^+6xsE_i8}L0^obQ(Bl6AH2y(l z&LJeXfy77G|B@c;ICVh7`4UnXABDz|CN!Ud@*OBmXy7!23KD323sAcdd@eh*PXa1` zK<)tHa!@@)1uI95U|?XVn7G#ttxXO-kE>$hURkuhpf{}o44U39Af-W2JcI26)ejXD z_nt?R1EmcaTDfH;nG5ZYpFj%Rw-poj@`1vjV&Yy$v~&TQ`vXZ)9Yf0?P@5kV?=Tz+ zvZ!L>UY3f9dtV^A5hPy-k{iY3hX6ERf#%;p;R(YhLE<1&D<=GFkB3l93{vOfr^QHebMq1C~QIHEO;Ir zS|@i_Oxz3V`-9f`nvrc8g&Lu4k!=+d_dc$exOWjTYmO^2{vQS~A4GuVe=sooe_;Oy&IidKkVoP(GWbxaK<7Ku|3Q%_ zg3pX>oLhv*Fysg zCQl(h0Zso0R6Zk||AB%10Rsc`519N12KIjp3~=xK|G>ch{{T|-{9s`I|BnHt`u~3r zAL?`v^?`u_?sX9V02&_@`yg=`M(2Y<6JhxO|J3G#>_enoP-nix2`C21!xQWO|KQ|`#0Q&)WFa^?BJsh=4H4S^(fQ!yfaJgf3=Jp_ZeaM262c9P zsC)*-{|(?09pc=7SojAR5YhREf$={UKHS4lc{u-{0ptIJNc#UjaQuI;0b&=JJb=!3 z_> 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..85d7ab4 100644 --- a/publisher/src/net/deertopia/publisher/roam.clj +++ b/publisher/src/net/deertopia/publisher/roam.clj @@ -37,8 +37,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?* 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..78720ec0bd2cbe92996b66b6479134d6cbc54b59 GIT binary patch literal 53248 zcmWFz^vNtqRY=P(%1ta$FlG>7U}R))P*7lCU|?imVBlpy04@dw1{MStEH8q@$Hru2 z(Chuh%m0Ichf9lrKal?{pC-Q=?_8dzJigp#xdpiPag}gsVbeG&HW~t>Aut*OqaiRF z0;3@?8UmvsFwi03!NVr5D$1CenU|7UQIeQm9G{n;k{X|x0%G$z2e~?ixGH%1xwu9s zfJ9)j3jTgzzJdl+xq_w#HyeRRJud zpiz)ttl;M#qTm*DI;8se(p>=^9q=;Eqb$H6Wx zD$3Xx4|Y2!7C@L2#pNIognto^FG);S&{06K9%NTM$R=X#Zxm$*JD`ad>;O+hC8e%beAa`ySThOW0NJ=UCEgxi6xo&dBt!BJBkb8!q}XdoLQ0@pPgEX zC!YVz3nDaa+X<&KS**i~&j;#bAO3 z#T{TFQ2K<2k3v9@r>|pBBq&MiD5PcPAm@Rc)UwnZxKbSj)SxWMPsvx%Q79p+WOL9`dI^bDLMSD<7o zkU+7528hCzu{2rO#BIeH8NlUCabXT94J4M9e~GINoGFBgTcsmX-TnP5BP4Z##M0|NsG zs73*I@C87uQ9K#~qaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFkC|bG^x)p z+W#M}(L3s|(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R0EfWn{69DtMv2i7 z7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVEcAuu}sKdhs7)N`XDFd71*Aut*O zqaiRF0;3@?8UmvsFd71*Aut*OqagqefzkPYa4?J#qaiRF0;3@?8UmvsFd71*Aut*O zqaiRF0;3@?8UmvsFswshwEsV>qj%JEqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8Umvs01koC{y#VvMv2i77!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVEcAu!ti zAJ)-3>bcPn7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GUQKz-a#;91NqxXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD4C@dWJ^z1LNAIZTMnhmU1V%$(Gz3ON zU^E0qLtr!nMnhmU1V%$(Gz3ON02~6N{eN&Uj1r?EFd71*Aut*OqaiRF0;3@?8Umvs zFd71*Aut*OqaiS?LtwQ3Kdhs7)N`XDFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*O Jqagqe0RWWs*KYs- literal 0 HcmV?d00001 diff --git a/publisher/test/net/deertopia/publisher/roam-test/20260325083230-awesome_file.org b/publisher/test/net/deertopia/publisher/roam-test/20260325083230-awesome_file.org new file mode 100644 index 0000000..0367438 --- /dev/null +++ b/publisher/test/net/deertopia/publisher/roam-test/20260325083230-awesome_file.org @@ -0,0 +1,7 @@ +:PROPERTIES: +:ID: 23ee464d-b13e-4649-826f-622d0edef24e +:DeertopiaVisibility: public +:END: +#+title: awesome file + +wow! diff --git a/publisher/test/net/deertopia/publisher/server_test.clj b/publisher/test/net/deertopia/publisher/server_test.clj index 89e4431..6996060 100644 --- a/publisher/test/net/deertopia/publisher/server_test.clj +++ b/publisher/test/net/deertopia/publisher/server_test.clj @@ -2,23 +2,57 @@ (:require [net.deertopia.publisher.server :as sut] [reitit.ring] [clojure.test :as t] - [clojure.set :as set] - [net.deertopia.publisher.server :as server] - [net.deertopia.publisher.config :as cfg] - [net.deertopia.publisher :as-alias publisher])) + [net.deertopia.publisher.config :as publisher-cfg] + [net.deertopia.doerg.config :as doerg-cfg] + [net.deertopia.publisher :as-alias publisher] + [net.deertopia.doerg :as-alias doerg] + [net.deertopia.publisher.roam :as roam])) + +(t/use-fixtures + :once (fn [f] + (binding [doerg-cfg/*cfg* + (doerg-cfg/read-config + ::doerg/config doerg-cfg/sources + :profile :test) + publisher-cfg/*cfg* + (doerg-cfg/read-config + ::publisher/config publisher-cfg/sources + :profile :test)] + (f)))) + +(defn with-server [f] + (let [was-already-running? (= :running (sut/status))] + (when-not was-already-running? + (sut/start!)) + (f) + (when-not was-already-running? + (sut/stop!)))) + +(defn get-sut [uri] + (sut/app {:request-method :get + :uri uri})) (t/deftest server-is-running - ;; 서버가 벌써 시작한 다음에 이 테스트 하면 잘못됩니다. - (assert (not= :running (server/status))) - (server/start!) - (t/is (= :running (server/status))) - (server/stop!)) + ;; 서버는 벌써 시작한 다음에 이 테스트 하면 잘못됩니다. + ;; (assert (not= :running (sut/status))) + (with-server + (fn [] + (t/is (= :running (sut/status))) + (t/is (->> (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!)) +(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))))) + +(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)))))