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 linkqui 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: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 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"
 
- Pour l’identifiant du site :
 
- Soit en utilisant le schéma de notation UURIdé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è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
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 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é.