basic rendering!
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
{
|
||||
"lock-version": 4,
|
||||
"git-deps": [],
|
||||
"git-deps": [
|
||||
{
|
||||
"lib": "io.github.msyds/spec-dict",
|
||||
"url": "https://github.com/msyds/spec-dict.git",
|
||||
"rev": "531d629b7f05f37232261cf9e8927a4b5915714f",
|
||||
"git-dir": "https/github.com/msyds/spec-dict",
|
||||
"hash": "sha256-5hMdPsB8OhOCtByPZS+CHXzVLq0H+OBKKnXec21xwmg="
|
||||
}
|
||||
],
|
||||
"mvn-deps": [
|
||||
{
|
||||
"mvn-path": "babashka/fs/0.5.24/fs-0.5.24.jar",
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
babashka/fs {:mvn/version "0.5.24"}
|
||||
org.clojure/core.match {:mvn/version "1.1.0"}
|
||||
cheshire/cheshire {:mvn/version "6.1.0"}
|
||||
babashka/process {:mvn/version "0.6.25"}}
|
||||
babashka/process {:mvn/version "0.6.25"}
|
||||
io.github.msyds/spec-dict
|
||||
{:git/sha "531d629b7f05f37232261cf9e8927a4b5915714f"}}
|
||||
:paths ["src" "resources" "test"]}
|
||||
|
||||
11
doerg/resources/net/deertopia/doerg/deerstar.css
Normal file
11
doerg/resources/net/deertopia/doerg/deerstar.css
Normal file
@@ -0,0 +1,11 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
:root
|
||||
{ --ds-hoof-black: #0c0c0f
|
||||
; --ds-velvet-grey: #4a4241
|
||||
; --ds-untitled-1: rgb(47, 35, 28)
|
||||
; --ds-untitled-2: rgb(168, 134, 109)
|
||||
; --ds-buck-brown: #b57b4c
|
||||
; --ds-antler-tan: #fce7ca
|
||||
; --ds-fawn-spot-white: #fffffa
|
||||
}
|
||||
544
doerg/resources/net/deertopia/doerg/tuftesque.css
Normal file
544
doerg/resources/net/deertopia/doerg/tuftesque.css
Normal file
@@ -0,0 +1,544 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@import "/vendor/ibm-plex-serif/css/ibm-plex-serif-default.min.css";
|
||||
@import "/vendor/ibm-plex-sans-kr/css/ibm-plex-sans-kr-default.min.css";
|
||||
@import "/vendor/ibm-plex-math/css/ibm-plex-math-default.min.css";
|
||||
@import "deerstar.css";
|
||||
|
||||
html {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "IBM Plex Serif", "IBM Plex Sans KR", "IBM Plex Math", Palatino, "Palatino Linotype", "Palatino LT STD", "Book Antiqua", Georgia, serif;
|
||||
counter-reset: sidenote-counter;
|
||||
background-color: var(--ds-antler-tan);
|
||||
color: var(--ds-hoof-black);
|
||||
}
|
||||
|
||||
/* Adds dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: var(--ds-untitled-1);
|
||||
color: var(--ds-antler-tan);
|
||||
}
|
||||
}
|
||||
|
||||
math { font-family: "IBM Plex Math"; }
|
||||
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 3.2rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
margin-top: 2.1rem;
|
||||
margin-bottom: 1.4rem;
|
||||
font-size: 2.2rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 1.7rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1.4rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 1px;
|
||||
width: 55%;
|
||||
border: 0;
|
||||
border-top: 1px solid var(--ds-velvet-grey);
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
hr {
|
||||
border-color: var(--ds-velvet-grey);
|
||||
}
|
||||
}
|
||||
|
||||
p.subtitle {
|
||||
font-style: italic;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.8rem;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.numeral {
|
||||
font-family: et-book-roman-old-style;
|
||||
}
|
||||
|
||||
.danger {
|
||||
color: red;
|
||||
}
|
||||
|
||||
article {
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 5rem;
|
||||
padding-left: 6%;
|
||||
padding-right: 0;
|
||||
width: 87.5%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
p,
|
||||
dl,
|
||||
ol,
|
||||
ul {
|
||||
font-size: 1.4rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 1.4rem;
|
||||
margin-bottom: 1.4rem;
|
||||
padding-right: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* Chapter Epigraphs */
|
||||
div.epigraph {
|
||||
margin: 5em 0;
|
||||
}
|
||||
|
||||
div.epigraph > blockquote {
|
||||
margin-top: 3em;
|
||||
margin-bottom: 3em;
|
||||
}
|
||||
|
||||
div.epigraph > blockquote,
|
||||
div.epigraph > blockquote > p {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
div.epigraph > blockquote > footer {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
div.epigraph > blockquote > footer > cite {
|
||||
font-style: italic;
|
||||
}
|
||||
/* end chapter epigraphs styles */
|
||||
|
||||
blockquote {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
blockquote p {
|
||||
width: 55%;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
blockquote footer {
|
||||
width: 55%;
|
||||
font-size: 1.1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
section > p,
|
||||
section > footer,
|
||||
section > table {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
/* 50 + 5 == 55, to be the same width as paragraph */
|
||||
section > dl,
|
||||
section > ol,
|
||||
section > ul {
|
||||
width: 50%;
|
||||
-webkit-padding-start: 5%;
|
||||
/* Accounts for the padding from the bullet, resulting in the same width as
|
||||
paragraphs. */
|
||||
width: calc(55% - 40px);
|
||||
}
|
||||
|
||||
dt:not(:first-child),
|
||||
li:not(:first-child) {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
figure {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
max-width: 55%;
|
||||
-webkit-margin-start: 0;
|
||||
-webkit-margin-end: 0;
|
||||
margin: 0 0 3em 0;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
float: right;
|
||||
clear: right;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.6;
|
||||
vertical-align: baseline;
|
||||
position: relative;
|
||||
max-width: 40%;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
figure.fullwidth figcaption {
|
||||
margin-right: 24%;
|
||||
}
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
color: inherit;
|
||||
text-underline-offset: 0.1em;
|
||||
text-decoration-thickness: 0.05em;
|
||||
}
|
||||
|
||||
/* Sidenotes, margin notes, figures, captions */
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.sidenote,
|
||||
.margin-note {
|
||||
float: right;
|
||||
clear: right;
|
||||
margin-right: -60%;
|
||||
width: 50%;
|
||||
margin-top: 0.3rem;
|
||||
/* margin-bottom: 0; */
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.3;
|
||||
vertical-align: baseline;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidenote-number {
|
||||
counter-increment: sidenote-counter;
|
||||
}
|
||||
|
||||
li .sidenote,
|
||||
li .margin-note {
|
||||
/* it's so close lol */
|
||||
transform: translateX(4px)
|
||||
}
|
||||
|
||||
.sidenote-number:after,
|
||||
.sidenote:before {
|
||||
font-family: et-book-roman-old-style;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.sidenote-number:after {
|
||||
content: counter(sidenote-counter);
|
||||
font-size: 1rem;
|
||||
top: -0.5rem;
|
||||
left: 0.1rem;
|
||||
}
|
||||
|
||||
.sidenote:before {
|
||||
content: counter(sidenote-counter) " ";
|
||||
font-size: 1rem;
|
||||
top: -0.5rem;
|
||||
}
|
||||
|
||||
blockquote .sidenote,
|
||||
blockquote .margin-note {
|
||||
margin-right: -82%;
|
||||
min-width: 59%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.fullwidth,
|
||||
table.fullwidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.table-wrapper {
|
||||
overflow-x: auto;
|
||||
font-family: "Trebuchet MS", "Gill Sans", "Gill Sans MT", sans-serif;
|
||||
}
|
||||
|
||||
.sans {
|
||||
font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif;
|
||||
letter-spacing: .03em;
|
||||
}
|
||||
|
||||
code, pre > code {
|
||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
font-size: 1.0rem;
|
||||
line-height: 1.42;
|
||||
-webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS. See https://github.com/edwardtufte/tufte-css/issues/81#issuecomment-261953409 */
|
||||
}
|
||||
|
||||
.sans > code {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h1 > code,
|
||||
h2 > code,
|
||||
h3 > code {
|
||||
font-size: 0.80em;
|
||||
}
|
||||
|
||||
.margin-note > code,
|
||||
.sidenote > code {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
font-size: 0.9rem;
|
||||
width: 52.5%;
|
||||
margin-left: 2.5%;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
pre.fullwidth > code {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
max-width: 90%;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
span.newthought {
|
||||
font-variant: small-caps;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
input.margin-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label.sidenote-number {
|
||||
display: inline-block;
|
||||
max-height: 2rem; /* should be less than or equal to paragraph line-height */
|
||||
}
|
||||
|
||||
label.margin-toggle:not(.sidenote-number) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.iframe-wrapper {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
padding-top: 25px;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.iframe-wrapper iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
article {
|
||||
width: 84%;
|
||||
padding-left: 8%;
|
||||
padding-right: 8%;
|
||||
}
|
||||
|
||||
hr,
|
||||
section > p,
|
||||
section > footer,
|
||||
section > table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
width: 97%;
|
||||
}
|
||||
|
||||
section > dl,
|
||||
section > ol,
|
||||
section > ul {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
figure {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
figcaption,
|
||||
figure.fullwidth figcaption {
|
||||
margin-right: 0%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-left: 1.5em;
|
||||
margin-right: 0em;
|
||||
}
|
||||
|
||||
blockquote p,
|
||||
blockquote footer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label.margin-toggle:not(.sidenote-number) {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.sidenote,
|
||||
.margin-note {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.margin-toggle:checked + .sidenote,
|
||||
.margin-toggle:checked + .margin-note {
|
||||
display: block;
|
||||
float: left;
|
||||
left: 1rem;
|
||||
clear: both;
|
||||
width: 95%;
|
||||
margin: 1rem 2.5%;
|
||||
vertical-align: baseline;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.table-wrapper,
|
||||
table {
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.link.external::after
|
||||
{ content: "↗"
|
||||
; vertical-align: super
|
||||
; font-size: 1rem
|
||||
; line-height: 0
|
||||
}
|
||||
|
||||
.center
|
||||
{ align-items: "center"
|
||||
; justify-content: "center"
|
||||
; display: flex
|
||||
; max-width: 55%
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.center
|
||||
{ max-width: 90%
|
||||
}
|
||||
}
|
||||
|
||||
.navbar
|
||||
{ padding: 0 1rem
|
||||
; font-size: 1rem
|
||||
; color: var(--ds-velvet-grey)
|
||||
; height: 2rem
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.navbar {
|
||||
color: var(--ds-untitled-2)
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-list
|
||||
{ list-style-type: none
|
||||
; padding-left: 0
|
||||
; font-size: 1.2rem
|
||||
; display: flex
|
||||
; flex-direction: row
|
||||
; flex-wrap: wrap
|
||||
; column-gap: 1.5rem
|
||||
}
|
||||
|
||||
.navbar-list li
|
||||
{ display: inline
|
||||
; margin-top: 0
|
||||
}
|
||||
|
||||
#page-info
|
||||
{ list-style-type: none
|
||||
; padding-left: 0
|
||||
; font-size: 0.8rem
|
||||
; color: var(--ds-velvet-grey)
|
||||
; text-align: center
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#page-info {
|
||||
color: var(--ds-untitled-2)
|
||||
}
|
||||
}
|
||||
|
||||
.home-link
|
||||
{ text-decoration: none
|
||||
}
|
||||
|
||||
#references ul
|
||||
{ width: auto
|
||||
}
|
||||
|
||||
figcaption {
|
||||
margin-right: -60%;
|
||||
width: 50%;
|
||||
margin-top: 1.4rem;
|
||||
line-height: 1.3;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
figure.fullwidth {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
figure.fullwidth figcaption {
|
||||
margin-right: 0%;
|
||||
float: none;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
figcaption,
|
||||
figure.fullwidth figcaption {
|
||||
margin-right: 0%;
|
||||
float: none;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-section-message
|
||||
{ color: var(--ds-untitled-2);
|
||||
; font-style: italic
|
||||
; text-align: center
|
||||
; max-width: 55%
|
||||
; font-size: 1.5rem
|
||||
}
|
||||
10
doerg/src/net/deertopia/doerg/config.clj
Normal file
10
doerg/src/net/deertopia/doerg/config.clj
Normal file
@@ -0,0 +1,10 @@
|
||||
(ns net.deertopia.doerg.config
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[spec-dict.main :refer [dict]]))
|
||||
|
||||
(s/def ::config
|
||||
(s/keys :req []))
|
||||
|
||||
(def default {})
|
||||
|
||||
(def ^:dynamic *cfg* default)
|
||||
101
doerg/src/net/deertopia/doerg/element.clj
Normal file
101
doerg/src/net/deertopia/doerg/element.clj
Normal file
@@ -0,0 +1,101 @@
|
||||
(ns net.deertopia.doerg.element
|
||||
(:require [babashka.process :as p]
|
||||
[clojure.string :as str]
|
||||
[clojure.zip :as z]
|
||||
[babashka.fs :as fs]
|
||||
[clojure.java.io :as io]
|
||||
[cheshire.core :as json]
|
||||
[clojure.spec.alpha :as s]
|
||||
[spec-dict.main :refer [dict]]
|
||||
[net.deertopia.doerg.config :as cfg])
|
||||
(:refer-clojure :exclude [read-string]))
|
||||
|
||||
|
||||
(defonce ^:private uniorg-script-path-atom (atom nil))
|
||||
|
||||
(def ^:dynamic *uniorg-timeout-after-milliseconds*
|
||||
(* 10 1000))
|
||||
|
||||
(defn deref-with-timeout [process ms]
|
||||
(let [p (promise)
|
||||
process-future (future (deliver p @process))
|
||||
timeout-future (future (Thread/sleep ms)
|
||||
(future-cancel process-future)
|
||||
(p/destroy-tree process)
|
||||
(deliver p ::timed-out))]
|
||||
(if (= @p ::timed-out)
|
||||
(throw (ex-info (format "external command `%s' timed out after %.2fs."
|
||||
(str/join " " (:cmd process))
|
||||
(/ (double ms) 1000))
|
||||
{:process process
|
||||
:timed-out-after-milliseconds ms}))
|
||||
@p)))
|
||||
|
||||
(defn- camel->kebab [s]
|
||||
(->> (str/split s #"(?<=[a-z])(?=[A-Z])")
|
||||
(map str/lower-case)
|
||||
(str/join "-")))
|
||||
|
||||
(defn uniorg [& {:keys [in]
|
||||
:or {in *in*}}]
|
||||
(let [r (-> (p/process
|
||||
{:in in :out :string}
|
||||
"doerg-parser")
|
||||
(deref-with-timeout *uniorg-timeout-after-milliseconds*))]
|
||||
(if (zero? (:exit r))
|
||||
(-> r :out (json/parse-string (comp keyword camel->kebab))))))
|
||||
|
||||
(defn read-string [s]
|
||||
(with-in-str s
|
||||
(uniorg :in *in*)))
|
||||
|
||||
|
||||
|
||||
(defn greater-element?
|
||||
"Return truthy if `e` is a greater org-element; i.e. one that can
|
||||
have children."
|
||||
[e]
|
||||
;; Not 100% sure if this is a valid definition. It seems that
|
||||
;; Uniorg sets `:children` to an empty vector when a great element
|
||||
;; lacks children.
|
||||
(contains? e :children))
|
||||
|
||||
(defn org-element? [element]
|
||||
#_
|
||||
(s/valid? ::org-element element)
|
||||
(and (map? element)
|
||||
(contains? element :type)))
|
||||
|
||||
(defn of-type? [element type]
|
||||
(= (:type element) type))
|
||||
|
||||
|
||||
;;; Spec
|
||||
|
||||
(s/def ::org-element
|
||||
(dict {:type string?}
|
||||
^:opt {:contents-begin nat-int?
|
||||
:contents-end nat-int?
|
||||
:children (s/coll-of ::org-element
|
||||
:kind seq?)}))
|
||||
|
||||
|
||||
;;; Zipper
|
||||
|
||||
(defn doerg-zip [document]
|
||||
(z/zipper greater-element?
|
||||
:children
|
||||
#(assoc %1 :children %2)
|
||||
document))
|
||||
|
||||
(defn cata
|
||||
"Catamorphism on a zipper."
|
||||
[loc f]
|
||||
(let [loc* (if-some [child (z/down loc)]
|
||||
(loop [current child]
|
||||
(let [current* (cata current f)]
|
||||
(if-some [right (z/right current*)]
|
||||
(recur right)
|
||||
(z/up current*))))
|
||||
loc)]
|
||||
(z/replace loc* (f (z/node loc*)))))
|
||||
37
doerg/src/net/deertopia/doerg/html.clj
Normal file
37
doerg/src/net/deertopia/doerg/html.clj
Normal file
@@ -0,0 +1,37 @@
|
||||
(ns net.deertopia.doerg.html
|
||||
"Common HTML elements and utilities"
|
||||
(:require [clojure.java.io :as io]))
|
||||
|
||||
#_
|
||||
(def navbar
|
||||
"Hiccup element for Deertopia.net's navbar."
|
||||
[:nav.navbar
|
||||
[:ol.navbar-list
|
||||
[:li
|
||||
[:a.home-link {:href "/"}
|
||||
"🦌 deertopia.net"]]
|
||||
[:li
|
||||
[:a.home-link {:href "/graph"}
|
||||
"graph"]]
|
||||
#_
|
||||
[:li
|
||||
[:a.home-link {:onclick "alert('unimplemented }:(')"}
|
||||
"search"]]]])
|
||||
|
||||
(def viewport
|
||||
[:meta {:name "viewport"
|
||||
:content "width=device-width, initial-scale=1.0"}])
|
||||
|
||||
(def charset
|
||||
[:meta {:charset "utf-8"}])
|
||||
|
||||
(def tuftesque
|
||||
#_
|
||||
[:link {:rel "stylesheet"
|
||||
:type "text/css"
|
||||
:href "/resources/tuftesque.css"}]
|
||||
[:style
|
||||
(slurp (io/resource "net/deertopia/doerg/tuftesque.css"))])
|
||||
|
||||
(def head
|
||||
(list viewport charset tuftesque))
|
||||
@@ -1,17 +0,0 @@
|
||||
(ns net.deertopia.doerg.parse
|
||||
(:require [babashka.process :as p]
|
||||
[babashka.fs :as fs]
|
||||
[clojure.java.io :as io]
|
||||
[cheshire.core :as json])
|
||||
(:refer-clojure :exclude [read-string]))
|
||||
|
||||
(defonce ^:private uniorg-script-path-atom (atom nil))
|
||||
|
||||
(defn- uniorg []
|
||||
@(p/process {:in (slurp "/home/msyds/org/20260124165717-if_so_in_korean.org")
|
||||
:out :string}
|
||||
"doerg-parser"))
|
||||
|
||||
(defn read-string [s]
|
||||
#_
|
||||
(p/process "node" (uniorg-script-path)))
|
||||
76
doerg/src/net/deertopia/doerg/render.clj
Normal file
76
doerg/src/net/deertopia/doerg/render.clj
Normal file
@@ -0,0 +1,76 @@
|
||||
(ns net.deertopia.doerg.render
|
||||
(:require [net.deertopia.doerg.element :as element]
|
||||
[clojure.tools.logging :as l]
|
||||
[clojure.tools.logging.readable :as lr]
|
||||
[net.deertopia.doerg.html :as doerg-html]
|
||||
[clojure.zip :as z]))
|
||||
|
||||
;;; Top-level API
|
||||
|
||||
(defmulti org-element
|
||||
"Render an Org element to Hiccup."
|
||||
#(do (assert (element/org-element? %)
|
||||
"Not an org-node!")
|
||||
(:type %)))
|
||||
|
||||
(defmulti org-link
|
||||
"Render an Org-mode link element to Hiccup. Dispatches on link
|
||||
type/protocol."
|
||||
#(do (assert (element/of-type? % "link"))
|
||||
(:link-type %)))
|
||||
|
||||
(defmulti org-special-block
|
||||
"Render an Org-mode special block to Hiccup. Dispatches on special
|
||||
block type (as in #+begin_«type» … #+end_«type»)."
|
||||
#(do (assert (element/of-type? % "special-block"))
|
||||
(:block-type %)))
|
||||
|
||||
(defmulti org-keyword
|
||||
"Render an Org-mode keyword."
|
||||
#(do (assert (element/of-type? % "keyword"))
|
||||
(:key %)))
|
||||
|
||||
(def ^:dynamic ^:private *document-info*)
|
||||
|
||||
(declare ^:private gather-footnotes renderer-error)
|
||||
|
||||
(defn org-element-recursive
|
||||
"Recursively render an Org-mode element to Hiccup."
|
||||
[e]
|
||||
(let [loc (element/doerg-zip e)]
|
||||
(-> loc
|
||||
(element/cata
|
||||
(fn [node]
|
||||
(try (org-element node)
|
||||
(catch Throwable e
|
||||
(lr/error e "Error in renderer" {:node node})
|
||||
(renderer-error e)))))
|
||||
z/node)))
|
||||
|
||||
(defn org-document
|
||||
"Recursively render an Org-mode document to Hiccup."
|
||||
[doc]
|
||||
(let [loc (element/doerg-zip doc)]
|
||||
(binding [*document-info* {:footnotes (gather-footnotes loc)}]
|
||||
(let [rendered (org-element-recursive doc)]
|
||||
[:html
|
||||
[:head
|
||||
[:title "org document"]
|
||||
doerg-html/viewport
|
||||
doerg-html/charset
|
||||
doerg-html/tuftesque]
|
||||
[:body
|
||||
[:article
|
||||
rendered]]]))))
|
||||
|
||||
|
||||
|
||||
(defn- gather-footnotes [loc]
|
||||
{})
|
||||
|
||||
|
||||
|
||||
(defn- renderer-error
|
||||
"Render a `Throwable` to display within the document."
|
||||
[e]
|
||||
"aaaa an error!")
|
||||
8
doerg/test/net/deertopia/doerg/config_test.clj
Normal file
8
doerg/test/net/deertopia/doerg/config_test.clj
Normal file
@@ -0,0 +1,8 @@
|
||||
(ns net.deertopia.doerg.config-test
|
||||
(:require [net.deertopia.doerg.config :as sut]
|
||||
[clojure.test :as t]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(t/deftest default-config-is-config
|
||||
(t/testing "default config is valid"
|
||||
(t/is (s/valid? ::sut/config sut/default))))
|
||||
54
doerg/test/net/deertopia/doerg/element_test.clj
Normal file
54
doerg/test/net/deertopia/doerg/element_test.clj
Normal file
@@ -0,0 +1,54 @@
|
||||
(ns net.deertopia.doerg.element-test
|
||||
(:require [net.deertopia.doerg.element :as sut]
|
||||
[babashka.process :as p]
|
||||
[clojure.test :as t]
|
||||
[clojure.zip :as z]
|
||||
[clojure.java.io :as io]))
|
||||
|
||||
(defn sleep-vs-timeout [& {:keys [sleep timeout]}]
|
||||
(sut/deref-with-timeout
|
||||
(p/process "sleep" (format "%ds" sleep))
|
||||
(* timeout 1000)))
|
||||
|
||||
;; Ideally we would test the following property:
|
||||
;;
|
||||
;; For natural numbers n and m, evaluating the form
|
||||
;; (sut/deref-with-timeout
|
||||
;; (p/process "sleep" (format "%ds" n))
|
||||
;; (* m 1000))
|
||||
;; will throw an exception iff n < m (probably with some margin of
|
||||
;; error lol).
|
||||
;;
|
||||
;; But, this is not something that we want to run dozens-to-hundreds
|
||||
;; of times. }:p
|
||||
|
||||
(t/deftest long-sleep-vs-short-timeout
|
||||
(t/testing "long sleep vs. short timeout"
|
||||
(t/is (thrown-with-msg?
|
||||
Exception #".*timed out.*"
|
||||
(sleep-vs-timeout :sleep 5 :timeout 1)))))
|
||||
|
||||
(t/deftest short-sleep-vs-long-timeout
|
||||
(t/testing "short sleep vs. long timeout"
|
||||
(t/is (instance? babashka.process.Process
|
||||
(sleep-vs-timeout :sleep 1 :timeout 5)))))
|
||||
|
||||
(defn- first-child-of-type [parent type]
|
||||
(some #(and (sut/of-type? % type) %) (:children parent)))
|
||||
|
||||
(t/deftest known-greater-elements
|
||||
(t/testing "known greater elements satisfy `greater-element?`"
|
||||
(let [s (-> "net/deertopia/doerg/element_test/greater-elements.org"
|
||||
io/resource slurp)
|
||||
root (sut/read-string s)
|
||||
section (first-child-of-type root "section")
|
||||
headline (first-child-of-type section "headline")
|
||||
headline-text (first-child-of-type headline "text")
|
||||
paragraph (first-child-of-type section "paragraph")
|
||||
paragraph-text (first-child-of-type paragraph "text")]
|
||||
(t/is (sut/greater-element? root))
|
||||
(t/is (sut/greater-element? section))
|
||||
(t/is (sut/greater-element? headline))
|
||||
(t/is (not (sut/greater-element? headline-text)))
|
||||
(t/is (sut/greater-element? paragraph))
|
||||
(t/is (not (sut/greater-element? paragraph-text))))))
|
||||
@@ -0,0 +1,5 @@
|
||||
#+title: greater elements test
|
||||
|
||||
* a headline/section
|
||||
|
||||
this should be a greater element
|
||||
Reference in New Issue
Block a user