diff --git a/Makefile b/Makefile
index 532382923..0217e3701 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,7 @@ IPADDR ?= static
FLAGS ?=
-.PHONY: all configure build run clean
+.PHONY: all configure build run clean news
all:
@echo "To build this website, look in the Makefile and set"
@@ -39,7 +39,10 @@ configure:
depend:
cd src && make depend
-build:
+news:
+ cd news && make run
+
+build: news
cd src && make build
run:
@@ -47,4 +50,5 @@ run:
clean:
cd src && make clean
+ cd feeds && make clean
$(RM) log src/mir-www src/*.img src/make-fat*.sh
diff --git a/files/css/site.css b/files/css/site.css
index 3311b9bbd..ffa15f26e 100644
--- a/files/css/site.css
+++ b/files/css/site.css
@@ -7,7 +7,7 @@ h4,h5,h6 { color: #222222; font-size: 1.4rem; font-weight: 600; }
.front_updates h4 { font-style: italic; font-size: 1.2rem; }
.front_updates ul { font-size: 1.0rem; }
.front_updates a i { font-style: normal; color: #332222; }
-.front_updates .front_date { color: #777799; }
+.front_date { color: #777799; }
.panel {
padding-top: 0.5rem;
diff --git a/news/.merlin b/news/.merlin
new file mode 100644
index 000000000..14ec7b7e5
--- /dev/null
+++ b/news/.merlin
@@ -0,0 +1,8 @@
+PKG cow
+PKG cow.syntax
+PKG cowabloga
+PKG lwt
+PKG cohttp
+PKG cohttp.lwt
+PKG xmlm
+PKG syndic
diff --git a/news/Makefile b/news/Makefile
new file mode 100644
index 000000000..9441ff6d5
--- /dev/null
+++ b/news/Makefile
@@ -0,0 +1,21 @@
+LIBS = -pkgs lwt.syntax,cow.syntax,cowabloga,lwt,cohttp,cohttp.lwt,xmlm,syndic
+PKGS = cow cowabloga lwt cohttp xmlm syndic
+SYNTAX = -tags "syntax(camlp4o)"
+OPAM = opam
+
+.PHONY: all depend run build clean
+
+all: build
+
+depend:
+ $(OPAM) install $(PKGS) --verbose
+
+run: build
+ @echo "updating news pages"
+ @./news.native
+
+build: depend
+ ocamlbuild -use-ocamlfind $(LIBS) $(SYNTAX) news.native
+
+clean:
+ ocamlbuild -clean
diff --git a/news/feeds.ml b/news/feeds.ml
new file mode 100644
index 000000000..25cd1a6a2
--- /dev/null
+++ b/news/feeds.ml
@@ -0,0 +1,7 @@
+let feeds = [
+ ("Anil Madhavapeddy", "http://anil.recoil.org/feeds/atom.xml");
+ ("Mindy Preston", "http://www.somerandomidiot.com/atom.xml");
+ ("Amir Chaudhry", "http://feeds.feedburner.com/amirmc");
+ ("My Little Garden of Code", "https://philtomson.github.io/atom.xml");
+ ("Thomas Leonard", "http://roscidus.com/blog/atom.xml");
+]
diff --git a/news/news.ml b/news/news.ml
new file mode 100644
index 000000000..e73d4cf78
--- /dev/null
+++ b/news/news.ml
@@ -0,0 +1,160 @@
+open Lwt
+open Cohttp
+open Cow
+open Cowabloga
+open Syndic_atom
+
+
+(*** Syndic and RSS feeds ***)
+
+let string_of_text (t:text_construct) : string = match t with
+ | Text(s) -> s
+ | Html(_,s) -> s
+ | Xhtml(_,xs) -> List.fold_left (fun acc x -> acc ^ Syndic_xml.to_string x) "" xs
+
+(* given a feed as a string, try parsing it as Atom, then as RSS2 *)
+let feed_of_string (u : string) (s : string) : Syndic_atom.feed option =
+ try
+ Some (Syndic_atom.parse (Xmlm.make_input (`String(0, s))))
+ with _ -> try
+ let rss = Syndic_rss2.parse (Xmlm.make_input (`String(0, s))) in
+ let atom = Syndic_rss2.to_atom rss in
+ Some atom
+ with _ ->
+ Printf.fprintf stderr "Error parsing feed: %s\n" u;
+ None
+
+(* compare Syndic entries by date *)
+let by_date (e1 : entry) (e2 : entry) : int =
+ Syndic_date.compare e2.updated e1.updated
+
+
+(*** HTML formatting ***)
+type news_item = {
+ title: string;
+ author:string;
+ date:int * string * int;
+ uri:string
+}
+
+(* extract data required to display a news item *)
+let mk_news_item ((n: string), (e : entry)) : news_item =
+ let date = e.updated in
+ let title = string_of_text e.title in
+ let uri = match e.links with
+ [] -> ""
+ | link :: _ -> Uri.to_string link.href
+ in
+ let open Syndic_date in
+ let day = day date in
+ let year = year date in
+ let month = month date |> string_of_month in
+ {title; author=n; date=(day, month, year); uri}
+
+
+(* format a news item for news page *)
+let news_page_item ((n: string), (e : entry)) =
+ let {title; author; date=(day, month, year); uri} = mk_news_item (n,e) in
+ let date = Printf.sprintf "%d %s %d" day month year in
+ <:html<
+
+
+
$str:author$ ($str:date$)
+
+ &>>
+
+(* format a news item for home page *)
+let home_page_item ((n: string), (e : entry)) =
+ let {title; date=(day, month, year); uri} = mk_news_item (n,e) in
+ let date = Printf.sprintf "%d %s %d" day month year in
+ <:html<
+
+ $str:title$
+ ($str:date$) &>>
+
+
+(* format a feed an HTML list item *)
+let syndication_item (name, uri) =
+ <:html<
+ $str:name$
+>>
+
+(* format the news page as a list of entries and a list of feeds *)
+let news_page feeds (es : (string * entry) list) =
+ <:html<
+
+
+
+
+ Here, we aggregate various blogs from the Mirage community.
+ If you would like to be added, please
+ get in touch .
+
+
+ $list:List.map news_page_item es$
+
+
+ Syndication
+
+ $list:List.map syndication_item feeds$
+
+
+
+ >>
+
+(* format the latest news as a list *)
+let latest_news es =
+ let ns = List.map (fun (n, e) -> home_page_item (n, e)) es in
+ <:html< &>>
+
+
+(*** Feeds retrieval and processing ***)
+
+(* return a list of named, Syndic entries, in chronological order *)
+let named_entries feeds : (string * entry) list Lwt.t =
+ let http_get (uri : string) : string Lwt.t =
+ Cohttp_lwt_unix.Client.get (Uri.of_string uri) >>= fun (_, body) ->
+ Cohttp_lwt_body.to_string body
+ in
+ let rec join = function [] -> [] | xs::xss -> xs @ join xss
+ in
+ Lwt_list.map_p (fun (n, u) ->
+ http_get u >>= fun s -> return
+ (match feed_of_string u s with
+ Some(f) -> List.map (fun e -> (n, e)) f.entries
+ | None -> [])
+ ) feeds >>= fun ess ->
+ let es = join ess in
+ Lwt.return @@ List.sort (fun (_,e1) (_,e2) -> by_date e1 e2) es
+
+(* write HTML news page *)
+let write_news_page feeds (es : (string * entry) list) : unit Lwt.t =
+ let html_page = news_page feeds es in
+ let fname = "../tmpl/news.html" in
+ Lwt_io.with_file
+ Lwt_io.output fname (fun ch -> Lwt_io.fprint ch (Html.to_string html_page))
+
+(* write a list of latest news for homepage *)
+let write_latest_news es =
+ let take n xs =
+ let rec take_aux n xs acc =
+ match (n, xs) with
+ (n, _) when n <= 0 -> acc
+ | (_, []) -> acc
+ | (n, x::xs) -> take_aux (n-1) xs (x::acc)
+ in List.rev (take_aux n xs [])
+ in
+ let latest_news = latest_news (take 10 es) in
+ let fname = "../tmpl/latest_news.html" in
+ Lwt_io.with_file
+ Lwt_io.output fname (fun ch -> Lwt_io.fprint ch (Html.to_string latest_news))
+
+let write_news feeds es =
+ (write_news_page feeds es) <&> (write_latest_news es)
+let _ =
+ let feeds = List.sort (fun (n1,_) (n2,_) -> compare n1 n2) Feeds.feeds in
+ Lwt_main.run (named_entries feeds >>= (write_news feeds))
diff --git a/src/dispatch.ml b/src/dispatch.ml
index 4218bd2ea..f2f7c5b89 100644
--- a/src/dispatch.ml
+++ b/src/dispatch.ml
@@ -48,7 +48,7 @@ module Main
`Blog (blog_feed, Data.Blog.entries);
`Wiki (wiki_feed, Data.Wiki.entries);
] in
-
+
lwt blog_dispatch = Blog.dispatch blog_feed Data.Blog.entries in
lwt wiki_dispatch = Wiki.dispatch wiki_feed Data.Wiki.entries in
lwt releases_dispatch = Pages.Releases.dispatch read_tmpl in
@@ -65,14 +65,14 @@ module Main
return (`Html (Pages.Index.t ~feeds:updates_feeds read_tmpl))
| ["about"]
- | ["community"] ->
- return (`Html (Pages.About.t read_tmpl))
+ | ["community"] -> return (`Html (Pages.About.t read_tmpl))
+ | ["news"] -> return (`Html (Pages.News.t read_tmpl))
| "releases" :: tl -> return (`Page (releases_dispatch tl))
| "blog" :: tl -> return (`Page (blog_dispatch tl))
| "links" :: tl -> return (links_dispatch tl)
| "updates" :: tl -> return (`Page (updates_dispatch tl))
-
+
| "docs" :: tl
| "wiki" :: tl -> return (`Page (wiki_dispatch tl))
diff --git a/src/pages.ml b/src/pages.ml
index 69d55c43d..067b92feb 100644
--- a/src/pages.ml
+++ b/src/pages.ml
@@ -37,7 +37,7 @@ module Global = struct
Background
Contact
Team
- Blogroll
+ News
Links
@@ -71,6 +71,7 @@ module Index = struct
lwt l2 = read_file read_fn "/intro-3.md" in
lwt footer = read_file read_fn "/intro-f.html" in
lwt recent = Cowabloga.Feed.to_html ~limit:12 feeds in
+ lwt latest_news = read_file read_fn "/latest_news.html" in
let content = <:html<
@@ -83,6 +84,9 @@ module Index = struct
Recent Updates (all)
$recent$
+
+ Latest News (all)
+ $latest_news$
@@ -166,7 +170,6 @@ module About = struct
lwt r = read_file read_fn "/about-community.md" in
lwt b = read_file read_fn "/about-b.md" in
lwt f = read_file read_fn "/about-funding.md" in
- lwt br = read_file read_fn "/about-blogroll.md" in
let content = <:html<
@@ -184,14 +187,24 @@ module About = struct
$l$
$r$
-
-
-
>> in
return (Global.page ~title:"Community" ~headers:[] ~content)
end
+
+module News = struct
+ let t read_fn =
+ lwt bs = read_file read_fn "news.html" in
+ let content = <:html<
+
+ >> in
+ return (Global.page ~title:"News" ~headers:[] ~content)
+end
+
+
+
module Releases = struct
let content_type_xhtml = Cowabloga.Headers.html