--- /dev/null
+#!/bin/sh
+#
+# fesh - An Atom feed generator for Gemini capsules written in POSIX sh.
+#
+# Copyright (C) 2021-2022 - Ricardo García Jiménez <ricardogj08@riseup.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this software except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Muestra mensajes de errores.
+#
+errors() {
+ printf '%s.\n' "$1" >&2
+ exit 1
+}
+
+#
+# Muestra un mensaje de ayuda.
+#
+shelp() { printf '%s' "\
+fesh 2.2 - An Atom feed generator for Gemini capsules written in POSIX sh.
+
+Synopsis:
+ fesh [OPTIONS]
+
+Options:
+ -a <NAME> - Author name [default: capsule].
+ -c <STRING> - Capsule name [default: fesh].
+ -d <DOMAIN> - Capsule domain name [default: localhost].
+ -l <LANG> - Capsule content language [default: es].
+ -n <NUMBER> - Number of entries [default: 15].
+ -o <PATH> - Output directory [default: .].
+ -r <PATH> - Capsule root directory [default: .].
+
+Example:
+ fesh -d myblog.com -c 'My blog'
+"
+exit 0
+}
+
+#
+# Genera el encabezado del feed de Atom.
+#
+header() { cat << EOF
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="${FESH_LANG:-es}">
+<id>${FESH_DOMAIN}/atom.xml</id>
+<title>${FESH_CAPSULE}</title>
+<updated>$(LC_ALL=C date "$FDATE")</updated>
+<author>
+ <name>${FESH_AUTHOR}</name>
+</author>
+<link rel="self" href="${FESH_DOMAIN}/atom.xml" type="application/atom+xml"/>
+<link rel="alternate" href="${FESH_DOMAIN}" type="text/gemini"/>
+<generator uri="https://github.com/ricardogj08/fesh" version="2.2">fesh</generator>
+EOF
+}
+
+#
+# Elimina espacios sobrantes de un string.
+# shellcheck disable=SC2048,SC2086
+#
+trim_all() {
+ set -f
+
+ set -- $*
+
+ printf '%s' "$*"
+
+ set +f
+}
+
+#
+# Genera una entrada para el feed de Atom.
+#
+entry() { cat << EOF
+<entry>
+ <id>${URL}</id>
+ <title>${TITLE}</title>
+ <updated>${DATE}</updated>
+ <author>
+ <name>${FESH_AUTHOR}</name>
+ </author>
+ <link rel="alternate" href="${URL}" type="text/gemini"/>
+</entry>
+EOF
+}
+
+#
+# Contruye un feed de atom.
+#
+render() {
+ FDATE=+%Y-%m-%dT%H:%M:%SZ
+
+ header
+
+ i=0
+
+ #
+ # Lista todos los archivos *.gmi o *.gemini
+ # ordenados por fecha de modificación.
+ #
+ find . -type f \( -name \*.gmi -o -name \*.gemini \) \
+ -a ! \( -name index.gmi -o -name index.gemini \) \
+ -exec ls -t {} + 2>/dev/null |
+
+ while read -r file; do
+ #
+ # URL Gemini del archivo.
+ #
+ URL="$FESH_DOMAIN/${file#./}"
+
+ #
+ # Título principal del archivo.
+ #
+ TITLE=$(grep -se '^#[[:space:]]' "$file" | head -n 1)
+ TITLE=${TITLE#\#[[:space:]]}
+ TITLE=$(trim_all "$TITLE")
+
+ #
+ # Fecha de modificación.
+ #
+ DATE=$(LC_ALL=C date -r "$file" "$FDATE" 2>/dev/null ||
+ LC_ALL=C date "$FDATE")
+
+ entry
+
+ i=$((i + 1))
+
+ [ "$i" -eq "$FESH_NUMBER" ] && break
+ done
+
+ printf '</feed>\n'
+}
+
+main() {
+ while getopts :a:c:d:l:n:o:r: opt; do
+ case $opt in
+ a) FESH_AUTHOR="$OPTARG";;
+ c) FESH_CAPSULE="$OPTARG";;
+ #d) FESH_DOMAIN=gemini://${OPTARG}:1965;;
+ d) FESH_DOMAIN=gemini://${OPTARG};; # Sin :1965
+ l) FESH_LANG="$OPTARG";;
+ n) FESH_NUMBER="$OPTARG";;
+ o) FESH_OUTPUT="$OPTARG";;
+ r) FESH_ROOT="$OPTARG";;
+ \?) shelp;;
+ :) errors "Option -$OPTARG requires an argument"
+ esac
+ done
+ #
+ # Decrementa el puntero del argumento $n
+ # para que apunte al siguente argumento.
+ #
+ shift $((OPTIND - 1))
+
+ #
+ # Configuración por defecto.
+ #
+ : "${FESH_CAPSULE=fesh}"
+ : "${FESH_AUTHOR:=$FESH_CAPSULE}"
+ #: "${FESH_DOMAIN:=gemini://localhost:1965}"
+ : "${FESH_DOMAIN:=gemini://localhost}" # sin :1965
+ : "${FESH_NUMBER:=15}"
+ : "${FESH_OUTPUT:=.}"
+
+ :> "$FESH_OUTPUT/atom.xml" ||
+ errors "Couldn't create Atom feed file"
+
+ cd "${FESH_ROOT:-.}" ||
+ errors "Couldn't access capsule root directory"
+
+ render > "$FESH_OUTPUT/atom.xml"
+}
+
+main "$@"