simple-learning

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

commit dc948cbb854f4728b32940afcaeb245412c6ba43
parent 72b52a63f2bc456d639f2dcbab8dac3f17bc791a
Author: Andrew Laack <andrew@laack.co>
Date:   Thu, 27 Aug 2026 18:55:08 -0500

This should be refactored soon, but added shared sidebar + preformatted sections and some other stuff

Diffstat:
Mcourses/ocaml/0_ocaml.sl | 74++++++++------------------------------------------------------------------
Mlearning/bin/main.ml | 46++++++++++++++++++++++++++++++++--------------
Mlearning/static/styles.css | 17+++++++++++++++++
Mlearning/templates/course_home.jingoo | 9+--------
Mlearning/templates/course_list.jingoo | 9+--------
Mlearning/templates/course_page.jingoo | 23+++++++----------------
Alearning/templates/sidebar.jingoo | 7+++++++
7 files changed, 73 insertions(+), 112 deletions(-)

diff --git a/courses/ocaml/0_ocaml.sl b/courses/ocaml/0_ocaml.sl @@ -8,74 +8,16 @@ OCaml is the acronym for Objective Categorical Abstract Machine Language. In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: -```ocaml -let x = 10 ``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 -``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 -``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 -``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 -``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 -``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 -``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 -``` - -## Syntax - -In OCaml we use the 'let' keyword to bind a expressions to names (variables). Shown below is an example of this: - -```ocaml -let x = 10 +let factorial x = + let rec go acc current = + if current = 0 + then acc + else + go (acc * current) (current - 1) + in go 1 x;; +print_endline (string_of_int(factorial 6));; ``` - =>i em.png =>v out.webm diff --git a/learning/bin/main.ml b/learning/bin/main.ml @@ -15,24 +15,37 @@ type page_line = { content: string; is_embedded_video: bool; is_embedded_image: bool; + is_preformatted: bool; } -let parse_simple_line line = - let hash_count = countPrefix '#' line in - let embedded_video = if String.length(line) > 3 && line.[0] = '=' && line.[1] = '>' && line.[2] = 'v' then true else false in - let embedded_image = if String.length(line) > 3 && line.[0] = '=' && line.[1] = '>' && line.[2] = 'i' then true else false in - {header_level = hash_count; content = (if embedded_video || embedded_image then (String.sub line 4 (String.length(line) - 4)) else line); is_embedded_image = embedded_image; is_embedded_video = embedded_video} +let parse_simple_line line preformatted = + let hash_count = if not preformatted then countPrefix '#' line else 0 in + let embedded_video = if not preformatted && String.length(line) > 3 && line.[0] = '=' && line.[1] = '>' && line.[2] = 'v' then true else false in + let embedded_image = if not preformatted && String.length(line) > 3 && line.[0] = '=' && line.[1] = '>' && line.[2] = 'i' then true else false in + {header_level = hash_count; content = (if embedded_video || embedded_image then (String.sub line 4 (String.length(line) - 4)) else line); is_embedded_image = embedded_image; is_embedded_video = embedded_video; is_preformatted = preformatted} + +let is_format_line cl = + String.length cl = 3 && cl.[0] = '`' && cl.[1] = '`' && cl.[2] = '`' let read_simple_learning_file fileName = let ic = open_in fileName in - let rec readAll () = + let rec readAll preformatted acc = try - let current_line = parse_simple_line (input_line ic) in - current_line :: readAll (); + let cl = input_line ic in + let current_format = if is_format_line cl then not preformatted else preformatted in + if not current_format && not preformatted then + let current_line = parse_simple_line cl current_format in + if String.length acc != 0 + then + parse_simple_line acc true :: current_line :: readAll current_format "" + else + current_line :: readAll current_format "" + else + readAll current_format (if not (is_format_line cl) then (if String.length acc != 0 then acc ^ "\n" ^ cl else cl) else acc) with End_of_file -> close_in_noerr ic; - [] - in readAll () + if String.length acc != 0 then parse_simple_line acc true :: [] else [] + in readAll false "" let readFile fileName = let ic = open_in fileName in @@ -52,7 +65,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); ("is_embedded_video", Jg_types.Tbool line.is_embedded_video); ("is_embedded_image", Jg_types.Tbool line.is_embedded_image)] + Jg_types.Tobj [("header_level", Jg_types.Tint line.header_level); ("content", Jg_types.Tstr line.content); ("is_embedded_video", Jg_types.Tbool line.is_embedded_video); ("is_embedded_image", Jg_types.Tbool line.is_embedded_image); ("is_preformatted", Jg_types.Tbool line.is_preformatted)] let get_courses = let arr = Sys.readdir course_dir in @@ -99,14 +112,19 @@ let render_page course page = let page_lines = read_simple_learning_file @@ get_path_to_page course page in (Jg_template.from_file "./templates/course_page.jingoo" ~models:(["lines", Jg_types.Tlist (List.map page_line_to_tobj page_lines)])) + +let render_sidebar s = + (Jg_template.from_file "./templates/sidebar.jingoo" ~models:[("courses", get_courses)]) ^ s + + let () = Dream.run @@ Dream.logger @@ Dream.router [ - 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 "/course/:course_name/" (fun request -> Dream.html (render_sidebar @@ render_course_home (Dream.param request "course_name"))); + Dream.get "/course/:course_name/page/:page_name" (fun request -> Dream.html @@ render_sidebar @@ 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_sidebar @@ 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/static/styles.css b/learning/static/styles.css @@ -25,6 +25,23 @@ h6 { color: #700000 ; } +pre-formatted { + background: #f4f4f4; + border: 1px solid #ddd; + border-left: 3px solid #f36d33; + color: #666; + page-break-inside: avoid; + font-family: monospace; + font-size: 15px; + line-height: 1.6; + margin-bottom: 1.6em; + max-width: 100%; + overflow: auto; + padding: 1em 1.5em; + display: block; + word-wrap: break-word; +} + content-block { display: block; color: #222222; diff --git a/learning/templates/course_home.jingoo b/learning/templates/course_home.jingoo @@ -1,12 +1,5 @@ -<link rel="stylesheet" href="/static/styles.css" type="text/css"> -<div class="sidenav"> - <a href="/">Home</a> - <a href="">Services</a> - <a href="">Clients</a> - <a href="">Contact</a> -</div> - <div class="main"> + <h1> Pages </h1> <ul> {% for page in pages %} <li><a href="page/{{ page.name }}">{{ page.name }}</a></li> diff --git a/learning/templates/course_list.jingoo b/learning/templates/course_list.jingoo @@ -1,12 +1,5 @@ -<link rel="stylesheet" href="/static/styles.css" type="text/css"> -<div class="sidenav"> - <a href="/">Home</a> - <a href="">Services</a> - <a href="">Clients</a> - <a href="">Contact</a> -</div> - <div class="main"> + <h1> Courses </h1> <ul> {% for course in courses %} <li><a href="/course/{{ course.name }}/">{{ course.name }}</a></li> diff --git a/learning/templates/course_page.jingoo b/learning/templates/course_page.jingoo @@ -1,24 +1,15 @@ -<link rel="stylesheet" href="/static/styles.css" type="text/css"> -<div class="sidenav"> - <a href="/">Home</a> - <a href="">Services</a> - <a href="">Clients</a> - <a href="">Contact</a> -</div> - <div class="main"> {% for line in lines %} - {% if line.header_level >= 1 %} + {% if line.is_preformatted %} + <pre><pre-formatted>{{line.content}}</pre-formatted></pre> + {% else if line.header_level >= 1 %} <h{{line.header_level}}>{{line.content}}</h{{line.header_level}}> - {% endif %} - {% if line.header_level == 0 %} - <content-block>{{line.content}}</content-block> - {% endif %} - {% if line.is_embedded_image %} + {% else if line.is_embedded_image %} <img src=assets/{{line.content}}> </img> - {% endif %} - {% if line.is_embedded_video %} + {% else if line.is_embedded_video %} <video> <source src="assets/{{line.content}}"> Your browser does not support the video tag. </video> + {% else if line.header_level == 0 %} + <content-block>{{line.content}}</content-block> {% endif %} {% endfor %} </div> diff --git a/learning/templates/sidebar.jingoo b/learning/templates/sidebar.jingoo @@ -0,0 +1,7 @@ +<link rel="stylesheet" href="/static/styles.css" type="text/css"> +<div class="sidenav"> + <a href="/">Home</a> + {% for course in courses %} + <a href="/course/{{ course.name }}/">{{ course.name }}</a> + {% endfor %} +</div>