simple-learning

Simple learning web program
git clone git://git.laack.co/simple-learning.git
Log | Files | Refs | README | LICENSE

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:
Mcourses/ocaml/0_ocaml.sl | 1+
Acourses/ocaml/assets/sc.png | 0
Adocs/sl.sl | 20++++++++++++++++++++
Mlearning/bin/main.ml | 11+++++++----
Mlearning/templates/course_page.jingoo | 3+++
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>