commit 714dfa88e8b0b3d1b58e5be99b77fe3b2fd6f175
parent 0bcb945484402054d96b88d414fdf8321ab1e8ce
Author: Andrew Laack <andrew@laack.co>
Date: Thu, 27 Aug 2026 16:19:46 -0500
continued working on format / rendering
Diffstat:
5 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/courses/ocaml/0_ocaml.sl b/courses/ocaml/0_ocaml.sl
@@ -76,3 +76,4 @@ In OCaml we use the 'let' keyword to bind a expressions to names (variables). Sh
let x = 10
```
+=> sc.png
diff --git a/courses/ocaml/assets/sc.png b/courses/ocaml/assets/sc.png
Binary files differ.
diff --git a/docs/sl.sl b/docs/sl.sl
@@ -0,0 +1,20 @@
+# Simple Learning Format (.sl)
+
+## Purpose
+
+The simple learning file format is intended to be a minimal but sufficient file format for the distribution of learning content. While not a superset of gemtext, sl is inspired by the syntax and ethos of the format.
+
+=> [https://geminiprotocol.net/docs/gemtext-specification.gmi] Gemtext Specification
+
+## Specifics
+
+A parser of sl files must track one bit of state: whether the current line is contained within a preformatted text block or not. Preformatted text blocks are delimited by lines that contain precisely three ` characters, and no other characters, new line character excluded. Such blocks may be used for code or other text that requires monospace alignment, such as ASCII art.
+
+Outside of preformatted blocks, there are seven line types:
+
+- Text lines
+- Link lines
+- Heading lines
+- List items
+- Quote lines
+- Embedding lines
diff --git a/learning/bin/main.ml b/learning/bin/main.ml
@@ -10,15 +10,16 @@ let countPrefix char data =
else idx
in go 0
-
type page_line = {
header_level: int;
content: string;
+ is_embedded: bool;
}
let parse_simple_line line =
let hash_count = countPrefix '#' line in
- {header_level = hash_count; content = line}
+ let embedded = if String.length(line) > 2 && line.[0] = '=' && line.[1] = '>' then true else false in
+ {header_level = hash_count; content = (if embedded then (String.sub line 3 (String.length(line) - 3)) else line); is_embedded = embedded}
let read_simple_learning_file fileName =
let ic = open_in fileName in
@@ -49,7 +50,7 @@ let to_tval value =
Jg_types.Tobj [("name", Jg_types.Tstr value);]
let page_line_to_tobj line =
- Jg_types.Tobj [("header_level", Jg_types.Tint line.header_level); ("content", Jg_types.Tstr line.content)]
+ Jg_types.Tobj [("header_level", Jg_types.Tint line.header_level); ("content", Jg_types.Tstr line.content); ("is_embedded", Jg_types.Tbool line.is_embedded)]
let get_courses =
let arr = Sys.readdir course_dir in
@@ -98,5 +99,7 @@ let () =
Dream.get "/course/:course_name/" (fun request -> Dream.html (render_course_home (Dream.param request "course_name")));
Dream.get "/course/:course_name/page/:page_name" (fun request -> Dream.html @@ render_page (Dream.param request "course_name") (Dream.param request "page_name"));
Dream.get "/static/**" (Dream.static "./static");
- Dream.get "/" (fun _ -> Dream.html render_course_list)
+ Dream.get "/" (fun _ -> Dream.html render_course_list);
+ Dream.get "/course/:course_name/page/assets/**" (fun request -> Dream.static
+ (course_dir ^ (Dream.param request "course_name") ^ "/assets") request);
]
diff --git a/learning/templates/course_page.jingoo b/learning/templates/course_page.jingoo
@@ -14,5 +14,8 @@
{% if line.header_level == 0 %}
<content-block>{{line.content}}</content-block>
{% endif %}
+ {% if line.is_embedded %}
+ <img src=assets/{{line.content}}> </img>
+ {% endif %}
{% endfor %}
</div>