Description
Parmi les flux de données, Hugo est capable de créer plusieurs flux dont RSS (actuellement, à la norme 2.0).
Hugo est donc capable de générer un flux RSS , ou de type JSON , mais pas ATOM - pour ce dernier, nous verrons comment faire dans le chapitre ad hoc.
Documentation
Un petit tour sur la documentation officielle Hugo :
- Hugo Documentation : Templates > Rss
- Hugo Documentation : Templates > Output formats
- Hugo Documentation : Variables > Site
- Hugo Documentation : Variables > Hugo
- Hugo Documentation : Functions > Sha
- Hugo Documentation : Functions > Range
Détails Techniques
Commençons par les détails techniques suivants à-propos de la gestion de différents éléments ; chaque format de flux a ses propres spécificités, certaines sont communes ou peuvent se ressembler. Merci de lire les RFC adéquates.
author
Les trois formats de flux gérent un élément author
dans les différentes
entrées générées. Quant à l’auteur du site, là où ATOM et JSON ont leur
élément author
avec leurs spécificités, RSS a son propre élément
managingEditor
, voire webMaster
.
Il faut configurer le bloc author
dans le fichier de configuration,
de telle manière :
[params]
[params.author]
email = "courriel@domaine.tld"
name = "Nom Prénom"
category
Là où ATOM et RSS sont capables de générer un élément catégory
, JSON
utilise l’élément tag
.
Dans tous les cas, j’ai utilisé la taxonomie des tags pour les créer.
Il faut configurer la taxonomie dans le fichier de configuration, de telle manière :
[taxonomies]
tag = "tags"
copyright
Là où RSS a son élément copyright
,
- ATOM peut annoncer par le biais d’un élément
link
qui permet de cibler un fichier de licence - telle une licence CC - ainsi que son élémentrights
. - JSON n’a rien de prévu.
Il faut configurer la variable copyright
dans le fichier de configuration.
description
Là où RSS et JSON ont leur élément description
du flux, ATOM n’en a pas,
mais il est possible d’utiliser l’élément subtitle
.
Concernant l’usage de l’élément description
, j’utilise personnellement
la variable site.Params.description
.
Il faut configurer la variable description
dans le fichier de configuration.
generator
Seul ATOM et RSS sont capables de générer un élément generator
, bien
qu’ATOM le fasse plus finement.
On peut utiliser les variables spécifiques à Hugo.
id
Pour RSS, l’identifiant d’une entrée est l’élément guid
. ATOM et JSON
ont un élément id
. ATOM a aussi son identifiant de site.
Il est important de veiller à ce que cet identifiant soit unique ; il y a plusieurs manières de les générer :
- Soit tout simplement par le biais de l’URL du site, ou de l’article
correspondant à l’entrée :
<id>{{ .Permalink }}</id>
- Soit en utilisant le schéma de notation
tag
, définie par la RFC 4151, de typetag:identifiant,DATE:alphanumerique
et oùtag:identifiant,DATE
ne doit pas changer sur l’ensemble du site, seule la partiealphanumerique
sera l’objet de l’unicité de l’article ou de l’identifiant du site :- où l’identifiant peut être soit une adresse mail, soit le nom de domaine ; ce dernier semble être la préférence.
- où DATE doit correspondre à la norme ISO 8601, à minima l’année,
codée sur 4 chiffres, telle que
2019
; la préférence peut être donnée à la formeYYYY-MM-DD
où l’année, le mois et le jour sont séparés par un tiret-
, telle que2019-10-07
qui peut correspondre à la date de création de votre domaine, par exemple. - où
alphanumerique
est un ensemble de lettres et de chiffres. - Pour exemple :
- Pour l’identifiant du site :
{{ $url := urls.Parse .Permalink }}{{ $id := print "tag:" $url.Host ",2019-10-07:" }}<id>{{ $id }}website</id>
- Pour l’identifiant d’entrée :
<id>{{ $id }}{{ anchorize .RelPermalink }}</id>
- Pour RSS, remplacez la balise
<id>
par<guid>
. De même, si vous utilisez la notation par tag ou par UURI, il vous faudra ajouter l’attributisPermalink
, tel que :isPermaLink="false"
- Pour l’identifiant du site :
- Soit en utilisant le schéma de notation
UURI
définie par la RFC 4122 - qui est humainement incompréhensible ; l’avantage avec Hugo est qu’on peut la générer dynamiquement en utilisant la fonctionsha
, tout particulièrementsha1
conforme à la version 5 de ladite numérotation, telle que :
{{ $uuid := sha1 .Permalink }}<id>urn:uuid:{{substr $uuid 0 8}}-{{substr $uuid 10 4}}-{{substr $uuid 15 4}}-{{substr $uuid 20 4}}-{{substr $uuid 25 12}}</id>
image
Là où RSS gère l’élément image
pour afficher le logo,
- ATOM et JSON gèrent deux éléments différents,
icon
pour l’image de favicon, etlogo
pour votre… logo ! - JSON est capable, en plus, de gérer un avatar pour l’auteur.
Limite
Le code Hugo suivant, que j’utilise dans chacun des modèles, permet de limiter le nombre d’entrées selon :
- La limite du service RSS,
- Si les pages ont le paramètre
disable_feed
surtrue
, elles ne sont pas incluses. - On capture le nombre de pages,
- Puis on utilise la fonction Hugo
range
pour générer dynamiquement le nombre d’entrées - l’usage de la fonction dans le modèle pour JSON différe légérement de celui pour les modèles ATOM et RSS - :
{{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) -}}
{{- $pages := where site.RegularPages ".Params.disable_feed" "!=" true -}}
{{- if ge $limit 1 -}}{{- $pages = $pages | first $limit -}}{{- end -}}
Configuration
- Le fichier de configuration principal :
config.toml
RSS
home
, section
et les deux
taxonomy
, taxonomyTerm
.Personnellement, je renomme le nom du flux RSS en modifiant dans le
fichier de configuration, la variable baseName
à rss
parce que je ne
pense pas que celui-ci doit porter le nom index
.
En effet, le nom rss.xml
est plus parlant !
[outputs]
home = ["HTML", "RSS"]
[outputFormats.RSS]
baseName = "rss"
_default
.RSS:Template
index.xml
.J’ai créé mon propre modèle, tenant compte du fait d’être multilangue.
{{ printf `<?xml version="1.0" encoding="utf-8" standalone="yes" ?>` | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ if eq .Title site.Title }}{{ site.Title }}{{ else }}{{ with .Title }}{{.}}{{ T "on" }}{{ end }}'{{ site.Title }}'{{ end }}</title>
<link>{{ .Permalink }}</link>
{{ with site.Params.description -}}<description>{{ . }}</description>{{- end }}
<docs>http://blogs.law.harvard.edu/tech/rss</docs>
<generator>Hugo {{ Hugo.Version }}</generator>
<image>
<description>Logo</description>
<height>128</height>
<link>{{ .Permalink }}</link>
<title>{{ if eq .Title site.Title }}{{ site.Title }}{{ else }}{{ with .Title }}{{.}}{{ T "on" }}{{ end }}'{{ site.Title }}'{{ end }}</title>
<url>{{ site.BaseURL }}img/Logo.png</url>
<width>128</width>
</image>
{{ with site.LanguageCode }}<language>{{.}}</language>{{end}}
{{ with site.Params.author.email }}<managingEditor>{{.}}{{ with site.Params.author.name }} ({{.}}){{end}}</managingEditor>
<webMaster>{{.}}{{ with site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}
{{ with site.Copyright }}<copyright>© {{ $.Date.Format "2006" | safeHTML }} {{ site.Author.name }}; {{.}}</copyright>{{end}}
{{ if not .Date.IsZero }}<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
{{ with .OutputFormats.Get "RSS" }}{{ printf `<atom:link href=%q rel="self" type=%q />` .Permalink .MediaType | safeHTML }}{{ end }}
{{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) -}}
{{- $pages := where site.Pages ".Params.disable_feed" "!=" true -}}
{{- if ge $limit 1 -}}{{- $pages = $pages | first $limit -}}{{- end -}}
{{- range first $limit $pages }}
<item>
<title>{{ .Title }}</title>
<link>{{ .Permalink }}</link>
{{ with .Summary }}<description>{{ . | html }}</description>{{end}}
{{ with site.Params.author.email }}<author>{{.}}{{ with site.Params.author.name }} ({{.}}){{end}}</author>{{end}}{{ $url := printf "%s" "/tags/" | absLangURL }}
{{ with .Params.tags }}{{ range . }}<category domain="{{ $url }}{{urlize .}}/">{{.}}</category>{{end}}{{end}}
<guid>{{ .Permalink }}</guid>
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
</item>
{{ end }}
</channel>
</rss>
Atom
Nativement Hugo ne génére pas de flux Atom. Il faut tout créer ; ce n’est pas bien difficile !
Il est nécessaire de modifier le fichier de configuration pour :
- créer un nouveau type de média
- créer un nouveau format de sortie
- créer le modèle pour le flux Atom
Atom::MediaType
Hugo >= 0.20
Depuis Hugo 0.20, il faut ajouter :
[mediaTypes]
[mediaTypes."application/atom+xml"]
suffix = "xml"
Là, nous avons donc implémenté un nouveau type de format ayant pour mime
type : application/atom+xml
, et pour nom d’extension : xml
.
Hugo >= 0.44
Depuis Hugo 0.44, pour que cela fonctionne correctement il faut ajouter :
[mediaTypes]
[mediaTypes."application/atom+xml"]
suffixes = ["xml"]
suffix
en suffixes = ['xml']
!Atom::OuputFormat
La déclaration du format de sortie, à ajouter :
[outputFormats.Atom]
baseName = "atom"
isPlainText = false
mediaType = "application/atom+xml"
Puis, il faut ajouter "ATOM"
à votre variable home
, tel que :
[outputs]
home = ["HTML", "ATOM", "RSS"]
Atom::Template
Le modèle peut simplement être créé dans le répertoire layouts/
et se
nommer index.atom.xml
, ou être dans son sous-répertoire _default/
et
se nommer, par exemple : home.atom.xml
.
- Si le site est multilangue, les liens alternatifs vers la version de langue correspondante à l’entrée du site, aux flux atom, RSS, voire JSON sont générés.
{{ printf `<?xml version="1.0" encoding="utf-8" standalone="yes" ?>` | safeHTML }}
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="{{ with site.Language.Lang }}{{.}}{{end}}">
<id>{{ .Permalink }}</id>
<title>{{ if eq .Title site.Title }}{{ site.Title }}{{ else }}{{ with .Title }}{{.}}{{ T "on" }}{{ end }}'{{ site.Title }}'{{ end }}</title>
{{ with site.Params.description -}}<subtitle>{{ . }}</subtitle>{{- end }}
{{ with .OutputFormats.Get "ATOM" }}{{ printf `<link href=%q rel="self" type=%q />` .Permalink .MediaType | safeHTML }}{{end}}
{{ range .AlternativeOutputFormats -}}
<link href="{{ if eq .Name "HTML" }}{{ $.Permalink }}{{ else }}{{ printf `%s%s.%s` $.Permalink (.Name | lower) (index .MediaType.Suffixes 0) }}{{end}}" rel="alternate" {{ printf "type=%q" .MediaType.Type | safeHTMLAttr }} />
{{end}}{{ if hugo.IsMultilingual }}{{ range site.Languages }}{{ if ne .Lang site.Language.Lang }}{{ $lang := .Lang }}
{{ with $.OutputFormats.Get "ATOM" }}{{ printf `<link href=%q hreflang=%q rel="alternate" type=%q />` (print $lang "/" .Name "." (index .MediaType.Suffixes 0) |absURL) $lang .MediaType | safeHTML }}{{end}}
{{ range $.AlternativeOutputFormats -}}
<link href="{{ if eq .Name "HTML" }}{{ printf `%s/` $lang | absURL }}{{ else }}{{ printf `%s/%s.%s` $lang (.Name | lower) (index .MediaType.Suffixes 0) | absURL }}{{end}}" hreflang="{{ $lang }}" rel="alternate" {{ printf "type=%q" .MediaType.Type | safeHTMLAttr }} />
{{end}}{{end}}{{end}}{{end}}{{ with site.Copyright }}{{ $lang := site.Language.Lang }}
<link href="http://creativecommons.org/publicdomain/zero/1.0/legalcode.{{ $lang }}" hreflang="{{ $lang }}" rel="license" />
<link href="http://creativecommons.org/publicdomain/zero/1.0/deed.{{ $lang }}" hreflang="{{ $lang }}" rel="license" />
<rights>© {{ $.Date.Format "2006" | safeHTML }} {{ site.Params.author.name }}</rights>{{end}}
<icon>/img/favicon.ico</icon>
{{ with site.Params.logo }}<logo>{{.}}</logo>{{end}}
{{ if not .Date.IsZero }}<updated>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>{{ end }}
{{ with site.Params.author.name }}
<author>
<name>{{.}}</name>
{{ with site.Params.author.email }}<email>{{.}}</email>{{end}}
<uri>{{ $.Permalink }}</uri>
</author>{{end}}
<generator uri="https://gohugo.io" version="{{ Hugo.Version }}">Hugo</generator>
{{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) -}}
{{- $pages := where site.RegularPages ".Params.disable_feed" "!=" true -}}
{{- if ge $limit 1 -}}{{- $pages = $pages | first $limit -}}{{- end -}}
{{- range first $limit $pages }}
<entry>
<id>{{ .Permalink }}</id>
<link href="{{ .Permalink }}" rel="alternate" type="text/html" />
<title>{{ .Title }}</title>{{ with site.Params.author }}
<author>
<name>{{.}}</name>
</author>{{end}}{{ $url := printf "%s" "/tags/" | absLangURL }}{{ with .Params.tags }}{{ range . }}
<category term="{{.}}" scheme="{{ print $url (urlize .) | absLangURL }}/" />{{end}}
{{end}}
{{ with .Content }}<content type="html">{{ `<![CDATA[` | safeHTML }}{{.}}]]></content>{{end}}
{{ with .Summary }}<summary type="html">{{ `<![CDATA[` | safeHTML }}{{.}}]]></summary>{{end}}
<published>{{ .Date.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</published>
{{ if gt .Lastmod .Date }}<updated>{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" | safeHTML }}</updated>{{end}}
</entry>
{{ end }}
</feed>
JSON
Pour générer le flux JSON, nous nous conformerons à la norme JSON Feed v1.
JSON::OutputFormat
La déclaration de sortie de format à ajouter :
[outputFormats.JSON]
mediaType = "application/json"
baseName = "feed"
suffix = "json"
IsHTML = false
IsPlainText = true
noUgly = false
rel = "alternate"
Puis, il faut ajouter la déclaration JSON
à votre variable home
, tel que :
[outputs]
`home = ["HTML", "ATOM", "JSON", "RSS"]
JSON::Template
JSON: Détails
En plus de la limite
mentionnée plus haut, nous
récupèrons le nombre de page, pour boucler correctement car le dernier
item
ne doit pas être suivi du symbole ‘,’ :
{{- $length := (len $pages) -}}
Et en fin de boucle range
, nous ajoutons :
{{ if ne (add $index 1) $length }},{{ end }}
Ainsi tant qu’il y a un élément suivant, il est précédé du symbole ‘,’ jusqu’au dernier.
{{- $limit := (cond (le site.Config.Services.RSS.Limit 0) 65536 site.Config.Services.RSS.Limit) -}}
{{- $pages := where site.RegularPages ".Params.disable_feed" "!=" true -}}
{{- if ge $limit 1 -}}{{- $pages = $pages | first $limit -}}{{- end -}}
{{- $length := (len $pages) -}}
{
"version": "https://jsonfeed.org/version/1",
"title": "{{ site.Title }}",
"home_page_url": "{{ site.BaseURL }}",
{{ with .OutputFormats.Get "JSON" -}}"feed_url": "{{.Permalink}}",{{- end }}
{{ with site.Params.description -}}"description": "{{ . }}",{{- end }}
{{ with site.Params.author.name }}
"author": {
"avatar": "{{ with site.Params.logo }}{{ . }}{{ end}}",
"name": "{{ . }}",
"url": "http://huc.fr.eu.org"
},
{{- end }}
{{ with site.Params.logo }}"icon": "{{ . }}",{{- end }}
"favicon": "/img/favicon.ico",
"items": [ {{- range $index, $elements := $pages -}}
{
"id": "{{ .Permalink }}",
"url": "{{ .Permalink }}",
"title": "{{ .Title | plainify }}",
{{ with site.Params.Author }}"author": { "name": "{{ . }}" }{{ end }}{{ with .Content }},
"content_text": {{ . | plainify | jsonify }},
"content_html": {{ . | safeHTML | jsonify }}{{ end }}{{ with .Summary }},
"summary": {{ . | plainify | jsonify }}{{ end }}{{ with .Params.tags }},
"tags": [{{ range $tindex, $tag := . }}{{ if $tindex }}, {{ end }}"{{ $tag| htmlEscape }}"{{ end }}]{{ end }}{{ if .PublishDate }},
"date_published": "{{ .PublishDate.Format "2006-01-02T15:04:05-01:00" | safeHTML }}"{{ end }}{{ if gt .Lastmod .Date }},
"date_modified": "{{ .Lastmod.Format "2006-01-02T15:04:05-01:00" | safeHTML }}"{{ end }}
}{{ if ne (add $index 1) $length }},{{ end }}{{- end }}
]
}
Documentations tierces
- RFC 3339 : https://tools.ietf.org/html/rfc3339
- RFC 4122 : https://tools.ietf.org/html/rfc4122
- RFC 4151 : https://tools.ietf.org/html/rfc4151
- RFC 4287 : https://tools.ietf.org/html/rfc4287
- RFC 5988 : https://tools.ietf.org/html/rfc5988
- RFC 8288 : https://tools.ietf.org/html/rfc8288
- Norme ISO 8601 : https://www.w3.org/TR/1998/NOTE-datetime-19980827
- L’article d’OpenWeb : Comment construire un flux Atom ?
- Plus d’informations sur la norme d’identification
tag
sur le site Tag URI - Plus d’informations sur les UUID : Universal_Unique_Identifier WP
- La spécification JSON Feed 1 : https://jsonfeed.org/version/1
- La spécification RSS 2.0 : https://cyber.harvard.edu/rss/index.html
- Le sujet Atom and JSON feeds sur le forum de la communauté Hugo - qui m’a bien aidé.