basic rendering!
This commit is contained in:
@@ -1,6 +1,14 @@
|
|||||||
{
|
{
|
||||||
"lock-version": 4,
|
"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-deps": [
|
||||||
{
|
{
|
||||||
"mvn-path": "babashka/fs/0.5.24/fs-0.5.24.jar",
|
"mvn-path": "babashka/fs/0.5.24/fs-0.5.24.jar",
|
||||||
|
|||||||
@@ -2,5 +2,7 @@
|
|||||||
babashka/fs {:mvn/version "0.5.24"}
|
babashka/fs {:mvn/version "0.5.24"}
|
||||||
org.clojure/core.match {:mvn/version "1.1.0"}
|
org.clojure/core.match {:mvn/version "1.1.0"}
|
||||||
cheshire/cheshire {:mvn/version "6.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"]}
|
: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