generate an atom feed
This commit is contained in:
		
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							| @@ -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
									
								
							
							
						
						
									
										44
									
								
								src/atom.ml
									
									
									
									
									
										Normal 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>" | ||||||
							
								
								
									
										3
									
								
								src/dune
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								src/dune
									
									
									
									
									
								
							| @@ -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
									
								
							
							
						
						
									
										5
									
								
								src/post.mli
									
									
									
									
									
										Normal 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 | ||||||
							
								
								
									
										76
									
								
								src/webcc.ml
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								src/webcc.ml
									
									
									
									
									
								
							| @@ -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 *) | ||||||
|  |  | ||||||
| @@ -97,16 +114,16 @@ 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 | ||||||
| @@ -115,23 +132,14 @@ let compile_posts oc = | |||||||
|   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 | ||||||
|  |   begin | ||||||
|     with_output @@ |     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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user