commit 2b9df834b9807aa37d6a5c89ebeb2b87ff73bce1
parent 34cac6ce2cc3be7f7441fb5081d2802d6e009c59
Author: Andrew Laack <andrew@laack.co>
Date: Thu, 27 Aug 2026 23:22:18 -0500
OCaml is so insanely easy to refactor in.
Diffstat:
5 files changed, 124 insertions(+), 123 deletions(-)
diff --git a/learning/bin/basic_functions.ml b/learning/bin/basic_functions.ml
@@ -0,0 +1,26 @@
+let remove_file_extension file_name =
+ let fin_period_pos = String.rindex file_name '.' in
+ String.sub file_name 0 fin_period_pos
+
+let count_prefix char data =
+ let rec go idx =
+ if String.length data > idx && data.[idx] = char
+ then
+ go (idx + 1)
+ else idx
+ in go 0
+
+(* Assumes length >= 3*)
+let has_sl_postfix s =
+ let len = String.length(s) in
+ s.[len - 1] = 'l' && s.[len - 2] = 's' && s.[len - 3] = '.'
+
+let prepend prefix s =
+ prefix ^ s
+
+let arr_to_string arr =
+ let rec go acc idx =
+ if idx >= Array.length(arr) then acc
+ else go (acc ^ arr.(idx)) (idx + 1)
+ in go "" 0
+
diff --git a/learning/bin/main.ml b/learning/bin/main.ml
@@ -1,132 +1,13 @@
open Jingoo
-let course_dir = "../courses/"
-
-let countPrefix char data =
- let rec go idx =
- if String.length data > idx && data.[idx] = char
- then
- go (idx + 1)
- else idx
- in go 0
-
-type page_line = {
- header_level: int;
- content: string;
- is_embedded_video: bool;
- is_embedded_image: bool;
- is_preformatted: bool;
- is_hidden: bool;
-}
-
-let parse_simple_line line preformatted =
- let hidden = if not preformatted && String.length(line) > 2 && line.[0] = '?' && line.[1] = '?' then true else false in
- 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; is_hidden = hidden}
-
-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 preformatted acc =
- try
- 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;
- if String.length acc != 0 then parse_simple_line acc true :: [] else []
- in readAll false ""
-
-let readFile fileName =
- let ic = open_in fileName in
- let rec readAll acc =
- try
- readAll (acc ^ ((input_line ic)) ^ "\n")
- with End_of_file ->
- close_in_noerr ic;
- acc
- in readAll ""
-
-let remove_file_extension file_name =
- let fin_period_pos = String.rindex file_name '.' in
- String.sub file_name 0 fin_period_pos
-
-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); ("is_preformatted", Jg_types.Tbool line.is_preformatted); ("is_hidden", Jg_types.Tbool line.is_hidden)]
-
-let get_courses =
- let arr = Sys.readdir course_dir in
- Array.sort compare arr;
- Jg_types.Tlist
- (Array.to_list (Array.map to_tval arr))
-
-let render_course_list =
- (Jg_template.from_file "./templates/course_list.jingoo" ~models:[("courses", get_courses)])
-
-let prepend prefix s =
- prefix ^ s
-
-(* Assumes length >= 3*)
-let has_sl_postfix s =
- let len = String.length(s) in
- s.[len - 1] = 'l' && s.[len - 2] = 's' && s.[len - 3] = '.'
-
-let get_pages_in_order course_name =
- let arr = Sys.readdir (course_dir ^ course_name) in
- Array.sort compare arr;
- let lst = List.filter has_sl_postfix (Array.to_list arr) in
- lst
-
-let get_pages page_arr =
- readFile page_arr.(0)
-
-let arr_to_string arr =
- let rec go acc idx =
- if idx >= Array.length(arr) then acc
- else go (acc ^ arr.(idx)) (idx + 1)
- in go "" 0
-
-let get_course_home course_name =
- List.map to_tval (get_pages_in_order course_name)
-
-let render_course_home course_name =
- (Jg_template.from_file "./templates/course_home.jingoo" ~models:[("pages", (Jg_types.Tlist (get_course_home course_name)))])
-
-let get_path_to_page course page =
- course_dir ^ course ^ "/" ^ page
-
-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_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 "/course/:course_name/" (fun request -> Dream.html (Render.render_sidebar @@ Render.render_course_home (Dream.param request "course_name")));
+ Dream.get "/course/:course_name/page/:page_name" (fun request -> Dream.html @@ Render.render_sidebar @@ Render.render_page (Dream.param request "course_name") (Dream.param request "page_name"));
Dream.get "/static/**" (Dream.static "./static");
- Dream.get "/" (fun _ -> Dream.html (render_sidebar @@ render_course_list));
+ Dream.get "/" (fun _ -> Dream.html (Render.render_sidebar @@ Render.render_course_list));
Dream.get "/course/:course_name/page/assets/**" (fun request -> Dream.static
- (course_dir ^ (Dream.param request "course_name") ^ "/assets") request);
+ (Shared.course_dir ^ (Dream.param request "course_name") ^ "/assets") request);
]
diff --git a/learning/bin/parser.ml b/learning/bin/parser.ml
@@ -0,0 +1,52 @@
+type page_line = {
+ header_level: int;
+ content: string;
+ is_embedded_video: bool;
+ is_embedded_image: bool;
+ is_preformatted: bool;
+ is_hidden: bool;
+}
+
+let parse_simple_line line preformatted =
+ let hidden = if not preformatted && String.length(line) > 2 && line.[0] = '?' && line.[1] = '?' then true else false in
+ let hash_count = if not preformatted then Basic_functions.count_prefix '#' 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; is_hidden = hidden}
+
+
+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 preformatted acc =
+ try
+ 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;
+ if String.length acc != 0 then parse_simple_line acc true :: [] else []
+ in readAll false ""
+
+let readFile fileName =
+ let ic = open_in fileName in
+ let rec readAll acc =
+ try
+ readAll (acc ^ ((input_line ic)) ^ "\n")
+ with End_of_file ->
+ close_in_noerr ic;
+ acc
+ in readAll ""
+
+let get_pages page_arr =
+ readFile page_arr.(0)
diff --git a/learning/bin/render.ml b/learning/bin/render.ml
@@ -0,0 +1,41 @@
+open Jingoo
+
+let to_tval value =
+ Jg_types.Tobj [("name", Jg_types.Tstr value);]
+
+let get_pages_in_order course_name =
+ let arr = Sys.readdir (Shared.course_dir ^ course_name) in
+ Array.sort compare arr;
+ let lst = List.filter Basic_functions.has_sl_postfix (Array.to_list arr) in
+ lst
+
+let get_course_home course_name =
+ List.map to_tval (get_pages_in_order course_name)
+
+let get_courses =
+ let arr = Sys.readdir Shared.course_dir in
+ Array.sort compare arr;
+ Jg_types.Tlist
+ (Array.to_list (Array.map to_tval arr))
+
+let render_course_list =
+ (Jg_template.from_file "./templates/course_list.jingoo" ~models:[("courses", get_courses)])
+
+
+
+let render_sidebar s =
+ (Jg_template.from_file "./templates/sidebar.jingoo" ~models:[("courses", get_courses)]) ^ s
+
+let page_line_to_tobj (line : Parser.page_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); ("is_preformatted", Jg_types.Tbool line.is_preformatted); ("is_hidden", Jg_types.Tbool line.is_hidden)]
+
+let render_course_home course_name =
+ (Jg_template.from_file "./templates/course_home.jingoo" ~models:[("pages", (Jg_types.Tlist (get_course_home course_name)))])
+
+let get_path_to_page course page =
+ Shared.course_dir ^ course ^ "/" ^ page
+
+let render_page course page =
+ let page_lines : Parser.page_line list = Parser.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)]))
+
diff --git a/learning/bin/shared.ml b/learning/bin/shared.ml
@@ -0,0 +1 @@
+let course_dir = "../courses/"