# #install_printer Yocaml.Path.pp ;;
# #install_printer Yocaml.Deps.pp ;;
# open Yocaml;;
let www = Path.rel [ "_www" ]
let assets = Path.rel [ "assets" ]
let content = Path.rel [ "content" ]
let templates = Path.(assets / "templates")
let with_ext exts file =
List.exists (fun ext -> Path.has_extension ext file) exts
let track_binary =
Sys.executable_name |> Yocaml.Path.from_string |> Pipeline.track_file
let is_markdown = with_ext [ "md"; "markdown"; "mdown" ]
let articles = Path.(content / "articles")
let compute_link source =
let into = Path.abs [ "articles" ] in
source |> Path.move ~into |> Path.change_extension "html"
let fetch_articles =
Archetype.Articles.fetch ~where:is_markdown ~compute_link
(module Yocaml_yaml)
articles
In this section, we’ll add a final touch to our blog generator: an ATOM feed. Although YOCaml (through plugins) can handle RSS1, RSS2 feeds and even OPML feeds, for this tutorial we’ve chosen to use ATOM — without any particular preference.
Adding the Plugin
The library for describing syndication feeds is not part of YOCaml’s core. However, the Yocaml_syndication plugin provides all the primitives needed to build feeds.
First, we’ll update our dune-project
once again, in the
package/blog
section, to include the plugin:
(package
(name blog)
(synopsis "My first blog using YOCaml")
(description
"My first personal blog using YOCaml for
fun and profit")
(depends
(ocaml (>= 5.3.0))
(yocaml (>= 2.5.0))
(yocaml_unix (>= 2.5.0))
(yocaml_yaml (>= 2.5.0))
(yocaml_markdown (>= 2.5.0))
(yocaml_jingoo (>= 2.5.0))
+ (yocaml_syndication (>= 2.5.0))
(utop :with-dev-setup)
(ocamlformat :with-dev-setup)
(ocp-indent :with-dev-setup)
(merlin :with-dev-setup)
(ocaml-lsp-server :with-dev-setup)))
In addition, to make them accessible from our blog code (blog.ml
),
we need to add the dependencies in our bin/dune
file:
(executable
(public_name blog)
(libraries yocaml yocaml_unix))
(libraries yocaml yocaml_unix yocaml_yaml
- yocaml_markdown yocaml_jingoo))
+ yocaml_markdown yocaml_jingoo
+ yocaml_syndication))
Do not forget to run dune build
to regenerate the OPAM file and then
run opam install . --deps-only
to download the newly added
dependencies.
Creating the Feed
Creating the feed is fairly straightforward, and the Yocaml_syndication.Atom module is thoroughly documented.
Building a feed revolves around two main functions (which themselves rely on other helpers). The first one lets us create an entry in the feed:
# Yocaml_syndication.Atom.entry ;;
- : ?authors:Yocaml_syndication.Person.t list ->
?contributors:Yocaml_syndication.Person.t list ->
?links:Yocaml_syndication.Atom.link list ->
?categories:Yocaml_syndication.Category.t list ->
?published:Yocaml_syndication.Datetime.t ->
?rights:Yocaml_syndication.Atom.text_construct ->
?source:Yocaml_syndication.Atom.source ->
?summary:Yocaml_syndication.Atom.text_construct ->
?content:Yocaml_syndication.Atom.content ->
title:Yocaml_syndication.Atom.text_construct ->
id:string ->
updated:Yocaml_syndication.Datetime.t ->
unit -> Yocaml_syndication.Atom.entry
= <fun>
The second one builds a feed from a list of entries:
# Yocaml_syndication.Atom.feed ;;
- : ?encoding:string ->
?standalone:bool ->
?subtitle:Yocaml_syndication.Atom.text_construct ->
?contributors:Yocaml_syndication.Person.t list ->
?categories:Yocaml_syndication.Category.t list ->
?generator:Yocaml_syndication.Generator.t option ->
?icon:string ->
?logo:string ->
?links:Yocaml_syndication.Atom.link list ->
?rights:Yocaml_syndication.Atom.text_construct ->
updated:Yocaml_syndication.Atom.updated_strategy ->
title:Yocaml_syndication.Atom.text_construct ->
authors:Yocaml_syndication.Person.t Nel.t ->
id:string ->
('a -> Yocaml_syndication.Atom.entry) ->
'a list -> Yocaml_syndication.Xml.t
= <fun>
Both functions accept a lot of arguments — however, many of them are optional. We'll proceed step by step to build the feed.
Feed Configuration
First, we’ll create a Feed
module whose purpose is to configure our
ATOM feed. To do this, we can add a Feed
module in our blog.ml
file:
module Feed = struct
let path = "atom.xml"
let title = "My first blog using YOCaml"
let site_url = "https://my_github_name.github.io"
let feed_description = "My personnal blog using YOCaml"
let owner =
Yocaml_syndication.Person.make
~uri:site_url ~email:"me@gmail.com"
"John Doe"
let authors = Nel.singleton owner
end
The only subtlety in this module is the owner
variable. The
Nel
module (short for Non-Empty List) is used to describe the list of
authors, which cannot be empty. With these variables in place, we’re
ready to move on to the next step.
Converting an Article
Now that our feed is configured, we’ll convert the path
(the
article’s URL) and article
pair into an ATOM entry by adding a
function to our module: article_to_entry
. This function will extract
information from an article and use it to build an ATOM entry.
let article_to_entry (url, article) =
let open Yocaml.Archetype in
let open Yocaml_syndication in
let page = Article.page article in
let title = Article.title article
and content_url =
site_url ^ Path.to_string url
and updated =
Datetime.make (Article.date article)
and categories =
List.map Category.make (Page.tags page)
and summary =
Option.map Atom.text (Page.description page)
in
let links =
[ Atom.alternate content_url ~title ]
in
Atom.entry
~links
~categories
?summary
~updated
~id:content_url
~title:(Atom.text title) ()
Now that we can convert articles into ATOM entries, it’s time to build the entire feed.
Creating the Feed
We can now add a make
function to the Feed
module, whose role is
to create a minimalist feed (using relatively few optional
parameters):
let make entries =
let open Yocaml_syndication in
Atom.feed ~title:(Atom.text title)
~subtitle:(Atom.text feed_description)
~updated:(Atom.updated_from_entries ())
~authors ~id:site_url article_to_entry entries
This use of Atom.feed
is very minimal (it will produce a valid
feed), even though it would
be possible to be more precise by addressing some of the warnings.
module Feed = struct
let path = "atom.xml"
let title = "My first blog using YOCaml"
let site_url = "https://my_github_name.github.io"
let feed_description = "My personnal blog using YOCaml"
let owner =
Yocaml_syndication.Person.make ~uri:site_url ~email:"me@gmail.com"
"John Doe"
let authors = Nel.singleton owner
let article_to_entry (url, article) =
let open Yocaml.Archetype in
let open Yocaml_syndication in
let page = Article.page article in
let title = Article.title article
and content_url = site_url ^ Path.to_string url
and updated = Datetime.make (Article.date article)
and categories = List.map Category.make (Page.tags page)
and summary = Option.map Atom.text (Page.description page) in
let links = [ Atom.alternate content_url ~title ] in
Atom.entry ~links ~categories ?summary ~updated ~id:content_url
~title:(Atom.text title) ()
let make entries =
let open Yocaml_syndication in
Atom.feed ~title:(Atom.text title)
~subtitle:(Atom.text feed_description)
~updated:(Atom.updated_from_entries ())
~authors ~id:site_url article_to_entry entries
end
Writing the File
Now that we can create a feed and have learned in the previous section how to retrieve all articles, we can write the feed to our target source:
let create_feed =
let feed_path = Path.(www / Feed.path)
and pipeline =
let open Task in
let+ () = track_binary
and+ articles = fetch_articles in
articles
|> Feed.make
|> Yocaml_syndication.Xml.to_string
in
Action.Static.write_file feed_path pipeline
We won’t go into this action in detail because it is generally very
similar (and simpler) than the actions we’ve created throughout this
tutorial. It uses the fetch_articles
function we created earlier. We
can now add create_feed
to our main program:
let program () =
let open Eff in
let cache = Path.(www / ".cache") in
Action.restore_cache cache
>>= copy_image
>>= create_css
>>= create_pages
>>= create_articles
>>= create_index
+ >>= create_feed
>>= Action.store_cache cache
And, as usual, you can test your generator with dune exec bin/blog.exe server
and pat yourself on the back for having a fully
functional static site generator!
Conclusion
Creating a syndication feed simply involves using the functions
provided by the
Yocaml_syndication
plugin to create a feed, and then converting this feed (which is
an XML document) into a string
.
To go further, we recommend exploring the module (and potentially the specification) to include even more data in your feed.