Skip to content

Commit 07403c8

Browse files
committed
Canopy now supports multiple kind of content
Before, the only kind of content expected on Canopy was markdown. This commit introduce Canopy_content and hide Markdown articles behind a type that will provide interfaces to generate html from any kind of file content type. A file content type is a simple field inside Canopy's header --- title: Title content: irclog ---
1 parent 30f2024 commit 07403c8

7 files changed

+177
-134
lines changed

canopy_article.ml

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
open Canopy_utils
2+
open Html5.M
3+
4+
type t = {
5+
title : string;
6+
content : string;
7+
author : string;
8+
abstract : string option;
9+
uri : string;
10+
date: string;
11+
tags: string list;
12+
}
13+
14+
let of_string meta uri date content =
15+
try
16+
let split_tags = Re_str.split (Re_str.regexp ",") in
17+
let content = Cow.Markdown.of_string content |> Cow.Html.to_string in
18+
let author = List.assoc "author" meta in
19+
let title = List.assoc "title" meta in
20+
let tags = assoc_opt "tags" meta |> map_opt split_tags [] in
21+
let abstract = assoc_opt "abstract" meta in
22+
Some {title; content; author; uri; abstract; date; tags}
23+
with
24+
| _ -> None
25+
26+
let to_tyxml article =
27+
let author = "Written by " ^ article.author in
28+
let updated = "Last updated: " ^ article.date in
29+
let tags = Canopy_templates.taglist article.tags in
30+
[div ~a:[a_class ["post"]] [
31+
h2 [pcdata article.title];
32+
span ~a:[a_class ["author"]] [pcdata author];
33+
br ();
34+
span ~a:[a_class ["date"]] [pcdata updated];
35+
br ();
36+
tags;
37+
br ();
38+
Html5.M.article [Unsafe.data article.content]
39+
]]
40+
41+
let to_tyxml_listing_entry article =
42+
let author = "Written by " ^ article.author in
43+
let abstract = match article.abstract with
44+
| None -> []
45+
| Some abstract -> [p ~a:[a_class ["list-group-item-text abstract"]] [pcdata abstract]] in
46+
let content = [
47+
h4 ~a:[a_class ["list-group-item-heading"]] [pcdata article.title];
48+
span ~a:[a_class ["author"]] [pcdata author];
49+
br ();
50+
] in
51+
a ~a:[a_href article.uri; a_class ["list-group-item"]] (content ++ abstract)

canopy_content.ml

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
open Canopy_utils
2+
3+
type t =
4+
| Markdown of Canopy_article.t
5+
| Unknown
6+
| Error of string
7+
8+
let meta_assoc str =
9+
Re_str.split (Re_str.regexp "\n") str |>
10+
List.map (fun meta ->
11+
let reg = Re_str.regexp "\\(.*\\): \\(.*\\)" in
12+
let _ = Re_str.string_match reg meta 0 in
13+
let key = Re_str.matched_group 1 meta in
14+
let value = Re_str.matched_group 2 meta in
15+
key, value)
16+
17+
let of_string ~uri ~date ~content =
18+
let splitted_content = Re_str.bounded_split (Re_str.regexp "---") content 2 in
19+
match splitted_content with
20+
| [raw_meta;raw_content] ->
21+
begin
22+
match meta_assoc raw_meta with
23+
| meta ->
24+
begin
25+
match assoc_opt "content" meta with
26+
| Some "markdown"
27+
| None ->
28+
Canopy_article.of_string meta uri date raw_content
29+
|> map_opt (fun article -> Markdown article) (Error "Error while parsing article")
30+
| Some _ -> Unknown
31+
end
32+
| exception _ -> Unknown
33+
end
34+
| _ -> Error "No header found"
35+
36+
let to_tyxml = function
37+
| Markdown m ->
38+
let open Canopy_article in
39+
m.title, to_tyxml m
40+
| Unknown -> "Error", Canopy_templates.error "Unknown file content"
41+
| Error msg -> "Error", Canopy_templates.error msg
42+
43+
let to_tyxml_listing_entry = function
44+
| Markdown m -> Canopy_article.to_tyxml_listing_entry m
45+
| Unknown -> Canopy_templates.empty
46+
| Error _ -> Canopy_templates.empty
47+
48+
let find_tag tagname = function
49+
| Markdown m ->
50+
let open Canopy_article in
51+
List.exists ((=) tagname) m.tags
52+
| Unknown
53+
| Error _ -> false

canopy_main.ml

+20-22
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module Main (C: CONSOLE) (RES: Resolver_lwt.S) (CON: Conduit_mirage.S) (S:Cohtt
1616
let start console res ctx http disk _ =
1717

1818
let open Canopy_config in
19-
let open Canopy_types in
19+
let open Canopy_utils in
2020
let config = Canopy_config.config () in
2121
let module Context =
2222
( struct
@@ -25,21 +25,15 @@ module Main (C: CONSOLE) (RES: Resolver_lwt.S) (CON: Conduit_mirage.S) (S:Cohtt
2525
in
2626
let module Store = Canopy_store.Store(C)(Context)(Inflator) in
2727

28-
let articles_hashtable = KeyHashtbl.create 32 in
29-
30-
let flatten_option_list l =
31-
List.fold_left
32-
(fun xs x -> match x with
33-
| None -> xs
34-
| Some x -> x::xs) [] l in
28+
let content_hashtbl = KeyHashtbl.create 32 in
3529

3630
let respond_html ~status ~content ~title =
3731
Store.get_subkeys [] >>= fun keys ->
38-
let body = Canopy_templates.template_main ~config ~content ~title ~keys in
32+
let body = Canopy_templates.main ~config ~content ~title ~keys in
3933
S.respond_string ~status ~body () in
4034

4135
Store.pull console >>= fun _ ->
42-
Store.fill_cache articles_hashtable >>= fun _ ->
36+
Store.fill_cache content_hashtbl >>= fun _ ->
4337

4438
let rec dispatcher uri =
4539
let s_uri = Re_str.split (Re_str.regexp "/") (Uri.pct_decode uri) in
@@ -58,31 +52,35 @@ module Main (C: CONSOLE) (RES: Resolver_lwt.S) (CON: Conduit_mirage.S) (S:Cohtt
5852

5953
| uri::[] when uri = config.push_hook_path ->
6054
Store.pull console >>= fun _ ->
61-
KeyHashtbl.clear articles_hashtable |> Lwt.return >>= fun _ ->
62-
Store.fill_cache articles_hashtable >>= fun _ ->
55+
KeyHashtbl.clear content_hashtbl |> Lwt.return >>= fun _ ->
56+
Store.fill_cache content_hashtbl >>= fun _ ->
6357
S.respond_string ~status:`OK ~body:"" ()
6458

6559
| "tags"::tagname::_ ->
66-
let is_in_tags = List.exists ((=) tagname) in
67-
let articles = KeyHashtbl.fold (fun _ v l -> if is_in_tags v.tags then v::l else l)
68-
articles_hashtable [] in
69-
let content = Canopy_templates.template_listing articles in
70-
respond_html ~status:`OK ~title:"Listing" ~content
60+
let aux _ v l =
61+
if Canopy_content.find_tag tagname v then (v::l) else l in
62+
let content =
63+
KeyHashtbl.fold aux content_hashtbl []
64+
|> List.map Canopy_content.to_tyxml_listing_entry
65+
|> Canopy_templates.listing in
66+
respond_html ~status:`OK ~title:"Listing" ~content
7167

7268
| key ->
7369
begin
74-
match KeyHashtbl.find_opt articles_hashtable key with
70+
match KeyHashtbl.find_opt content_hashtbl key with
7571
| None ->
7672
Store.get_subkeys key >>= fun keys ->
7773
if (List.length keys) = 0 then
7874
S.respond_string ~status:`Not_found ~body:"Not found" ()
7975
else
80-
let articles = List.map (KeyHashtbl.find_opt articles_hashtable) keys in
81-
let content = flatten_option_list articles |> Canopy_templates.template_listing in
76+
let articles = List.map (KeyHashtbl.find_opt content_hashtbl) keys in
77+
let content =
78+
list_map_opt Canopy_content.to_tyxml_listing_entry articles
79+
|> Canopy_templates.listing in
8280
respond_html ~status:`OK ~title:"Listing" ~content
8381
| Some article ->
84-
let content = Canopy_templates.template_article article in
85-
respond_html ~status:`OK ~title:article.title ~content
82+
let title, content = Canopy_content.to_tyxml article in
83+
respond_html ~status:`OK ~title ~content
8684
end
8785

8886
in

canopy_store.ml

+4-6
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,18 @@ module Store (C: CONSOLE) (CTX: Irmin_mirage.CONTEXT) (INFL: Git.Inflate.S) = st
6868
CalendarLib.Printer.Calendar.sprint "%d/%m/%Y" cal |> Lwt.return
6969

7070
let fill_cache article_hashtbl =
71-
let open Canopy_types in
7271
let iter_fn key value =
73-
value >>= fun value ->
72+
value >>= fun content ->
7473
date_updated_last key >>= fun date ->
7574
let uri = List.fold_left (fun s a -> s ^ "/" ^ a) "" key in
76-
match article_of_string uri value date with
77-
| None -> Lwt.return_unit
78-
| Some article -> KeyHashtbl.replace article_hashtbl key article |> Lwt.return
75+
Canopy_content.of_string ~uri ~content ~date
76+
|> KeyHashtbl.replace article_hashtbl key
77+
|> Lwt.return
7978
in
8079
new_task () >>= fun t ->
8180
Store.iter (t "Iterating through values") iter_fn
8281

8382
let setup_watch hashtbl =
84-
let open Canopy_types in
8583
new_task () >>= fun t ->
8684
Store.watch_head (t "watch branch") (fun _ ->
8785
KeyHashtbl.clear hashtbl |> Lwt.return >>= fun _ ->

canopy_templates.ml

+12-37
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
open Canopy_types
21
open Canopy_config
2+
open Canopy_utils
33
open Html5.M
44

55
module StringPrinter = struct
@@ -14,15 +14,13 @@ end
1414

1515
module StringHtml = Html5.Make_printer(StringPrinter)
1616

17-
let (++) = List.append
18-
19-
let template_taglist tags =
17+
let taglist tags =
2018
let format_tag tag =
2119
let taglink = Printf.sprintf "/tags/%s" in
2220
a ~a:[taglink tag |> a_href; a_class ["tag"]] [pcdata tag] in
2321
List.map format_tag tags |> div ~a:[a_class ["tags"]]
2422

25-
let template_links keys =
23+
let links keys =
2624
let paths = List.map (function
2725
| x::_ -> x
2826
| _ -> assert false
@@ -34,8 +32,8 @@ let template_links keys =
3432
let script_mathjax =
3533
[script ~a:[a_src "https://travis-ci.org/Engil/Canopy"] (pcdata "")]
3634

37-
let template_main ~config ~content ~title ~keys =
38-
let links = template_links keys in
35+
let main ~config ~content ~title ~keys =
36+
let links = links keys in
3937
let mathjax = if config.mathjax then script_mathjax else [] in
4038
let page =
4139
html
@@ -77,37 +75,14 @@ let template_main ~config ~content ~title ~keys =
7775
in
7876
StringHtml.print page
7977

80-
81-
let template_article article =
82-
let author = "Written by " ^ article.author in
83-
let updated = "Last updated: " ^ article.date in
84-
let tags = template_taglist article.tags in
85-
[div ~a:[a_class ["post"]] [
86-
h2 [pcdata article.title];
87-
span ~a:[a_class ["author"]] [pcdata author];
88-
br ();
89-
span ~a:[a_class ["date"]] [pcdata updated];
90-
br ();
91-
tags;
92-
br ();
93-
Html5.M.article [Unsafe.data article.content]
94-
]]
95-
96-
let template_listing_entry article =
97-
let author = "Written by " ^ article.author in
98-
let abstract = match article.abstract with
99-
| None -> []
100-
| Some abstract -> [p ~a:[a_class ["list-group-item-text abstract"]] [pcdata abstract]] in
101-
let content = [
102-
h4 ~a:[a_class ["list-group-item-heading"]] [pcdata article.title];
103-
span ~a:[a_class ["author"]] [pcdata author];
104-
br ();
105-
] in
106-
a ~a:[a_href article.uri; a_class ["list-group-item"]] (content ++ abstract)
107-
108-
let template_listing articles =
109-
let entries = List.map template_listing_entry articles in
78+
let listing entries =
11079
[div ~a:[a_class ["flex-container"]] [
11180
div ~a:[a_class ["list-group listing"]] entries
11281
]
11382
]
83+
84+
let error msg =
85+
[div ~a:[a_class ["alert alert-danger"]] [pcdata msg]]
86+
87+
let empty =
88+
div []

canopy_types.ml

-69
This file was deleted.

0 commit comments

Comments
 (0)