generate an atom feed

This commit is contained in:
Dan Frumin 2020-08-29 14:28:11 +02:00
parent 72c07b6b55
commit e71a904120
5 changed files with 116 additions and 27 deletions

View File

@ -3,19 +3,21 @@ cache := .cache
www_root := _site www_root := _site
www := $(www_root)/posts www := $(www_root)/posts
post_index := $(www_root)/posts.html post_index := $(www_root)/posts.html
feed := $(www_root)/feed.xml
post_compiler := src/webcc.ml post_compiler := src/webcc.ml
builddir := _build/default builddir := _build/default
POSTCC := $(builddir)/$(patsubst %.ml,%.exe,$(post_compiler)) POSTCC := $(builddir)/$(patsubst %.ml,%.exe,$(post_compiler))
INDEXCC := $(POSTCC) -i INDEXCC := $(POSTCC) -i
FEEDCC := $(POSTCC) -a
post-sources := $(shell find $(src)/ -type f) post-sources := $(shell find $(src)/ -type f)
post-htmls := $(patsubst $(src)/%.md,$(www)/%.html,$(post-sources)) post-htmls := $(patsubst $(src)/%.md,$(www)/%.html,$(post-sources))
.PHONY: all serve clean $(POSTCC) .PHONY: all serve clean $(POSTCC)
all: $(POSTCC) $(post-htmls) $(post_index) $(www_root)/static all: $(POSTCC) $(post-htmls) $(post_index) $(feed) $(www_root)/static
cp -R ~/www/* $(www_root) cp -R ~/www/* $(www_root)
$(www): $(www):
@ -27,6 +29,9 @@ $(www)/%.html: $(src)/%.md $(www)
$(post_index): $(post-sources) $(post_index): $(post-sources)
$(INDEXCC) $^ -o $@ $(INDEXCC) $^ -o $@
$(feed): $(post-sources)
$(FEEDCC) $^ -o $@
$(www_root)/static: static $(www_root)/static: static
cp -R static $(www_root) cp -R static $(www_root)

44
src/atom.ml Normal file
View File

@ -0,0 +1,44 @@
open Post
let format_entry ~title ~date ~url ~contents ~author =
String.concat ""
[
{|<entry>
<title>|}; title; {|</title>
<id>|};
url;
{|</id>
<author>
<name>|}; author; {|</name>
</author>
<updated>|}; ISO8601.Permissive.string_of_datetime date;{|</updated>
<link href="|}; url ;{|" rel="alternate" />
<content type="xhtml"> <div xmlns="http://www.w3.org/1999/xhtml">|};
contents;
{| </div></content>
</entry>
|}]
let format_feed ~title ~subtitle ~author ~feed_url ~site_url ~last_updated ~(entries: Post.t list) =
let opener =
String.concat "" [
{|<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>|}; title; {|</title>
<id>|}; feed_url; {|</id>
<subtitle>|}; subtitle; {|</subtitle>
<updated>|}; ISO8601.Permissive.string_of_datetime last_updated; {|</updated>
<link href="|}; feed_url; {|" rel="self"/>
<link href="|}; site_url; {|"/>
|}] in
opener ^
(String.concat "\n" @@ List.map
(fun (md,contents) ->
format_entry
~title:md.title
~date:md.date
~url:(site_url ^ md.filename)
~author
~contents)
entries) ^
"</feed>"

View File

@ -1,3 +1,4 @@
(executable (executable
(name webcc) (name webcc)
(libraries omd ISO8601 str)) (libraries omd ISO8601 str)
(modules_without_implementation post))

5
src/post.mli Normal file
View File

@ -0,0 +1,5 @@
type date = float (* repr for the ISO8601 library *)
type metadata = { title : string ; date : date ; tags : string list ;
filename : string ;
other : (string * string) list }
type t = metadata * string

View File

@ -1,21 +1,27 @@
open Post
let author = "Daniel"
let site_url = "https://groupoid.moe"
let feed_url = "https://groupoid.moe/feed.xml"
let site_title = "groupoid.moe"
let feed_title = "Recent posts"
let input = ref [] let input = ref []
let output = ref "" let output = ref ""
let gen_index = ref false let gen_index = ref false
let gen_atom = ref false
let spec = let spec =
[ [
("-o", Arg.Set_string output, ("-o", Arg.Set_string output,
" file.html Specify the output file (default is stdout)."); " file.html Specify the output file (default is stdout).");
("-i", Arg.Set gen_index, ("-i", Arg.Set gen_index,
" generate a post index instead of compiling individual posts."); " generate the post index instead of compiling individual posts.");
("-a", Arg.Set gen_atom,
" generate the atom feed.");
("--", Rest(fun s -> input := s :: !input), ("--", Rest(fun s -> input := s :: !input),
" Consider all remaining arguments as input file names.") " Consider all remaining arguments as input file names.")
] ]
type post_metadata = { title : string ; date : float ; tags : string list ;
filename : string ;
other : (string * string) list }
(** Utitilies *) (** Utitilies *)
let with_open_in fn f = let with_open_in fn f =
let ic = open_in fn in let ic = open_in fn in
@ -29,7 +35,7 @@ let with_open_out fn f =
| r -> close_out oc; r | r -> close_out oc; r
| exception e -> close_out_noerr oc; raise e | exception e -> close_out_noerr oc; raise e
let read_metadata filename ic : post_metadata = let read_metadata filename ic : Post.metadata =
let rec go lst = let rec go lst =
let line = let line =
try input_line ic try input_line ic
@ -46,12 +52,23 @@ let read_metadata filename ic : post_metadata =
| None -> "-" | None -> "-"
in in
let date = ISO8601.Permissive.date (lookup "date") in let date = ISO8601.Permissive.date (lookup "date") in
(* TODO! Fix the paths here!!!!!! *)
let target_fname =
let open Str in
if string_match (regexp {|content/\(.*\)\.md|}) filename 0
then "/posts/" ^ matched_group 1 filename ^ ".html"
else filename
in
{ title = lookup "title"; { title = lookup "title";
date = date; date = date;
tags = String.split_on_char ',' (lookup "tags"); tags = String.split_on_char ',' (lookup "tags");
filename = filename; filename = target_fname;
other = md } other = md }
let read_post filename ic : Post.t =
let metadata = read_metadata filename ic in
let md = Omd.of_channel ic in
(metadata, Omd.to_html md)
(** Functions for processing an individual post *) (** Functions for processing an individual post *)
@ -77,7 +94,7 @@ let format_page ~title ~contents =
<span class="menu-item"><a href="/feed.xml">atom feed</a></span> <span class="menu-item"><a href="/feed.xml">atom feed</a></span>
</div> </div>
<div id="wrapper"> <div id="wrapper">
<h1>|}; title; {|</h1>|}; <h1>|}; title; {|</h1>|};
contents; contents;
{| </div> {| </div>
<hr /> <hr />
@ -97,41 +114,32 @@ let format_post ~title ~tags ~date ~contents =
(* convert contents from [ic] to a post webpage, output to [oc] *) (* convert contents from [ic] to a post webpage, output to [oc] *)
let process filename ic oc = let process filename ic oc =
let metadata = read_metadata filename ic in let (metadata, contents) = read_post filename ic in
let md = Omd.of_channel ic in
let html = format_post let html = format_post
~title:(metadata.title) ~title:(metadata.title)
~tags:(String.concat ", " metadata.tags) ~tags:(String.concat ", " metadata.tags)
~date:(ISO8601.Permissive.string_of_date metadata.date) ~date:(ISO8601.Permissive.string_of_date metadata.date)
~contents:(Omd.to_html md) ~contents
in in
output_string oc html output_string oc html
(** Compile the posts to HTML *)
let compile_posts oc = let compile_posts oc =
if !input = [] then process "<stdin>" stdin oc if !input = [] then process "<stdin>" stdin oc
else begin else begin
let f filename = with_open_in filename @@ fun ic -> process filename ic oc in let f filename = with_open_in filename @@ fun ic -> process filename ic oc in
List.iter f !input List.iter f !input
end end
(** Generating index of posts *)
(** Generate the index of posts *)
let generate_index oc = let generate_index oc =
let f filename = with_open_in filename (read_metadata filename) in let f filename = with_open_in filename (read_metadata filename) in
let mds = List.map f !input in let mds = List.map f !input in
let mds = List.sort (fun md1 md2 -> compare md2.date md1.date) mds in let mds = List.sort (fun md1 md2 -> compare md2.date md1.date) mds in
(* TODO! Fix the paths here!!!!!! *)
let format_item md = let format_item md =
(* path to the html file *)
let fname = md.filename in
let target_fname =
let open Str in
if string_match (regexp {|content/\(.*\)\.md|}) fname 0
then matched_group 1 fname ^ ".html"
else fname
in
String.concat "" [ String.concat "" [
{|<li><a href="/posts/|}; target_fname; {|">|}; {|<li><a href="|}; md.filename; {|">|};
md.title; md.title;
{|</a> - |}; ISO8601.Permissive.string_of_date md.date ; {|</li>|}] {|</a> - |}; ISO8601.Permissive.string_of_date md.date ; {|</li>|}]
in in
@ -139,6 +147,28 @@ let generate_index oc =
let html = format_page ~title:"Recent posts" ~contents in let html = format_page ~title:"Recent posts" ~contents in
output_string oc html output_string oc html
(** Generate the atom feed *)
let generate_atom oc =
let f filename = with_open_in filename (read_post filename) in
let posts = List.map f !input in
let posts = List.sort (fun (md1,_) (md2,_) -> compare md2.date md1.date) posts in
let last_updated =
match posts with
| [] -> 0.
| (md,_)::_ -> md.date
in
let xml = Atom.format_feed
~title:site_title
~subtitle:feed_title
~author
~feed_url
~site_url
~last_updated
~entries:posts
in
output_string oc xml
(** main *) (** main *)
let main () = let main () =
Arg.parse (Arg.align spec) Arg.parse (Arg.align spec)
@ -150,10 +180,14 @@ let main () =
else else
with_open_out !output f with_open_out !output f
in in
with_output @@ begin
with_output @@
if !gen_index if !gen_index
then generate_index then generate_index
else compile_posts else compile_posts
end;
if !gen_atom then with_output generate_atom
let () = let () =
try try