Description
Parmi les flux de données, Hugo est capable de créer plusieurs flux dont RSS (Really Simple Syndication) (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](#documentations tierces) 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 (JavaScript Object Notation)
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
linkqui permet de cibler un fichier de licence - telle une licence CC (Creatives Commons) - 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 (Uniform Resource Locator) 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 JSON 4151, de typetag:identifiant,DATE:alphanumeriqueet oùtag:identifiant,DATEne doit pas changer sur l’ensemble du site, seule la partiealphanumeriquesera 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 (International Organization for Standardization) 8601, à minima l’année, codée sur 4 chiffres, telle que
2019; la préférence peut être donnée à la formeYYYY-MM-DDoù l’année, le mois et le jour sont séparés par un tiret-, telle que2019-10-07qui peut correspondre à la date de création de votre domaine, par exemple. -
où
alphanumeriqueest 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"
-
-
-
Soit en utilisant le schéma de notation
UURIdéfinie par la RFC (Requests for comments) 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èrementsha1conforme à 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,
iconpour l’image de favicon, etlogopour 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_feedsurtrue, elles ne sont pas incluses. -
On capture le nombre de pages,
-
Puis on utilise la fonction Hugo
rangepour 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
Il est possible de spécifier dans le fichier de configuration le format de sortie
RSS
pour 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"
Sachez qu’il est possible de surcharger la génération native du flux
RSS
, en créant un modèle dans _default.
RSS:Template
Il n’est pas nécessaire de créer ce modèle du fait que Hugo génére correctement, mais pour de l’anglais, le fichier
RSS
, nommé 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
tagsur 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é.