~melchizedek6809/nujel.net

b725e3cb65c0c7549dde957bba974edbedf79f42 — Ben (X13/Arch) 1 year, 7 months ago 2cc5413
Updated for new nujel
M content/config.nuj => content/config.nuj +7 -6
@@ 1,6 1,7 @@
@[ :title "Nujel.net"
   :logo "img/logo.png"
   :logo-text "Nujel"
   :imprint "<br/>Benjamin Vincent Schulenburg - Wöhlerstr. 1A - 01139 Dresden - GERMANY"
   :contact "If you want to contact me, you can send me an <a href=\"mailto:ben@wolkenwelten.net\">E-Mail</a>, or write me via Discord where I am available as <quote>Melchizedek6809#4065</quote>"
   :favicon "favicon.png" ]
{ :title "Nujel.net"
  :logo "img/logo.png"
  :logo-text "Nujel"
  :imprint "<br/>Benjamin Vincent Schulenburg - Wöhlerstr. 1A - 01139 Dresden - GERMANY"
  :contact "If you want to contact me, you can send me an <a href=\"mailto:ben@wolkenwelten.net\">E-Mail</a>, or write me via Discord where I am available as <quote>Melchizedek6809#4065</quote>"
  :favicon "favicon.png"
}

M content/docs.html => content/docs.html +1 -2
@@ 9,6 9,5 @@ hide-prev-next: #t

<p>Apart from the source itself, there is this "book" which might help if you are already experienced with another Lisp dialect.</p>
<nav>
    {{ [join [sort [map [navigation 1] [fn [v] [render-link v]]]] "<br/>"] }}
    {{ (join (sort (map (navigation 1) (fn (v) (render-link v)))) "<br/>") }}
</nav>


M content/language/chapters/1-1_parentheses.html => content/language/chapters/1-1_parentheses.html +9 -12
@@ 5,24 5,21 @@ date: "2022-08-31"

<h1>Parentheses</h1>
<p>Nujel uses the S-Expression notation as used by languages such as <i>Lisp</i>, <i>Scheme</i> and <i>Clojure</i>, but with one difference: Nujel defaults to using brackets instead of parentheses. This is mainly because brackets are much easier to type with a default US keyboard layout. You can still use parentheses if you must, since the two are completely interchangeable.</p>
{{ [component :Nujel #nil
"[+ 1 2]
3

(+ 1 2)
3"] }}
{{ (component :Nujel #nil
"(+ 1 2)
3") }}

<p>Dotted pairs are also supported:</p>
{{ [component :Nujel #nil
"[car '[a . b]]
{{ (component :Nujel #nil
"(car '(a . b))
a

[cdr '[a . b]]
(cdr '(a . b))
b

[car [cons 1 2]]
(car (cons 1 2))
1

[cdr [cons 1 2]]
2"] }}
(cdr (cons 1 2))
2") }}
<p>And as you can see Nujel uses <code>car</code> and <code>cdr</code> to access the first or rest parts of lists. To build a new pair you can use <code>cons</code>.</p>

M content/language/chapters/1-2_comments.html => content/language/chapters/1-2_comments.html +3 -3
@@ 5,14 5,14 @@ date: "2022-08-31"

<h1>Comments</h1>
<p>Comments use Scheme syntax, and some SRFI's have been implemented directly.</p>
{{ [component :Nujel #nil
{{ (component :Nujel #nil
"; A single semicolon comments out everything until the next line
(+ 1 #;2 3) ; You can use #; to comment out the following form, should be SRFI-62 compatible!
4
#|
 | Nujel also allows for SRFI-30 like nested Multi-line comments
 |#
[comment [println \"A message, never to be printed.\"]] ; Nujel also has a comment macro, this however returns #nil unlike #;"] }}
(comment (println \"A message, never to be printed.\")) ; Nujel also has a comment macro, this however returns #nil unlike #;") }}
</pre>

<p>Apart from that it is recommended to use a single <code>;</code> at the end of a line to comment whatever is happening on that particular line.</p>


@@ 23,4 23,4 @@ date: "2022-08-31"

<p>The comment macro is mostly useful for usage with <code>nujel-mode</code> since you can easily evaluate commented out forms. Beware however that everything within a comment macro needs to be a valid Nujel S-Expression!</p>

<p>This is in accordance with <i>Scheme</i>, <i>Common Lisp</i> as well as <i>Clojure</i> best practices.</p>
\ No newline at end of file
<p>This is in accordance with <i>Scheme</i>, <i>Common Lisp</i> as well as <i>Clojure</i> best practices.</p>

M content/language/chapters/1-3_numbers.html => content/language/chapters/1-3_numbers.html +3 -3
@@ 7,7 7,7 @@ date: "2022-08-31"

<p>Nujel supports normal decimal notation and treats <code>,</code>  and <code>_</code> as whitespace characters so you can split big numbers for increased legibility.  There is also special syntax for binary, octal and hexadecimal literals, scientific notation however is <b>not</b> supported.</p>

{{ [component :Nujel #nil
{{ (component :Nujel #nil
"9 ; probably not that suprising
9



@@ 39,5 39,5 @@ date: "2022-08-31"
:read-error

-100 ; You can also write negative numbers
-100"] }}
</pre>
\ No newline at end of file
-100") }}
</pre>

M content/language/chapters/1-4_arithmetic.html => content/language/chapters/1-4_arithmetic.html +17 -17
@@ 14,27 14,27 @@ date: "2022-08-31"
<p>Nujel supports most standard operators for integers as well as floating-point numbers (or fixnums and flonums). There is no operation called mod/modulo, but instead it is called <code>rem</code>, just like in <i>Clojure</i>.</p>
<p>Division will also always return a flonum, if you want to do an integer division you can use <code>div/int</code> instead. Apart from that doing operations between fixnums and flonums will result in a flonum, with fixnums only being returned when both operands are fixnums.</p>

{{ [component :Nujel #nil
"[+ 1 2 3 4] ; You can add as many numbers as you want
{{ (component :Nujel #nil
"(+ 1 2 3 4) ; You can add as many numbers as you want
10

[+ 1 2 [+ 3 4]]
(+ 1 2 (+ 3 4))
10

[+ [+ 1 2] [+ 3 4]]
(+ (+ 1 2) (+ 3 4))
10

[+ 1 -2] ; You can also use negative numbers in additions
(+ 1 -2) ; You can also use negative numbers in additions
-1

[+ 1 [- 2]] ; - can also be used to negate numbers
(+ 1 (- 2)) ; - can also be used to negate numbers
-1

[+ 1 [- [+ 1 1]]] ; Results of a calculation can also be negated
(+ 1 (- (+ 1 1))) ; Results of a calculation can also be negated
-1

[+ 1 \"drei\"] ; You can only calculate with numbers
:type-error"] }}
(+ 1 \"drei\") ; You can only calculate with numbers
:type-error") }}

<h3>2. Exceptions</h3>
<p>Dividing through zero generates an exception, which is probably not that suprising, this however also extends to floating point operations where NaN as well as +/-Inf are invalid values that Nujel will not create, instead raising an Exception.</p>


@@ 44,21 44,21 @@ date: "2022-08-31"
    <p>This seems to be an inconsitency in how integers and floating-point numbers are handled, since I can't think of time where I wanted a NaN/Inf value it seems reasonable to me to handle this case just like integers, meaning raising an exception.</p>
</box-wrap>

{{ [component :Nujel #nil
"[/ 10 0]
{{ (component :Nujel #nil
"(/ 10 0)
:float-inf

[div/int 10 0]
:divide-by-zero"] }}
(div/int 10 0)
:divide-by-zero") }}

<h3>3. Conversion</h3>
<p>Nujel also provides a couple of functions for converting between the different numeric formats.</p>
<p>In general, Nujel will never implicitly convert a flonum to a fixnum, since that might mean loosing precision, if it does it is considered a bug in the implementation.</p>
<p>It will however implicitly convert fixnum's to flownum's whenever that seems to be the least suprising choice.</p>
{{ [component :Nujel #nil
"[int 1.1]
{{ (component :Nujel #nil
"(int 1.1)
1

[float 1]
1.0"] }}
(float 1)
1.0") }}
</pre>

M content/language/chapters/1-5_symbols-keywords.html => content/language/chapters/1-5_symbols-keywords.html +11 -11
@@ 13,18 13,18 @@ date: "2022-09-07"

<h3>1. Symbols</h3>
<p>Symbols in Nujel behave pretty much the same as in most other Lisps, in that they are implictly looked up unless quoted.</p>
{{ [component :Nujel #nil
{{ (component :Nujel #nil
"asd ; Every symbol is implicitly looked up, with an exception thrown on failure
; => :unbound-variable

'asd ; By quoting a symbol this implicit look up can be disabled
asd

[string->symbol \"asd\"] ; You can also turn any old string into a symbol, this is sometimes called interning a string
(string->symbol \"asd\") ; You can also turn any old string into a symbol, this is sometimes called interning a string
asd

[== 'asd [string->symbol \"asd\"]] ; The two values actually point to the same underlying data structure
#t"] }}
(== 'asd (string->symbol \"asd\")) ; The two values actually point to the same underlying data structure
#t") }}

<box-wrap box-color="yellow">
    <h3>Implementation detail</h3>


@@ 34,18 34,18 @@ asd
<h3>2. Keywords</h3>
<p>Since quoting can become quite tedious there is another very similar data type, keywords. They share most functionality with symbols with the main difference being that they will not be implicitly looked up.</p>
<p>By pre- or suffixing a symbol with a <code>:</code> the reader will return a keywords.</p>
{{ [component :Nujel #nil
{{ (component :Nujel #nil
":asd ; A keyword
:asd
    

asd: ; Another keyword
asd:
    
[= asd: :asd] ; It doesn't matter where we put the colon

(= asd: :asd) ; It doesn't matter where we put the colon
#t
    
[= :asd [symbol->keyword 'asd]]
#t"] }}

(= :asd (symbol->keyword 'asd))
#t") }}

<h3>3. Usage</h3>
<p>Regarding their usage, keywords should be used when the keyword itself has a useful meaning, symbols should be used to label other values which could work just as well with another symbol associated.</p>

M content/language/chapters/1-6_quote-quasiquote.html => content/language/chapters/1-6_quote-quasiquote.html +9 -12
@@ 10,15 10,12 @@ date: "2022-08-31"
<p>If you want to simply return a value as is, meaning a symbol without resolving it, or a list instead of evaluating it as a from then you can use <code>quote</code>.</p>
<p>Since this is quite the common occurence there is a special reader form to make this more convenient: <code>'</code>. By prefixing any symbol with a single quote it will be used as is, without any attempt to resolve it.</p>
<p>Quote can not only be used to inhibit the implicit behaviour of symbol resolution, but can also stop expressions from being evaluated and instead being passed along as simple lists. This allows for the easy inclusion of literal lists.</p>
{{ [component :Nujel #nil
{{ (component :Nujel #nil
"'a ; You can quote symbols
a

'[1 2 3] ; Or forms, making them plain lists
[1 2 3]

'(1 2 3) ; With parentheses or brackets
[1 2 3]"] }}
'(1 2 3) ; Or forms, making them plain lists
(1 2 3)") }}

<h3>2. Quasiquote</h3>
<p>What is one supposed to do if one wants to quote only parts of an expression though? You could build it up using <code>cons</code>,<code>list</code> and quote but there is a more convenient way for that: <code>quasiquote</code>.</p>


@@ 27,13 24,13 @@ a
    <h3>Reasoning</h3>
    <p>I chose the Clojure style over Scheme/Lisp because using a <code>,</code> for unquoting would make it non-whitespace, making its use as a thousands separator very problematic.</p>
</box-wrap>
{{ [component :Nujel #nil
"`[1 2 ~[+ 1 1 1]] ; To unquote you can use a tilde
[1 2 3]
{{ (component :Nujel #nil
"`(1 2 ~(+ 1 1 1)) ; To unquote you can use a tilde
(1 2 3)

`[1 ~@[list 2 3]] ; And ~@ for unquote-splicing
[1 2 3]"] }}
`(1 ~@(list 2 3)) ; And ~@ for unquote-splicing
(1 2 3)") }}
<box-wrap box-color="yellow">
    <h3>Implementation detail</h3>
    <p>Unlike in other Lisp/Scheme implementations Quasiquote is just a regular macro, this shouldn't make a difference in most cases but might trip you up if you delve deeper into the runtime.</p>
</box-wrap>
\ No newline at end of file
</box-wrap>

M content/language/chapters/1-7_variables.html => content/language/chapters/1-7_variables.html +11 -11
@@ 8,23 8,23 @@ date: "2022-09-02"

<h3>1. Defining variables</h3>
<p>You can define new variables using <code>def</code> and give old variables a new value using <code>set!</code>.</p>
{{ [component :Nujel #nil
{{ (component :Nujel #nil
"my-temp ; You can access a variables value by evaluating the symbol
:unbound-variable

[def my-temp 123] ; Of course it needs to be defined first
(def my-temp 123) ; Of course it needs to be defined first
123

my-temp
123"] }}
123") }}

<h3>2. Setting variables</h3>
<p>If you wish to change the value already associated with a given symbol, you can use <code>set!</code> to achieve that.</p>
{{ [component :Nujel #nil
"[def my-temp 123] ; Gotta define it first before we can use set!
[set! my-temp 234]
{{ (component :Nujel #nil
"(def my-temp 123) ; Gotta define it first before we can use set!
(set! my-temp 234)
my-temp
234"] }}
234") }}

<h3>3. Let blocks</h3>
<p>You can use <code>let</code> to create a new context for assignments, you still have access to all symbols defined in parent contexts.</p>


@@ 33,8 33,8 @@ my-temp
    <p>Currently you can shadow already defined variables, this is highly discouraged and will probably become an error in later Nujel versions. This is because it has already led to some bugs and makes some optimizations more complicated.</p>
</box-wrap>
<p>Unlike most other Lisps using <code>let</code> is discouraged in most situations, since you can just use <code>def</code> within a function call in order to define temporary variables, it is available though and should work like in <i>Scheme</i> or <i>CL</i>.</p>
{{ [component :Nujel #nil
"[let [[new-temp 123]]
     [println new-temp]] ; => 123
{{ (component :Nujel #nil
"(let ((new-temp 123))
     (println new-temp)) ; => 123
new-temp
:unbound-variable"] }}
:unbound-variable") }}

M content/language/chapters/1-8_functions.html => content/language/chapters/1-8_functions.html +28 -28
@@ 7,50 7,50 @@ date: "2022-09-01"
<p>Functions use <code>defn</code> and <code>fn</code>, just like <i>Clojure</i>, however in Nujel we can freely choose whether we want to use brackets or parentheses.</p>

<h3>1. Defining functions</h3>
<p>Defining a function is best done with the <code>[defn]</code> macro if it is named, or <code>[fn]</code> for anonymous functions.</p>
{{ [component :Nujel #nil
"[defn double [α] [* 2 α]]
[double 2]
<p>Defining a function is best done with the <code>(defn)</code> macro if it is named, or <code>(fn)</code> for anonymous functions.</p>
{{ (component :Nujel #nil
"(defn double (a) (* 2 a))
(double 2)
4

[def double [fn [b] [* 2 b]]]
[double 2]
4"] }}
(def double (fn (b) (* 2 b)))
(double 2)
4") }}

<h3>2. Defining functions with a rest argument</h3>
<p>In order to define a function that has a certain number of named arguments, and then a final catch-all argument you can use the dotted pair notation, just like in <i>Scheme</i>.</p>
{{ [component :Nujel #nil
"[defn multiply-vals [val . l]
      [map l [fn [v] [* v val]]]]
[multiply-vals 2 1 2 3]
[2 4 6]"] }}
{{ (component :Nujel #nil
"(defn multiply-vals (val . l)
      (map l (fn (v) (* v val))))
(multiply-vals 2 1 2 3)
(2 4 6)") }}

<h3>3. Defining a functions with any amount of arguments</h3>
<p>If you want to define a function that can take an arbitrary amount of arguments, you can just omit the brackets surrounding the argument list, this is just like you may be used from <i>Scheme</i>.</p>
{{ [component :Nujel #nil
"[defn my-list l l]
[my-list 1 2 3 4]
[1 2 3 4]"] }}
{{ (component :Nujel #nil
"(defn my-list l l)
(my-list 1 2 3 4)
(1 2 3 4)") }}

<h3>4. Documenting functions</h3>
<p>You can document your functions behaviour by having your functions start with a string literal (don't worry it will be optimized out of the final bytecode).</p>
<p>Adding multiple string literals to the beginning results in the docstring being all strings joined by a linebreak, this makes it easier to add multi-line docstrings.</p>
<p>In order to look up the docstring for any given function (even built-in ones) you can use the <code>[describe]</code> function </p>
{{ [component :Nujel #nil
"[defn double [α]
      \"Return α multiplied by 2\"
      [* 2 α]]
[double 2]
4"] }}
<p>In order to look up the docstring for any given function (even built-in ones) you can use the <code>(describe)</code> function </p>
{{ (component :Nujel #nil
"(defn double (a)
      \"Return a multiplied by 2\"
      (* 2 a))
(double 2)
4") }}

<h3>5. Function decorators</h3>
<p>In order to tell the compiler some additional information about the function you are declaring, you can add keywords to the beginning of your function body.</p>
<p>Right now we only support <code>:export</code> and <code>:export-as</code>, <code>:inline</code> also exists but is still very buggy. More are very likely to follow though.</p>
{{ [component :Nujel #nil
"[defn double [α]
{{ (component :Nujel #nil
"(defn double (a)
      :inline
      \"Return α multiplied by 2\"
      [* 2 α]]
[double 2]
      (* 2 a))
(double 2)
4
;; Doesn't really make much difference here, but helps especially with some simple predicates like [zero?]"] }}
\ No newline at end of file
;; Doesn't really make much difference here, but helps especially with some simple predicates like (zero?)") }}

M content/language/chapters/1-9_modules.html => content/language/chapters/1-9_modules.html +21 -21
@@ 20,15 20,15 @@ date: "2022-08-31"
<h3>1. Importing from built-in modules</h3>
<p>Let's start with some simple example on how to import a function from a builtin stdlib module:</p>

{{ [component :Nujel #nil
"[import [red] :ansi]
[println [red \"Test\"]] ; This should be printed in red in most terminals
{{ (component :Nujel #nil
"(import (red) :ansi)
(println (red \"Test\")) ; This should be printed in red in most terminals

[import [blue green] :ansi] ; You can also import multiple symbols at once
[println [cat [blue \"Te\"] [green \"st\"]]]
(import (blue green) :ansi) ; You can also import multiple symbols at once
(println (cat (blue \"Te\") (green \"st\")))

[import [yellow :as ansi-yellow] :ansi] ; Renaming is also possible
[println [ansi-yellow \"Test\"]]"] }}
(import (yellow :as ansi-yellow) :ansi) ; Renaming is also possible
(println (ansi-yellow \"Test\"))") }}

<box-wrap box-color="yellow">
    <h3>Expected changes</h3>


@@ 37,33 37,33 @@ date: "2022-08-31"

<h3>2. Writing your own modules</h3>
<p>You don't need to do anything special, every Nujel source file can be treated as a module. When it is imported for the first time all top-level code will be executed and all values marked for :export will be added to a map. The name is also determined by the path in the filesystem.</p>
{{ [component :Nujel #nil
{{ (component :Nujel #nil
"; test.nuj
[defn test []
      :export  ; The easiest way to export is to decorate your [defn] forms with the :export keyword, it will export the function with the name specified
      [println \"Test has been called!\"]]
(defn test ()
      :export  ; The easiest way to export is to decorate your (defn) forms with the :export keyword, it will export the function with the name specified
      (println \"Test has been called!\"))

[defn test-2 []
(defn test-2 ()
      :export-as test-zwei  ; You can also use the :export-zwei decorator if you want different internal and external names
      [println \"Test has been called!\"]]
      (println \"Test has been called!\"))

[def internal-value 123]
[export exported-value internal-value] ; Exporting arbitrary values is best done with the [export] macro, it has pretty much the same form as [def]"] }}
(def internal-value 123)
(export exported-value internal-value) ; Exporting arbitrary values is best done with the (export) macro, it has pretty much the same form as (def)") }}

<h3>3. Importing your own modules</h3>
<p>To import modules from the filesystem, mainly ones you wrote yourself, you need to specify a relative path to the file, without the .nuj ending.</p>
{{ [component :Nujel #nil
{{ (component :Nujel #nil
"; test.nuj
[defn test []
(defn test ()
      :export
      [println \"Test has been called!\"]]
      (println \"Test has been called!\"))

; main.nuj
[import [test] \"./test\"]
[test] ; => \"Test has been called!\""] }}
(import (test) \"./test\")
(test) ; => \"Test has been called!\"") }}

<h3>4. Executing modules</h3>
<p>One uncommon feature of the Nujel module system might be that there is no big distinction between executables and libraries, executable modules just have to export a [main] function which then might be called with the commandline arguments.</p>
<p>One uncommon feature of the Nujel module system might be that there is no big distinction between executables and libraries, executable modules just have to export a (main) function which then might be called with the commandline arguments.</p>
<p>This enables the runtime to bundle multiple executable modules within the main executable, for example right now the REPL, Help screen and C Asset Code generator are all executable modules contained in the default stdlib.</p>
<p>Nujel defaults to executing the REPL module, but you can change that by for example specifying <code>nujel -m :games/guess</code> which should start a very simple guess the number game.</p>


M content/language/index.html => content/language/index.html +1 -1
@@ 17,5 17,5 @@ Most functions/macros borrow their name directly from <i>Clojure</i> or <i>Commo

<h2>Chapters</h2>
<nav>{{
  [join [sort [map [navigation #nil "language/chapters"] [fn [v] [render-link v]]]] "<br/>"]
  (join (sort (map (navigation #nil "language/chapters") (fn (v) (render-link v)))) "<br/>")
}}</nav>

M content/news.html => content/news.html +1 -1
@@ 5,5 5,5 @@ hide-prev-next: #t

<h1>News</h1>
{{
    [component :PostOverview]
    (component :PostOverview)
}}

M content/reference/core/generator.nuj => content/reference/core/generator.nuj +93 -93
@@ 1,106 1,106 @@
[import [pp-nujel] :pretty/nujel]
(import (pp-nujel) :pretty/nujel)

[defn closure-signature [f]
      [def args ""]
      [def arg-i 0]
      [def arguments [closure/arguments f]]
      [while arguments
        [if [pair? arguments]
            [when [car arguments] [set! args [fmt "{args} <span class=\"argument\" arg-i=\"{}\">{}</span>" [inc! arg-i] [car arguments]]]]
            [set! args [fmt "{args} <span class=\"argument rest-argument\" arg-i=\"{}\">{}</span>" [inc! arg-i] arguments]]]
        [cdr! arguments]]
      [fmt "<div class=\"signature\"><h1>{}</h1>{}</div>" [closure/name f] args]]
(defn closure-signature (f)
      (def args "")
      (def arg-i 0)
      (def arguments (closure/arguments f))
      (while arguments
        (if (pair? arguments)
            (when (car arguments) (set! args (fmt "{args} <span class=\"argument\" arg-i=\"{}\">{}</span>" (inc! arg-i) (car arguments))))
            (set! args (fmt "{args} <span class=\"argument rest-argument\" arg-i=\"{}\">{}</span>" (inc! arg-i) arguments)))
        (cdr! arguments))
      (fmt "<div class=\"signature\"><h1>{}</h1>{}</div>" (closure/name f) args))

[defn closure-html-arg [f arg]
      [def i [index-of arg ":"]]
      [when [< i 0] [return arg]]
      [return [fmt "<div class=\"docstring-arg-name\">{}</div><div class=\"docstring-arg-desc\">{}</div>" [string/cut arg 0 i] [color-args f [string/cut arg [inc i]]]]]]
(defn closure-html-arg (f arg)
      (def i (index-of arg ":"))
      (when (< i 0) (return arg))
      (return (fmt "<div class=\"docstring-arg-name\">{}</div><div class=\"docstring-arg-desc\">{}</div>" (string/cut arg 0 i) (color-args f (string/cut arg (inc i))))))

[defn closure-html-arguments [f args]
      [def i 0]
      [fmt "<div class=\"docstring-args-wrap\">{}</div>"
           [join [map [split args "\n"]
                      [fn [arg]
                          [fmt "<div class=\"docstring-arg\" arg-i=\"{}\">{}</div>" [inc! i] [closure-html-arg f arg]]]] ""]]]
(defn closure-html-arguments (f args)
      (def i 0)
      (fmt "<div class=\"docstring-args-wrap\">{}</div>"
           (join (map (split args "\n")
                      (fn (arg)
                          (fmt "<div class=\"docstring-arg\" arg-i=\"{}\">{}</div>" (inc! i) (closure-html-arg f arg)))) "")))

[defn closure-html-return-val [f v]
      [fmt "<div class=\"docstring-return-val\">{}</div>" [color-args f v]]]
(defn closure-html-return-val (f v)
      (fmt "<div class=\"docstring-return-val\">{}</div>" (color-args f v)))

[defn color-args [f text]
      [def i 0]
      [def args [closure/arguments f]]
      [while args
             [inc! i]
             [if [pair? args]
                 [def arg [car args]]
                 [def arg args]]
             [set! text [join [split text [string arg]] [fmt "<span class=\"arg-ref\" arg-i=\"{}\">{arg}</span>" i]]]
             [cdr! args]]
      [return text]]
(defn color-args (f text)
      (def i 0)
      (def args (closure/arguments f))
      (while args
             (inc! i)
             (if (pair? args)
                 (def arg (car args))
                 (def arg args))
             (set! text (join (split text (string arg)) (fmt "<span class=\"arg-ref\" arg-i=\"{}\">{arg}</span>" i)))
             (cdr! args))
      (return text))

[defn closure-html-documentation [f]
      [def raw [closure/documentation f]]
      [when-not raw [return "<p>No documentation available</p>"]]
      [def parts [split raw "\n\n"]]
      [def ret [fmt "<h4>Summary</h4><p class=\"docstring-intro\">{}</p>" [color-args f [car parts]]]]
      [cdr! parts]
      [def len [length parts]]
      [when [> len 2]
        [set! ret [fmt "{ret}<h4>Description</h4>"]]]
      [while [> len 2]
        [set! ret [fmt "{ret}<p class=\"docstring-desc\">{}</p>" [color-args f [car parts]]]]
        [cdr! parts]
        [dec! len]]
      [when [>= len 2]
        [set! ret [fmt "{ret}<h4>Arguments</h4>{}" [closure-html-arguments f [car parts]]]]
        [cdr! parts]
        [dec! len]]
      [when [>= len 1]
        [set! ret [fmt "{ret}<h4>Return value</h4>{}" [closure-html-return-val f [car parts]]]]
        [cdr! parts]
        [dec! len]]
      [return ret]]
(defn closure-html-documentation (f)
      (def raw (closure/documentation f))
      (when-not raw (return "<p>No documentation available</p>"))
      (def parts (split raw "\n\n"))
      (def ret (fmt "<h4>Summary</h4><p class=\"docstring-intro\">{}</p>" (color-args f (car parts))))
      (cdr! parts)
      (def len (length parts))
      (when (> len 2)
        (set! ret (fmt "{ret}<h4>Description</h4>")))
      (while (> len 2)
        (set! ret (fmt "{ret}<p class=\"docstring-desc\">{}</p>" (color-args f (car parts))))
        (cdr! parts)
        (dec! len))
      (when (>= len 2)
        (set! ret (fmt "{ret}<h4>Arguments</h4>{}" (closure-html-arguments f (car parts))))
        (cdr! parts)
        (dec! len))
      (when (>= len 1)
        (set! ret (fmt "{ret}<h4>Return value</h4>{}" (closure-html-return-val f (car parts))))
        (cdr! parts)
        (dec! len))
      (return ret))

[defn closure-html-examples [f]
      [def examples [meta f :tests]]
      [when examples
        [cat "<h4>Examples</h4><pre class=\"docstring-examples source source-nujel\">"
             [-> examples
                 [map [fn [e] [pp-nujel [fmt "{:?}\n{:?}\n" [cadr e] [car e]] :html]]]
                 [join "\n"]]
             "</pre>"]]]
(defn closure-html-examples (f)
      (def examples (meta f :tests))
      (when examples
        (cat "<h4>Examples</h4><pre class=\"docstring-examples source source-nujel\">"
             (-> examples
                 (map (fn (e) (pp-nujel (fmt "{:?}\n{:?}\n" (cadr e) (car e)) :html)))
                 (join "\n"))
             "</pre>")))

[defn closure-html-related [f]
      [def rel [meta f :related]]
      [when rel
        [cat "<h4>Related</h4><div class=\"docstring-related-wrap\">"
             [-> rel
                 [map [fn [e] [fmt "{} "  e]]]
                 [join ""]]
             "</div>"]]]
(defn closure-html-related (f)
      (def rel (meta f :related))
      (when rel
        (cat "<h4>Related</h4><div class=\"docstring-related-wrap\">"
             (-> rel
                 (map (fn (e) (fmt "{} "  e)))
                 (join ""))
             "</div>")))

[defn closure-html-internal [f]
      [when [meta f :internal]
        "{{ [component :InternalWarning] }}"]]
(defn closure-html-internal (f)
      (when (meta f :internal)
        "{{ (component :InternalWarning) }}"))

[defn generate-reference-for [f]
      [cat [closure-signature f]
(defn generate-reference-for (f)
      (cat (closure-signature f)
           "<br/><br/>"
           [closure-html-internal f]
           [closure-html-documentation f]
           [closure-html-examples f]
           [closure-html-related f]]]
           (closure-html-internal f)
           (closure-html-documentation f)
           (closure-html-examples f)
           (closure-html-related f)))

[defn generate-content-for [f]
      [def filename [fmt "{}.html" [closure/name f]]]
      [when [= filename ".html"] [return #nil]]
      [def tags #nil]
      [when [meta f :internal] [cons! :internal tags]]
      [queue-content filename
                     @[:type :page :title [closure/name f] :category [closure/cat f] :tags tags]
                     [fn [] [generate-reference-for f]]]]
(defn generate-content-for (f)
      (def filename (fmt "{}.html" (closure/name f)))
      (when (= filename ".html") (return #nil))
      (def tags #nil)
      (when (meta f :internal) (cons! :internal tags))
      (queue-content filename
                     {:type :page :title (closure/name f) :category (closure/cat f) :tags tags}
                     (fn () (generate-reference-for f))))

[-> [symbol-table]
    [map resolve-or-nil]
    [filter callable?]
    [for-each generate-content-for]]
(-> (symbol-table)
    (map resolve-or-nil)
    (filter callable?)
    (for-each generate-content-for))

M content/reference/index.html => content/reference/index.html +9 -6
@@ 18,10 18,13 @@ date: "2022-09-13"

<div class="category-list">
{{
    [def cats @[]]
    [-> [symbol-table]
        [map resolve-or-nil]
        [for-each [fn [a] [tree/set! cats [closure/cat a] #t]]]]
    [join [map [sort [tree/keys cats][fn [a b] [< [string a] [string b]]]] [fn [a] [component :CategoryNavigation @[:category a]]]] ""]
}}
    (def cats {})
    (-> (symbol-table)
        (map resolve-or-nil)
        (for-each (fn (a) (tree/set! cats (closure/cat a) #t))))
    (-> (tree/keys cats)
        (sort (fn (a b) (< (string a) (string b))))
        (map (fn (a) (component :CategoryNavigation {:category a})))
        (join ""))
                            }}
</div>

M main.nuj => main.nuj +12 -10
@@ 3,17 3,19 @@
;;;
;;; Most functionality is separated into modules that can be found under /ssg/

[import [rainbow] :ansi]
[import [get :as context-get] "ssg/context"]
[import [build-ctx build-queued generate-content build-frontmatter-index add-resources] "ssg/content"]
[import [load-builtin-themes load-components] "ssg/theme"]
[import [build-prev-next-list] "ssg/navigation"]
(import (rainbow) :ansi)
(import (get :as context-get) "ssg/context")
(import (build-ctx build-queued generate-content build-frontmatter-index add-resources) "ssg/content")
(import (load-builtin-themes load-components) "ssg/theme")
(import (build-prev-next-list) "ssg/navigation")

[defn main [args]
(defn main (args)
      :export
      [pfmtln "Welcome to the {} SSG" [rainbow "Nujel"]]
      [when [and [car args] [string? [car args]]] [cd [car args]]]
      [-> [context-get "./"]
      (pfmtln "Welcome to the {} SSG" (rainbow "Nujel"))
      (when (and (car args)
                 (string? (car args)))
        (cd (car args)))
      (-> (context-get "./")
          load-builtin-themes
          load-components
          build-frontmatter-index


@@ 21,4 23,4 @@
          build-prev-next-list
          build-ctx
          build-queued
          add-resources]]
          add-resources))

M ssg/content.nuj => ssg/content.nuj +58 -58
@@ 1,81 1,81 @@
[import [build :as loader/build mkdir-safe get-out-path] "loader"]
[import [render parse-frontmatter frontmatter get-frontmatter] "theme"]
(import (build :as loader/build mkdir-safe get-out-path) "loader")
(import (render parse-frontmatter frontmatter get-frontmatter) "theme")

[defn add-resources [ctx]
(defn add-resources (ctx)
      :export
      "Copy all resources used over"
      [def deploy-dir [tree/ref ctx :deploy-dir]]
      [doseq [res [tree/values [tree/ref ctx :resources-needed]] ctx]
             [mkdir [fmt "{deploy-dir}/{}" [path/dirname [cdr res]]]]
             [file/copy [car res] [fmt "{deploy-dir}/{}" [cdr res]]]]]
      (def deploy-dir (tree/ref ctx :deploy-dir))
      (doseq (res (tree/values (tree/ref ctx :resources-needed)) ctx)
             (mkdir (fmt "{deploy-dir}/{}" (path/dirname (cdr res))))
             (file/copy (car res) (fmt "{deploy-dir}/{}" (cdr res)))))

[defn path/content? [path]
      [= [lower-case [path/extension path]] "html"]]
(defn path/content? (path)
      (= (lower-case (path/extension path)) "html"))

[defn load-frontmatter [path name content-frontmatter]
      [when-not [path/content? path] [return #nil]]
      [def fm [get-frontmatter path name [frontmatter [slurp path]]]]
      [tree/set! content-frontmatter [string->keyword name] fm]]
(defn load-frontmatter (path name content-frontmatter)
      (when-not (path/content? path) (return #nil))
      (def fm (get-frontmatter path name (frontmatter (slurp path))))
      (tree/set! content-frontmatter (string->keyword name) fm))

[defn build-content [ctx path content-frontmatter build-fun]
      [when [file/file? [cat path "/config.nuj"]]
        [set! ctx [tree/merge ctx [file/eval [cat path "/config.nuj"]]]]]
      [doseq [c [directory/read-relative path] ctx]
             [if [file/dir? c]
                 [build-content ctx c content-frontmatter build-fun]
                 [build-fun ctx c content-frontmatter]]]]
(defn build-content (ctx path content-frontmatter build-fun)
      (when (file/file? (cat path "/config.nuj"))
        (set! ctx (tree/merge ctx (file/eval (cat path "/config.nuj")))))
      (doseq (c (directory/read-relative path) ctx)
             (if (file/dir? c)
                 (build-content ctx c content-frontmatter build-fun)
                 (build-fun ctx c content-frontmatter))))

[defn queue-content* [ctx base-path out-path fm content-fun]
      [def path [cat base-path out-path]]
      [def href [string/cut path [inc [length [tree/ref ctx :content-root-dir]]]]]
      [tree/set! fm :href href]
      [tree/set! fm :depth [- [length [split [tree/ref fm :href] "/"]] 1]]
      [tree/set! [tree/ref ctx :frontmatter] [string->keyword href] fm]
      [tree/set! [tree/ref ctx :generator-queue] [string->keyword href] @[:path path :meta fm :fun content-fun]]]
(defn queue-content* (ctx base-path out-path fm content-fun)
      (def path (cat base-path out-path))
      (def href (string/cut path (inc (length (tree/ref ctx :content-root-dir)))))
      (tree/set! fm :href href)
      (tree/set! fm :depth (- (length (split (tree/ref fm :href) "/")) 1))
      (tree/set! (tree/ref ctx :frontmatter) (string->keyword href) fm)
      (tree/set! (tree/ref ctx :generator-queue) (string->keyword href) {:path path :meta fm :fun content-fun}))


[defn generate-content [ctx]
(defn generate-content (ctx)
      "Generate all autogenerated content"
      :export
      [build-content ctx
                     [tree/ref ctx :content-root-dir]
                     [tree/ref ctx :frontmatter]
                     [fn [ctx path content-frontmatter]
                         [when-not [== [path/basename path] "generator.nuj"]
                                   [return #nil]]
                       [def base-path [cat [path/dirname path] "/"]]
                       [defn queue-content [out-path fm content-fun]
                             [queue-content* ctx base-path out-path fm content-fun]]
                       [file/eval path [current-closure]]]]]
      (build-content ctx
                     (tree/ref ctx :content-root-dir)
                     (tree/ref ctx :frontmatter)
                     (fn (ctx path content-frontmatter)
                         (when-not (== (path/basename path) "generator.nuj")
                                   (return #nil))
                       (def base-path (cat (path/dirname path) "/"))
                       (defn queue-content (out-path fm content-fun)
                             (queue-content* ctx base-path out-path fm content-fun))
                       (file/eval path (current-closure)))))


[defn build-frontmatter-index [ctx]
(defn build-frontmatter-index (ctx)
      "Load all the frontmatters and look generate datastructures necessary for"
      "building navigations"
      :export
      [def content-frontmatter @[]]
      [build-content ctx
                     [tree/ref ctx :content-root-dir]
      (def content-frontmatter {})
      (build-content ctx
                     (tree/ref ctx :content-root-dir)
                     content-frontmatter
                     [fn [ctx path content-frontmatter]
                         [load-frontmatter path
                                           [string/cut path [inc [length [tree/ref ctx :content-root-dir]]]]
                                           content-frontmatter]]]
      [tree/set! ctx :frontmatter content-frontmatter]]
                     (fn (ctx path content-frontmatter)
                         (load-frontmatter path
                                           (string/cut path (inc (length (tree/ref ctx :content-root-dir))))
                                           content-frontmatter)))
      (tree/set! ctx :frontmatter content-frontmatter))

[defn build-ctx [ctx]
(defn build-ctx (ctx)
      "Build everything"
      :export
      [build-content ctx
                     [tree/ref ctx :content-root-dir]
                     [tree/ref ctx :frontmatter]
                     loader/build]]
      (build-content ctx
                     (tree/ref ctx :content-root-dir)
                     (tree/ref ctx :frontmatter)
                     loader/build))

[defn build-queued [ctx]
(defn build-queued (ctx)
      "Build queued generator content"
      :export
      [doseq [d [tree/values [tree/ref ctx :generator-queue]] ctx]
             [def raw-content [[tree/ref d :fun]]]
             [def path [get-out-path ctx [tree/ref d :path]]]
             [mkdir-safe [path/dirname path]]
             [spit path [render ctx path [tree/ref d :meta] raw-content]]]]
      (doseq (d (tree/values (tree/ref ctx :generator-queue)) ctx)
             (def raw-content ((tree/ref d :fun)))
             (def path (get-out-path ctx (tree/ref d :path)))
             (mkdir-safe (path/dirname path))
             (spit path (render ctx path (tree/ref d :meta) raw-content))))

M ssg/context.nuj => ssg/context.nuj +10 -10
@@ 1,15 1,15 @@
[defn get [path]
(defn get (path)
      "Return the context for a SSG project, located at PATH"
      :export
      @[ :path path
         :title "Some site"
         :content-root-dir "content"
         :deploy-dir "deploy"
      { :path path
        :title "Some site"
        :content-root-dir "content"
        :deploy-dir "deploy"

         :theme :default
        :theme :default

         :components @[]
         :templates @[]
        :components {}
        :templates {}

         :resources-needed @[]
         :generator-queue @[]]]
        :resources-needed {}
        :generator-queue {}})

M ssg/loader.nuj => ssg/loader.nuj +32 -32
@@ 1,44 1,44 @@
[import [render split-frontmatter get-frontmatter] "theme"]
(import (render split-frontmatter get-frontmatter) "theme")

[defn get-out-path [ctx path]
(defn get-out-path (ctx path)
      :export
      [cat [tree/ref ctx :deploy-dir]
           [string/cut path [length [tree/ref ctx :content-root-dir]]]]]
      (cat (tree/ref ctx :deploy-dir)
           (string/cut path (length (tree/ref ctx :content-root-dir)))))

[defn mkdir-safe [full-path]
(defn mkdir-safe (full-path)
      :export
      [def parts [split full-path "/"]]
      [def path #nil]
      [while parts
        [set! path [if path
                       [cat path "/" [car parts]]
                       [car parts]]]
        [cdr! parts]
        [mkdir path]]]
      (def parts (split full-path "/"))
      (def path #nil)
      (while parts
        (set! path (if path
                       (cat path "/" (car parts))
                       (car parts)))
        (cdr! parts)
        (mkdir path)))

[defn build-html [ctx path]
(defn build-html (ctx path)
      "Loader that renders a HTML template"
      :export
      [def dest-path [get-out-path ctx path]]
      [mkdir-safe [path/dirname dest-path]]
      [def page-parts [split-frontmatter [slurp path]]]
      [def meta [get-frontmatter path
                                 [string/cut path [inc [length [tree/ref ctx :content-root-dir]]]]
                                 [car page-parts]]]
      [spit dest-path [render ctx path meta [cdr page-parts]]]
      [return dest-path]]
      (def dest-path (get-out-path ctx path))
      (mkdir-safe (path/dirname dest-path))
      (def page-parts (split-frontmatter (slurp path)))
      (def meta (get-frontmatter path
                                 (string/cut path (inc (length (tree/ref ctx :content-root-dir))))
                                 (car page-parts)))
      (spit dest-path (render ctx path meta (cdr page-parts)))
      (return dest-path))

[defn build-copy [ctx path]
(defn build-copy (ctx path)
      "Loader that just copies the file over"
      [def dest-path [get-out-path ctx path]]
      [mkdir-safe [path/dirname dest-path]]
      [file/copy path dest-path]
      dest-path]
      (def dest-path (get-out-path ctx path))
      (mkdir-safe (path/dirname dest-path))
      (file/copy path dest-path)
      dest-path)

[defn build [ctx path]
(defn build (ctx path)
      "Build a certain path using the loader"
      :export
      [def ext [string->keyword [lower-case [path/extension path]]]]
      [if [== ext :html]
          [build-html ctx path]
          [build-copy ctx path]]]
      (def ext (string->keyword (lower-case (path/extension path))))
      (if (== ext :html)
          (build-html ctx path)
          (build-copy ctx path)))

M ssg/navigation.nuj => ssg/navigation.nuj +30 -30
@@ 1,37 1,37 @@
[defn build-prev-next-list [ctx]
(defn build-prev-next-list (ctx)
      "Build the list linking each page to the next/previous one, which might"
      "not be displayed on every page but will be calculated nonetheless"
      :export
      [def nav @[]]
      [def l [-> [tree/values [tree/ref ctx :frontmatter]]
                 [list/sort [fn [a b] [< [tree/ref a :href]
                                         [tree/ref b :href]]]]]]
      [def c l]
      [while [cdr c]
        [when [== [path/dirname [tree/ref [car c] :href]]
                  [path/dirname [tree/ref [cadr c] :href]]]
          [def cur-href [string->keyword [tree/ref [cadr c] :href]]]
          [def prev-href [tree/ref [car c] :href]]
          [when-not [tree/ref nav cur-href] [tree/set! nav cur-href @[:prev #nil :next #nil]]]
          [tree/set! [tree/ref nav cur-href] :prev prev-href]
      (def nav {})
      (def l (-> (tree/values (tree/ref ctx :frontmatter))
                 (list/sort (fn (a b) (< (tree/ref a :href)
                                         (tree/ref b :href))))))
      (def c l)
      (while (cdr c)
        (when (== (path/dirname (tree/ref (car c) :href))
                  (path/dirname (tree/ref (cadr c) :href)))
          (def cur-href (string->keyword (tree/ref (cadr c) :href)))
          (def prev-href (tree/ref (car c) :href))
          (when-not (tree/ref nav cur-href) (tree/set! nav cur-href {:prev #nil :next #nil}))
          (tree/set! (tree/ref nav cur-href) :prev prev-href)

          [def cur-href [string->keyword [tree/ref [car c] :href]]]
          [def next-href [tree/ref [cadr c] :href]]
          [when-not [tree/ref nav cur-href] [tree/set! nav cur-href @[:prev #nil :next #nil]]]
          [tree/set! [tree/ref nav cur-href] :next next-href]]
        [cdr! c]]
      [tree/set! ctx :prev-next-nav nav]]
          (def cur-href (string->keyword (tree/ref (car c) :href)))
          (def next-href (tree/ref (cadr c) :href))
          (when-not (tree/ref nav cur-href) (tree/set! nav cur-href {:prev #nil :next #nil}))
          (tree/set! (tree/ref nav cur-href) :next next-href))
        (cdr! c))
      (tree/set! ctx :prev-next-nav nav))

[defn build [ctx depth prefix category]
(defn build (ctx depth prefix category)
      "Build up a navigation and return a list of trees, describing the entries"
      :export
      [def l [-> [tree/values [tree/ref ctx :frontmatter]]
                 [filter [fn [a] [not [tree/ref a :hide-in-nav]]]]]]
      [when depth
        [set! l [filter l [fn [a] [== depth [tree/ref a :depth]]]]]]
      [when prefix
        [set! l [filter l [fn [a] [== prefix [cut [tree/ref a :href] 0 [buffer/length prefix]]]]]]]
      [when category
        [set! l [filter l [fn [a] [== category [tree/ref a :category]]]]]]
      [list/sort l [fn [a b] [< [tree/ref a :href]
                                [tree/ref b :href]]]]]
      (def l (-> (tree/values (tree/ref ctx :frontmatter))
                 (filter (fn (a) (not (tree/ref a :hide-in-nav))))))
      (when depth
        (set! l (filter l (fn (a) (== depth (tree/ref a :depth))))))
      (when prefix
        (set! l (filter l (fn (a) (== prefix (cut (tree/ref a :href) 0 (buffer/length prefix)))))))
      (when category
        (set! l (filter l (fn (a) (== category (tree/ref a :category))))))
      (list/sort l (fn (a b) (< (tree/ref a :href)
                                (tree/ref b :href)))))

M ssg/theme.nuj => ssg/theme.nuj +205 -195
@@ 1,231 1,241 @@
[import [build :as navigation/build] "navigation"]
(import (build :as navigation/build) "navigation")

[def themes @[]]
(def themes {})

[defn Nujel [source]
      [import [pp-nujel] :pretty/nujel]
      [pp-nujel source :html]]
(defn Nujel (source)
      (import (pp-nujel) :pretty/nujel)
      (pp-nujel source :html))

[defn add-theme [name mod]
      [tree/set! themes name [module/load mod [current-closure]]]]
(defn add-theme (name mod)
      (tree/set! themes name (module/load mod (current-closure))))

[defn load-builtin-themes [ctx]
(defn load-builtin-themes (ctx)
      :export
      [add-theme :default [module/load "./theme/default" [current-closure]]]
      [return ctx]]
      (add-theme :default (module/load "./theme/default" (current-closure)))
      (return ctx))

[defn include-resource* [ctx path dest-path]
      [tree/set! [tree/ref ctx :resources-needed] [string->keyword path] [cons path dest-path]]
      [return dest-path]]
(defn include-resource* (ctx path dest-path)
      (tree/set! (tree/ref ctx :resources-needed) (string->keyword path) (cons path dest-path))
      (return dest-path))

[defn load-components [ctx]
(defn load-components (ctx)
      :export
      [import [init :as theme/init]
              [tree/ref themes [tree/ref ctx :theme]]]
      [theme/init ctx]
      ctx]
      (import (init :as theme/init)
              (tree/ref themes (tree/ref ctx :theme)))
      (theme/init ctx)
      ctx)

[defn render [ctx path]
(defn render (ctx path)
      :export
      "Apply a theme to a particular file in PATH"
      [def theme [tree/ref themes [tree/ref ctx :theme]]]
      [import [render :as cur/render load-components] theme]
      [load-components ctx]
      [cur/render ctx path]]

[defn theme/split/rec [theme-text start ret]
      [def i [index-of theme-text "{{" start]]
      [if [< i 0]
          [if [not= start 0]
              [cons [string/cut theme-text start] ret]
              [cons theme-text ret]]
          [do [def end-i [index-of theme-text "}}" i]]
              [when [< end-i 0] [exception "Unpaired Brackets in theme" theme-text]]
              [cons! [string/cut theme-text start i] ret]
              [cons! [cons 'do [read [string/cut theme-text [+ i 2] end-i]]] ret]
              [theme/split/rec theme-text [+ end-i 2] ret]]]]

[defn theme/split [theme-text]
      (def theme (tree/ref themes (tree/ref ctx :theme)))
      (import (render :as cur/render load-components) theme)
      (load-components ctx)
      (cur/render ctx path))

(defn theme/split/rec (theme-text start ret)
      (def i (index-of theme-text "{{" start))
      (if (< i 0)
          (if (not= start 0)
              (cons (string/cut theme-text start) ret)
              (cons theme-text ret))
          (do (def end-i (index-of theme-text "}}" i))
              (when (< end-i 0) (exception "Unpaired Brackets in theme" theme-text))
              (cons! (string/cut theme-text start i) ret)
              (cons! (cons 'do (read (string/cut theme-text (+ i 2) end-i))) ret)
              (theme/split/rec theme-text (+ end-i 2) ret))))

(defn theme/split (theme-text)
      "Split a theme into separate strings and expressions for every {{ }} block."
      [nreverse [theme/split/rec theme-text 0 #nil]]]
      (nreverse (theme/split/rec theme-text 0 #nil)))

[defn split-frontmatter [raw]
(defn split-frontmatter (raw)
      "Return a dotted pair of the frontmatter and content part from a raw piece of content"
      :export
      [def in [trim raw]]
      [def start [index-of in "+++"]]
      [when [not= start 0]
        [return [cons "" in]]]
      [def end [index-of in "+++" 3]]
      [when [< end 0]
        [return [cons "" in]]]
      [cons [trim [string/cut in 3 end]]
            [trim [string/cut in [+ 3 end]]]]]

[defn frontmatter [text]
      (def in (trim raw))
      (def start (index-of in "+++"))
      (when (not= start 0)
        (return (cons "" in)))
      (def end (index-of in "+++" 3))
      (when (< end 0)
        (return (cons "" in)))
      (cons (trim (string/cut in 3 end))
            (trim (string/cut in (+ 3 end)))))

(defn frontmatter (text)
      "Return the frontmatter, if found, from some content"
      :export
      [car [split-frontmatter text]]]
      (car (split-frontmatter text)))

[defn strip-frontmatter [text]
(defn strip-frontmatter (text)
      "Remove the frontmatter, if found, from some content"
      :export
      [cdr [split-frontmatter text]]]
      (cdr (split-frontmatter text)))

[defn get-frontmatter [path name raw-fm]
(defn get-frontmatter (path name raw-fm)
      :export
      [def fm [parse-frontmatter raw-fm]]
      [when-not [tree/has? fm :type] [tree/set! fm :type :page]]
      [tree/set! fm :href name]
      [tree/set! fm :depth [- [length [split name "/"]] 1]]
      [return fm]]
      (def fm (parse-frontmatter raw-fm))
      (when-not (tree/has? fm :type) (tree/set! fm :type :page))
      (tree/set! fm :href name)
      (tree/set! fm :depth (- (length (split name "/")) 1))
      (return fm))

[defn parse-content [ctx path raw-content *page-meta*]
(defn parse-content (ctx path raw-content *page-meta*)
      "Parses content, evaluating all embedded Nujel forms"
      :export
      [eval [cons 'cat [theme/split raw-content]]]]
      (try (fn (err)
             (efmtln "Error while rendering {path}"))
           (eval (cons 'cat (theme/split raw-content)))))

[defn parse-frontmatter [fm]
(defn parse-frontmatter (fm)
      ""
      :export
      [def expr [read fm]]
      [apply tree/new expr]]

[def *cur-component-path* #nil]
[def *cur-page-ctx* #nil]
[def *cur-page-path* #nil]
[def *cur-page-meta* #nil]
[defn set-page-ctx! [ctx path meta theme-path]
      [when *cur-page-ctx* [exception "Double set page ctx" ctx]]
      [set! *cur-page-ctx* ctx]
      [set! *cur-page-path* path]
      [set! *cur-page-meta* meta]]

[defn reset-page-ctx! []
      [when-not *cur-page-ctx* [exception "Double unset page ctx" #nil]]
      [set! *cur-page-ctx* #nil]
      [set! *cur-page-path* #nil]
      [set! *cur-page-meta* #nil]]

[defn get-ctx [] *cur-page-ctx*]
[defn get-path [] *cur-page-path*]
[defn get-meta [] *cur-page-meta*]

[defn get-component-path [] [return *cur-component-path*]]
[defn set-component-path! [new-path]
      [set! *cur-component-path* new-path]]

[defn urlescape [in]
      [with-string-port out
                        [dotimes [i [buffer/length in]]
                          [def c [buffer/ref in i]]
                          [case c
                                [#\# [out 'block-write "%23"]]
                                [#\& [out 'block-write "%26"]]
                                [#\? [out 'block-write "%3F"]]
                                [otherwise [out 'char-write c]]]]]]

[defn get-href* [ctx path target]
      [def name [string/cut path [inc [length [tree/ref ctx :content-root-dir]]]]]
      [def depth [- [length [split name "/"]] 1]]
      [def href [if [tree? target]
                    [tree/ref target :href]
                    target]]
      [dotimes [i depth [urlescape href]]
        [set! href [cat "../" href]]]]

[defn render-link* [ctx path target]
      [if [string? target]
          [render-link* ctx path [tree/ref [tree/ref ctx :frontmatter] [string->keyword target]]]
          [fmt "<a href=\"{}\" class=\"{}\">{}</a>"
               [get-href* ctx path target]
               [do [def classes #nil]
                   [def tags [and [tree? target] [tree/ref target :tags]]]
                   [doseq [tag tags] [cons! [cat "tag-" [keyword->string tag]] classes]]
                   [join classes " "]]
               [if [tree? target]
                   [or [tree/ref target :nav-title]
                       [tree/ref target :title]]
                   target]]]]

[defn render-link [target]
      [render-link* [get-ctx] [get-path] target]]

[defn get-href [target]
      [get-href* [get-ctx] [get-path] target]]

[defn navigation [depth prefix category]
      [navigation/build [get-ctx] depth prefix category]]

[defn get-posts []
      [def ret #nil]
      [doseq [p [tree/values [tree/ref [get-ctx] :frontmatter]] ret]
             [when [== :post [tree/ref p :type]]
               [cons! p ret]]]]

[defn include-resource [res-path]
      [include-resource* [get-ctx]
                         [fmt "{}/{res-path}" [get-component-path]]
                         res-path]
      [get-href res-path]]

[defn page-title []
      [def ct [ref [get-meta] :title]]
      [if ct
          [fmt "{} - {}" [ref [get-ctx] :title] ct]
          [ref [get-ctx] :title]]]

[defn this-href []
      [string/cut [get-path] [inc [length [tree/ref [get-ctx] :content-root-dir]]]]]

[defn this-prev-next []
      [tree/ref [tree/ref [get-ctx] :prev-next-nav]
                [string->keyword [this-href]]]]

[defn this-prev []
      [and [this-prev-next]
           [tree/ref [this-prev-next] :prev]]]

[defn this-next []
      [and [this-prev-next]
           [tree/ref [this-prev-next] :next]]]

[defn component [name props children]
      [def component-fun [tree/ref [tree/ref [get-ctx] :components] name]]
      [when-not component-fun [exception "Can't find a component called: " name]]
      [component-fun props children]]

[defn template [name props children]
      [def template-fun [tree/ref [tree/ref [get-ctx] :templates] [or name :page]]]
      [when-not template-fun [exception "Can't find a template named: " name]]
      [template-fun props children]]

[defn add-component [ctx name raw-text component-path]
      (def expr (read fm))
      (apply tree/new expr))

(def *cur-component-path* #nil)
(def *cur-page-ctx* #nil)
(def *cur-page-path* #nil)
(def *cur-page-meta* #nil)
(defn set-page-ctx! (ctx path meta theme-path)
      (when *cur-page-ctx* (exception "Double set page ctx" ctx))
      (set! *cur-page-ctx* ctx)
      (set! *cur-page-path* path)
      (set! *cur-page-meta* meta))

(defn reset-page-ctx! ()
      (when-not *cur-page-ctx* (exception "Double unset page ctx" #nil))
      (set! *cur-page-ctx* #nil)
      (set! *cur-page-path* #nil)
      (set! *cur-page-meta* #nil))

(defn get-ctx () *cur-page-ctx*)
(defn get-path () *cur-page-path*)
(defn get-meta () *cur-page-meta*)

(defn get-component-path () (return *cur-component-path*))
(defn set-component-path! (new-path)
      (set! *cur-component-path* new-path))

(defn urlescape (in)
      (with-string-port out
                        (dotimes (i (buffer/length in))
                          (def c (buffer/ref in i))
                          (case c
                                (#\# (out 'block-write "%23"))
                                (#\& (out 'block-write "%26"))
                                (#\? (out 'block-write "%3F"))
                                (otherwise (out 'char-write c))))))

(defn get-href* (ctx path target)
      (def name (string/cut path (inc (length (tree/ref ctx :content-root-dir)))))
      (def depth (- (length (split name "/")) 1))
      (def href (if (tree? target)
                    (tree/ref target :href)
                    target))
      (dotimes (i depth (urlescape href))
        (set! href (cat "../" href))))

(defn render-link* (ctx path target)
      (if (string? target)
          (render-link* ctx path (tree/ref (tree/ref ctx :frontmatter) (string->keyword target)))
          (fmt "<a href=\"{}\" class=\"{}\">{}</a>"
               (get-href* ctx path target)
               (do (def classes #nil)
                   (def tags (and (tree? target) (tree/ref target :tags)))
                   (doseq (tag tags) (cons! (cat "tag-" (keyword->string tag)) classes))
                   (join classes " "))
               (if (tree? target)
                   (or (tree/ref target :nav-title)
                       (tree/ref target :title))
                   target))))

(defn render-link (target)
      (render-link* (get-ctx) (get-path) target))

(defn get-href (target)
      (get-href* (get-ctx) (get-path) target))

(defn navigation (depth prefix category)
      (navigation/build (get-ctx) depth prefix category))

(defn get-posts ()
      (def ret #nil)
      (doseq (p (tree/values (tree/ref (get-ctx) :frontmatter)) ret)
             (when (== :post (tree/ref p :type))
               (cons! p ret))))

(defn include-resource (res-path)
      (include-resource* (get-ctx)
                         (fmt "{}/{res-path}" (get-component-path))
                         res-path)
      (get-href res-path))

(defn page-title ()
      (def ct (ref (get-meta) :title))
      (if ct
          (fmt "{} - {}" (ref (get-ctx) :title) ct)
          (ref (get-ctx) :title)))

(defn this-href ()
      (string/cut (get-path) (inc (length (tree/ref (get-ctx) :content-root-dir)))))

(defn this-prev-next ()
      (tree/ref (tree/ref (get-ctx) :prev-next-nav)
                (string->keyword (this-href))))

(defn this-prev ()
      (and (this-prev-next)
           (tree/ref (this-prev-next) :prev)))

(defn this-next ()
      (and (this-prev-next)
           (tree/ref (this-prev-next) :next)))

(defn component (name props children)
      (def component-fun (tree/ref (tree/ref (get-ctx) :components) name))
      (when-not component-fun (exception "Can't find a component called: " name))
      (try (fn (err)
             (efmtln "Error while rendering component {name}"))
           (component-fun props children)))

(defn template (name props children)
      (def template-fun (tree/ref (tree/ref (get-ctx) :templates) (or name :page)))
      (when-not template-fun (exception "Can't find a template named: " name))
      (try (fn (err)
             (efmtln "Error while rendering theme {name}"))
           (template-fun props children)))

(defn add-component (ctx name raw-text component-path)
      :export
      [tree/set! [tree/ref ctx :components]
      (tree/set! (tree/ref ctx :components)
                 name
                 [parse-component raw-text component-path]]]
                 (parse-component raw-text component-path)))

[defn add-template [ctx name raw-text component-path]
(defn add-template (ctx name raw-text component-path)
      :export
      [tree/set! [tree/ref ctx :templates]
      (tree/set! (tree/ref ctx :templates)
                 name
                 [parse-component raw-text component-path]]]
                 (parse-component raw-text component-path)))

[defn parse-component [raw-text component-path]
(defn parse-component (raw-text component-path)
      :export
      [def theme-parts [cons 'cat [theme/split raw-text]]]
      [eval `[fn [props children]
                 [def old-component-path [get-component-path]]
                 [set-component-path! component-path]
                 [def ret ~theme-parts]
                 [set-component-path! old-component-path]
                 [return ret]]]]

[defn render [ctx path meta raw-content]
      (def theme-parts (cons 'cat (theme/split raw-text)))
      (eval `(fn (props children)
                 (def old-component-path (get-component-path))
                 (set-component-path! component-path)
                 (def ret ~theme-parts)
                 (set-component-path! old-component-path)
                 (return ret))))

(defn render (ctx path meta raw-content)
      :export
      [set-page-ctx! ctx path meta]
      [def content [parse-content ctx path raw-content meta]]
      [def ret [template [tree/ref [get-meta] :type] meta content]]
      [reset-page-ctx!]
      [return ret]]
      (set-page-ctx! ctx path meta)
      (def ret "")
      (try (fn (err)
               (efmtln "Error while rendering {path}")
             (efmtln "{err:?}"))
           (def content (parse-content ctx path raw-content meta))
           (set! ret (template (tree/ref (get-meta) :type) meta content)))
      (reset-page-ctx!)
      (return ret))

M ssg/theme/default.nuj => ssg/theme/default.nuj +12 -12
@@ 1,19 1,19 @@
[import [add-component add-template] "../theme"]
(import (add-component add-template) "../theme")

[def +theme-path+ [fmt "{}/default" *module-path*]]
(def +theme-path+ (fmt "{}/default" *module-path*))

[defn load-part [ctx suffix fun]
      [doseq [comp-file [directory/read-recursive [fmt "{+theme-path+}/{suffix}"]] ctx]
             [fun
(defn load-part (ctx suffix fun)
      (doseq (comp-file (directory/read-recursive (fmt "{+theme-path+}/{suffix}")) ctx)
             (fun
              ctx
              [-> comp-file
              (-> comp-file
                  path/basename
                  path/without-extension
                  string->keyword]
              [slurp comp-file]
              [cat [path/dirname comp-file] "/.."]]]]
                  string->keyword)
              (slurp comp-file)
              (cat (path/dirname comp-file) "/.."))))

[defn init [ctx]
(defn init (ctx)
      :export
      [load-part ctx "templates" add-template]
      [load-part ctx "components" add-component]]
      (load-part ctx "templates" add-template)
      (load-part ctx "components" add-component))

M ssg/theme/default/components/CategoryNavigation.html => ssg/theme/default/components/CategoryNavigation.html +5 -5
@@ 1,10 1,10 @@
<div class="category-navigation">
	<h2>{{
		[def cur-cat [tree/ref props :category]]
		[def items [navigation #nil #nil cur-cat]]
		[fmt "{} ({})" [capitalize [keyword->string cur-cat]] [length items]]
		(def cur-cat (tree/ref props :category))
		(def items (navigation #nil #nil cur-cat))
		(fmt "{} ({})" (capitalize (keyword->string cur-cat)) (length items))
	}}</h2>
	<nav>
		{{ [join [sort [map items [fn [v] [render-link v]]]] "<br/>"] }}
		{{ (join (sort (map items (fn (v) (render-link v)))) "<br/>") }}
	</nav>
</div>
\ No newline at end of file
</div>

M ssg/theme/default/components/Favicon.html => ssg/theme/default/components/Favicon.html +2 -2
@@ 1,4 1,4 @@
{{
[when [tree/ref [get-ctx] :favicon]
      [fmt "<link rel=\"icon\" rel=\"icon\" type=\"image/png\" href=\"{}\"/>" [get-href [tree/ref [get-ctx] :favicon]]]]
(when (tree/ref (get-ctx) :favicon)
      (fmt "<link rel=\"icon\" rel=\"icon\" type=\"image/png\" href=\"{}\"/>" (get-href (tree/ref (get-ctx) :favicon))))
}}

M ssg/theme/default/components/Footer.html => ssg/theme/default/components/Footer.html +3 -3
@@ 1,5 1,5 @@
{{
  [case [tree/ref [get-meta] :type]
        [:page [component :PageFooter]]
        [otherwise ""]]
  (case (tree/ref (get-meta) :type)
        (:page (component :PageFooter))
        (otherwise ""))
}}

M ssg/theme/default/components/Header.html => ssg/theme/default/components/Header.html +2 -2
@@ 1,4 1,4 @@
<nav class="top-nav">
  {{ [component :Logo] }}
  {{ [join [sort [map [navigation 0] [fn [v] [render-link v]]]] ""] }}
  {{ (component :Logo) }}
  {{ (join (sort (map (navigation 0) (fn (v) (render-link v)))) "") }}
</nav>

M ssg/theme/default/components/Logo.html => ssg/theme/default/components/Logo.html +5 -2
@@ 1,4 1,7 @@
{{
[when [tree/ref [get-ctx] :logo]
      [fmt "<a class=\"logo-link\" href=\"{}\"><img src=\"{}\"/><span>{}</span></a>" [get-href "index.html"] [get-href [tree/ref [get-ctx] :logo]] [tree/ref [get-ctx] :logo-text]]]
(when (tree/ref (get-ctx) :logo)
      (fmt "<a class=\"logo-link\" href=\"{}\"><img src=\"{}\"/><span>{}</span></a>"
      (get-href "index.html")
      (get-href (tree/ref (get-ctx) :logo))
      (tree/ref (get-ctx) :logo-text)))
}}

M ssg/theme/default/components/Nujel.html => ssg/theme/default/components/Nujel.html +1 -1
@@ 1,1 1,1 @@
<pre class="source source-nujel">{{ [Nujel children] }}</pre>
<pre class="source source-nujel">{{ (Nujel children) }}</pre>

M ssg/theme/default/components/PageFooter.html => ssg/theme/default/components/PageFooter.html +8 -8
@@ 1,18 1,18 @@
<div>
    {{ [when [tree/ref [get-ctx] :author]
             [cat "by " [tree/ref [get-ctx] :author]]] }}
    {{ (when (tree/ref (get-ctx) :author)
             (cat "by " (tree/ref (get-ctx) :author))) }}
</div>
<div>
    {{ [and [tree/ref [get-meta] :date]
            [cat "Published " [tree/ref [get-meta] :date]]]
    {{ (and (tree/ref (get-meta) :date)
            (cat "Published " (tree/ref (get-meta) :date)))
    }}
    {{ [and [tree/ref [get-meta] :modified-date]
            [cat "Last modified " [tree/ref [get-meta] :modified-date]]]
    {{ (and (tree/ref (get-meta) :modified-date)
            (cat "Last modified " (tree/ref (get-meta) :modified-date)))
    }}
</div>
<div>
  {{ [or [tree/ref [get-ctx] :imprint] ""] }}
  {{ (or (tree/ref (get-ctx) :imprint) "") }}
</div>
<div>
  {{ [or [tree/ref [get-ctx] :contact] ""] }}
  {{ (or (tree/ref (get-ctx) :contact) "") }}
</div>

M ssg/theme/default/components/PostOverview.html => ssg/theme/default/components/PostOverview.html +2 -2
@@ 1,3 1,3 @@
{{
    [join [map [get-posts] [fn [p] [component :PostSnippet p #nil]]] ""]
}}
\ No newline at end of file
    (join (map (get-posts) (fn (p) (component :PostSnippet p #nil))) "")
}}

M ssg/theme/default/components/PostSnippet.html => ssg/theme/default/components/PostSnippet.html +5 -5
@@ 1,10 1,10 @@
<div class="post-snippet">
	<h4 class="post-date">{{ [tree/ref props :date] }}</h4>
	<h4 class="post-date">{{ (tree/ref props :date) }}</h4>
	<h3>
		<a href="{{ [get-href [tree/ref props :href]] }}">
			{{ [tree/ref props :title] }}
		<a href="{{ (get-href (tree/ref props :href)) }}">
			{{ (tree/ref props :title) }}
		</a>
	</h3>
	<p>{{ [tree/ref props :summary] }}</p>
	<a href="{{ [get-href [tree/ref props :href]] }}">Read more</a>
	<p>{{ (tree/ref props :summary) }}</p>
	<a href="{{ (get-href (tree/ref props :href)) }}">Read more</a>
</div>

M ssg/theme/default/components/PrevNextNav.html => ssg/theme/default/components/PrevNextNav.html +3 -3
@@ 1,5 1,5 @@
{{
[when [and [not [tree/ref [get-meta] :hide-prev-next]] [or [this-prev] [this-next]]]
      [cat [fmt "<div class=\"prev-chapter\">{}</div>" [render-link [this-prev]]]
           [fmt "<div class=\"next-chapter\">{}</div>" [render-link [this-next]]]]]
(when (and (not (tree/ref (get-meta) :hide-prev-next)) (or (this-prev) (this-next)))
      (cat (fmt "<div class=\"prev-chapter\">{}</div>" (render-link (this-prev)))
           (fmt "<div class=\"next-chapter\">{}</div>" (render-link (this-next)))))
}}

M ssg/theme/default/templates/page.html => ssg/theme/default/templates/page.html +8 -8
@@ 1,27 1,27 @@
<!doctype html>
<html lang="en">
  <head>
    <title>{{ [page-title] }}</title>
    <title>{{ (page-title) }}</title>
    <meta http-equiv="content-type" content="text/html;" charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="{{ [include-resource "resources/main.css"] }}"/>
    {{ [component :Favicon] }}
    <link rel="stylesheet" href="{{ (include-resource "resources/main.css") }}"/>
    {{ (component :Favicon) }}
  </head>
  <body page-type="{{ [keyword->string [or [tree/ref props :type] :page]] }}">
  <body page-type="{{ (keyword->string (or (tree/ref props :type) :page)) }}">
    <header>
      {{ [component :Header] }}
      {{ (component :Header) }}
    </header>
    <main>
      <nav class="chapter-nav top-chapter-nav">
        {{ [component :PrevNextNav] }}
        {{ (component :PrevNextNav) }}
      </nav>
      {{ children }}
      <nav class="chapter-nav bottom-chapter-nav">
        {{ [component :PrevNextNav] }}
        {{ (component :PrevNextNav) }}
      </nav>
    </main>
    <footer>
      {{ [component :Footer] }}
      {{ (component :Footer) }}
    </footer>
  </body>
</html>

M ssg/theme/default/templates/post.html => ssg/theme/default/templates/post.html +10 -10
@@ 1,25 1,25 @@
<!doctype html>
<html lang="en">
  <head>
    <title>{{ [page-title] }}</title>
    <title>{{ (page-title) }}</title>
    <meta http-equiv="content-type" content="text/html;" charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="{{ [include-resource "resources/main.css"] }}"/>
    {{ [component :Favicon] }}
    <link rel="stylesheet" href="{{ (include-resource "resources/main.css") }}"/>
    {{ (component :Favicon) }}
  </head>
  <body page-type="{{ [keyword->string [or [tree/ref props :type] :post]] }}">
  <body page-type="{{ (keyword->string (or (tree/ref props :type) :post)) }}">
    <header>
      {{ [component :Header] }}
      {{ (component :Header) }}
    </header>
    <main>
      <h4 class="post-date">{{ [tree/ref props :date] }}</h4>
      {{ [when [tree/ref props :author]
               [cat "<h4 class=\"byline\">by " [tree/ref props :author] "</h4>"]] }}
      <h1>{{ [tree/ref props :title] }}</h1>
      <h4 class="post-date">{{ (tree/ref props :date) }}</h4>
      {{ (when (tree/ref props :author)
               (cat "<h4 class=\"byline\">by " (tree/ref props :author) "</h4>")) }}
      <h1>{{ (tree/ref props :title) }}</h1>
      {{ children }}
    </main>
    <footer>
      {{ [component :Footer] }}
      {{ (component :Footer) }}
    </footer>
  </body>
</html>