···11+type image = {
22+ filename : string;
33+ description : string option;
44+}
55+66+type frontmatter = {
77+ title: string option;
88+ date: Ptime.t;
99+ synopsis: string option;
1010+ titleimage: image option;
1111+ draft: bool;
1212+}
1313+1414+type t = {
1515+ frontmatter : frontmatter;
1616+ url : string;
1717+}
1818+1919+let yaml_dict_to_string a k =
2020+ match List.assoc_opt k a with
2121+ | None -> None
2222+ | Some v -> (
2323+ match v with
2424+ | `String str -> Some str
2525+ | _ -> None
2626+ )
2727+2828+let yaml_dict_to_bool a k =
2929+ match List.assoc_opt k a with
3030+ | None -> None
3131+ | Some v -> (
3232+ match v with
3333+ | `Bool b -> Some b
3434+ | _ -> None
3535+ )
3636+3737+let yaml_dict_to_date a k =
3838+ match List.assoc_opt k a with
3939+ | None -> None
4040+ | Some v -> (
4141+ match v with
4242+ | `String str -> (
4343+ match (Ptime.of_rfc3339 str) with
4444+ | Ok (t, _, _) -> Some t
4545+ | _ -> (
4646+ (* Some are not RFC3339, they're '2019-09-30 08:57:22' or such *)
4747+ Scanf.sscanf str "%d-%d-%d %d:%d:%d" (fun y m d h i s -> Ptime.of_date_time ((y, m, d), ((h, i, s), 0)))
4848+ )
4949+ )
5050+ | _ -> None
5151+ )
5252+5353+let yaml_dict_to_image a k =
5454+ match List.assoc_opt k a with
5555+ | None -> None
5656+ | Some v -> (
5757+ match v with
5858+ | `O assoc -> (
5959+ match yaml_dict_to_string assoc "image" with
6060+ | Some filename -> Some {
6161+ filename ;
6262+ description = yaml_dict_to_string assoc "alt"
6363+ }
6464+ | None -> None
6565+6666+ )
6767+ | `String filename -> (
6868+ Some { filename ; description=None }
6969+ )
7070+ | _ -> None
7171+ )
7272+7373+let yaml_to_struct y =
7474+ match y with
7575+ | `O assoc -> (
7676+ {
7777+ title = yaml_dict_to_string assoc "title";
7878+ date = (match (yaml_dict_to_date assoc "date") with Some d -> d | None -> Ptime.epoch);
7979+ synopsis = yaml_dict_to_string assoc "synopsis";
8080+ titleimage = yaml_dict_to_image assoc "titleimage";
8181+ draft = (match (yaml_dict_to_bool assoc "draft") with Some b -> b | None -> true);
8282+ }
8383+ )
8484+ | _ -> failwith "malformed yaml"
8585+8686+let read_frontmatter path =
8787+ let raw_frontmatter = In_channel.with_open_text (Fpath.to_string path) (fun ic ->
8888+ let parts = In_channel.input_all ic |> Astring.String.cuts ~sep:"---" in
8989+ match parts with
9090+ | _ :: fm :: _body -> fm
9191+ | _ -> failwith (Printf.sprintf "failed to parse %s" (Fpath.to_string path))
9292+ ) in
9393+ let frontmatter = String.trim raw_frontmatter |> Yaml.of_string_exn in
9494+ yaml_to_struct frontmatter
9595+9696+9797+let of_file ~base path =
9898+ let url = Fpath.rem_prefix base path in
9999+ let frontmatter = try
100100+ read_frontmatter path
101101+ with
102102+ | Not_found | Invalid_argument _ -> failwith (Printf.sprintf "Failed to find key in %s" (Fpath.to_string path))
103103+ in
104104+105105+ match url with
106106+ | Some url ->{ frontmatter ; url="/" ^ Fpath.to_string url}
107107+ | None -> failwith "base is not parent directory"
108108+109109+let title t =
110110+ match t.frontmatter.title with
111111+ | Some t -> t
112112+ | None -> "Untitled"
113113+114114+let url t = t.url
115115+116116+let date t = t .frontmatter.date
117117+118118+let synopsis t = t.frontmatter.synopsis
119119+120120+let titleimage t = t.frontmatter.titleimage
121121+122122+let draft t = t.frontmatter.draft
123123+
···11+type t
22+33+val of_directory : base:Fpath.t -> Fpath.t -> t
44+55+val pages : t -> Page.t list
66+77+val title : t -> string
88+99+val url : t -> string
1010+
···11+type t
22+33+val of_directory : Fpath.t -> t
44+55+val sections : t -> Section.t list
66+77+val path : t -> Fpath.t
88+99+val title : t -> string