ideate
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# Hiccup for Typst Content - Prototype
|
||||
|
||||
## The Idea
|
||||
|
||||
Hiccup represents markup as nested data structures:
|
||||
```clojure
|
||||
[:tag {attrs} & children]
|
||||
```
|
||||
|
||||
## Syntax Mapping
|
||||
|
||||
### Clojure-Typst Hiccup → Typst Output
|
||||
|
||||
```clojure
|
||||
;; Heading
|
||||
[:heading {:level 2} "My Section"]
|
||||
;; => #heading(level: 2)[My Section]
|
||||
|
||||
;; Inline formatting (paragraphs are implicit in Typst)
|
||||
["Hello " [:strong "world"] " and " [:emph "goodbye"]]
|
||||
;; => Hello #strong[world] and #emph[goodbye]
|
||||
|
||||
;; Lists (children are direct content, not :item tags)
|
||||
[:list
|
||||
"First thing"
|
||||
"Second thing"
|
||||
[[:strong "Important"] " third thing"]]
|
||||
;; => #list(
|
||||
;; [First thing],
|
||||
;; [Second thing],
|
||||
;; [#strong[Important] third thing]
|
||||
;; )
|
||||
|
||||
;; Tables (cells are direct content, not :cell tags)
|
||||
[:table {:columns 3}
|
||||
"A" "B" "C"
|
||||
"1" "2" "3"]
|
||||
;; => #table(columns: 3,
|
||||
;; [A], [B], [C],
|
||||
;; [1], [2], [3]
|
||||
;; )
|
||||
|
||||
;; Raw content blocks
|
||||
[:raw "= Direct Typst Markup\nJust passed through"]
|
||||
|
||||
;; Code blocks (use :raw with :lang)
|
||||
[:raw {:lang "python" :block true} "print('hello')"]
|
||||
;; => #raw(lang: "python", block: true, "print('hello')")
|
||||
|
||||
;; Math
|
||||
[:math "x^2 + y^2 = z^2"]
|
||||
;; => $x^2 + y^2 = z^2$
|
||||
|
||||
[:math {:block true} "sum_(i=0)^n i = (n(n+1))/2"]
|
||||
;; => $ sum_(i=0)^n i = (n(n+1))/2 $
|
||||
|
||||
;; Images
|
||||
[:image {:width "50%"} "diagram.png"]
|
||||
;; => #image("diagram.png", width: 50%)
|
||||
|
||||
;; Links
|
||||
[:link {:dest "https://typst.app"} "Typst"]
|
||||
;; => #link("https://typst.app")[Typst]
|
||||
```
|
||||
|
||||
## Full Document Example
|
||||
|
||||
```clojure
|
||||
(def my-doc
|
||||
[[:heading {:level 1} "My Paper"]
|
||||
|
||||
"This is the introduction. We discuss "
|
||||
[:emph "important things"] "."
|
||||
|
||||
[:parbreak]
|
||||
|
||||
[:heading {:level 2} "Methods"]
|
||||
|
||||
"We used the following approach:"
|
||||
|
||||
[:list
|
||||
"Step one"
|
||||
"Step two"
|
||||
"Step three"]
|
||||
|
||||
[:heading {:level 2} "Results"]
|
||||
|
||||
"The result is " [:$ "x = 42"] "."
|
||||
|
||||
[:table {:columns 2}
|
||||
[:strong "Input"] [:strong "Output"]
|
||||
"1" "1"
|
||||
"2" "4"
|
||||
"3" "9"]])
|
||||
```
|
||||
|
||||
**Compiles to:**
|
||||
|
||||
```typst
|
||||
#heading(level: 1)[My Paper]
|
||||
|
||||
This is the introduction. We discuss #emph[important things].
|
||||
|
||||
#heading(level: 2)[Methods]
|
||||
|
||||
We used the following approach:
|
||||
|
||||
#list(
|
||||
[Step one],
|
||||
[Step two],
|
||||
[Step three]
|
||||
)
|
||||
|
||||
#heading(level: 2)[Results]
|
||||
|
||||
The result is $x = 42$.
|
||||
|
||||
#table(columns: 2,
|
||||
[#strong[Input]], [#strong[Output]],
|
||||
[1], [1],
|
||||
[2], [4],
|
||||
[3], [9]
|
||||
)
|
||||
```
|
||||
|
||||
## Mixing Code and Content
|
||||
|
||||
```clojure
|
||||
;; Define a reusable component
|
||||
(defn author-block [name institution]
|
||||
[:block {:align "center"}
|
||||
[:strong name]
|
||||
[:linebreak]
|
||||
[:emph institution]])
|
||||
|
||||
;; Use it with data
|
||||
(def authors
|
||||
[{:name "Alice" :inst "MIT"}
|
||||
{:name "Bob" :inst "Stanford"}])
|
||||
|
||||
;; Generate content programmatically
|
||||
[[:heading "Authors"]
|
||||
(map #(author-block (:name %) (:inst %)) authors)]
|
||||
```
|
||||
|
||||
## Special Forms for Content
|
||||
|
||||
```clojure
|
||||
;; Conditional content (strings become paragraphs naturally)
|
||||
(if peer-reviewed?
|
||||
"This paper was peer reviewed."
|
||||
"Preprint - not yet reviewed.")
|
||||
|
||||
;; Loop over data (items are direct content in lists)
|
||||
[:list
|
||||
(for [item items]
|
||||
(format-item item))]
|
||||
|
||||
;; Let bindings for intermediate content
|
||||
(let [title (get-title data)
|
||||
abstract (get-abstract data)]
|
||||
[[:heading title]
|
||||
abstract])
|
||||
```
|
||||
|
||||
## The Alternative: Lisp for Code Only
|
||||
|
||||
If hiccup feels too heavy, we could just use Lisp for logic and escape to raw Typst:
|
||||
|
||||
```clojure
|
||||
(defn factorial [n]
|
||||
(if (<= n 1)
|
||||
1
|
||||
(* n (factorial (dec n)))))
|
||||
|
||||
;; Escape to typst content
|
||||
#typst{
|
||||
= Results
|
||||
|
||||
The factorial of 5 is ~(factorial 5).
|
||||
|
||||
Here's a table of factorials:
|
||||
~(for [i (range 1 11)]
|
||||
(str "- " i "! = " (factorial i) "\n"))
|
||||
}
|
||||
```
|
||||
|
||||
## Questions
|
||||
|
||||
1. **Implicit paragraphs?** Should adjacent strings auto-wrap in `[:p ...]`?
|
||||
2. **Shorthand syntax?** Maybe `:#strong` instead of `[:strong ...]`?
|
||||
3. **Component system?** How fancy do we get with reusable components?
|
||||
4. **Escaping?** How to include literal `[` or other special chars?
|
||||
5. **Whitespace handling?** Typst is whitespace-sensitive in content mode
|
||||
|
||||
## Verdict?
|
||||
|
||||
Hiccup gives us:
|
||||
- ✅ Pure data representation of documents
|
||||
- ✅ Full programmatic control
|
||||
- ✅ Composable components
|
||||
- ✅ Familiar to Clojure devs
|
||||
|
||||
But:
|
||||
- ❌ More verbose than raw Typst markup
|
||||
- ❌ Loses Typst's nice content syntax
|
||||
- ❌ Another layer to learn
|
||||
|
||||
**Hybrid approach might be best:** Lisp for code/logic, with easy escape to Typst content syntax when you just want to write prose.
|
||||
Reference in New Issue
Block a user