copyright
[fesh.git/.git] / fesh.sh
1 #!/bin/sh
2 #
3 # fesh - An Atom feed generator for Gemini capsules written in POSIX sh.
4 #
5 # Copyright (C) 2021-2022 - Ricardo García Jiménez <ricardogj08@riseup.net>
6 # Copyright (C) 2022 - Fabián Bonetti <fabianbonetti@vk.com>
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License");
9 # you may not use this software except in compliance with the License.
10 # You may obtain a copy of the License at:
11 #
12 #     http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS,
16 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 # See the License for the specific language governing permissions and
18 # limitations under the License.
19 #
20
21 #
22 # Muestra mensajes de errores.
23 #
24 errors() {
25   printf '%s.\n' "$1" >&2
26   exit 1
27 }
28
29 #
30 # Muestra un mensaje de ayuda.
31 #
32 shelp() { printf '%s' "\
33 fesh 2.2 - An Atom feed generator for Gemini capsules written in POSIX sh.
34
35 Synopsis:
36   fesh [OPTIONS]
37
38 Options:
39   -a <NAME>   - Author name [default: capsule].
40   -c <STRING> - Capsule name [default: fesh].
41   -d <DOMAIN> - Capsule domain name [default: localhost].
42   -l <LANG>   - Capsule content language [default: es].
43   -n <NUMBER> - Number of entries [default: 15].
44   -o <PATH>   - Output directory [default: .].
45   -r <PATH>   - Capsule root directory [default: .].
46
47 Example:
48   fesh -d myblog.com -c 'My blog'
49 "
50 exit 0
51 }
52
53 #
54 # Genera el encabezado del feed de Atom.
55 #
56 header() { cat << EOF
57 <?xml version="1.0" encoding="utf-8"?>
58 <feed xmlns="http://www.w3.org/2005/Atom" xml:lang="${FESH_LANG:-es}">
59 <id>${FESH_DOMAIN}/atom.xml</id>
60 <title>${FESH_CAPSULE}</title>
61 <updated>$(LC_ALL=C date "$FDATE")</updated>
62 <author>
63   <name>${FESH_AUTHOR}</name>
64 </author>
65 <link rel="self" href="${FESH_DOMAIN}/atom.xml" type="application/atom+xml"/>
66 <link rel="alternate" href="${FESH_DOMAIN}" type="text/gemini"/>
67 <generator uri="https://github.com/ricardogj08/fesh" version="2.2">fesh</generator>
68 EOF
69 }
70
71 #
72 # Elimina espacios sobrantes de un string.
73 # shellcheck disable=SC2048,SC2086
74 #
75 trim_all() {
76   set -f
77
78   set -- $*
79
80   printf '%s' "$*"
81
82   set +f
83 }
84
85 #
86 # Genera una entrada para el feed de Atom.
87 #
88 entry() { cat << EOF
89 <entry>
90   <id>${URL}</id>
91   <title>${TITLE}</title>
92   <updated>${DATE}</updated>
93   <author>
94     <name>${FESH_AUTHOR}</name>
95   </author>
96   <link rel="alternate" href="${URL}" type="text/gemini"/>
97 </entry>
98 EOF
99 }
100
101 #
102 # Contruye un feed de atom.
103 #
104 render() {
105   FDATE=+%Y-%m-%dT%H:%M:%SZ
106
107   header
108
109   i=0
110
111   #
112   # Lista todos los archivos *.gmi o *.gemini
113   # ordenados por fecha de modificación.
114   #
115   find . -type f \( -name \*.gmi -o -name \*.gemini \) \
116     -a ! \( -name index.gmi -o -name index.gemini \) \
117     -exec ls -t {} + 2>/dev/null |
118
119   while read -r file; do
120     #
121     # URL Gemini del archivo.
122     #
123     URL="$FESH_DOMAIN/${file#./}"
124
125     #
126     # Título principal del archivo.
127     #
128     TITLE=$(grep -se '^#[[:space:]]' "$file" | head -n 1)
129     TITLE=${TITLE#\#[[:space:]]}
130     TITLE=$(trim_all "$TITLE")
131
132     #
133     # Fecha de modificación.
134     #
135     DATE=$(LC_ALL=C date -r "$file" "$FDATE" 2>/dev/null ||
136       LC_ALL=C date "$FDATE")
137
138     entry
139
140     i=$((i + 1))
141
142     [ "$i" -eq "$FESH_NUMBER" ] && break
143   done
144
145   printf '</feed>\n'
146 }
147
148 main() {
149   while getopts :a:c:d:l:n:o:r: opt; do
150     case $opt in
151       a) FESH_AUTHOR="$OPTARG";;
152       c) FESH_CAPSULE="$OPTARG";;
153       #d) FESH_DOMAIN=gemini://${OPTARG}:1965;;
154       d) FESH_DOMAIN=gemini://${OPTARG};; # Sin :1965 por Fabián
155       l) FESH_LANG="$OPTARG";;
156       n) FESH_NUMBER="$OPTARG";;
157       o) FESH_OUTPUT="$OPTARG";;
158       r) FESH_ROOT="$OPTARG";;
159       \?) shelp;;
160       :) errors "Option -$OPTARG requires an argument"
161     esac
162   done
163   #
164   # Decrementa el puntero del argumento $n
165   # para que apunte al siguente argumento.
166   #
167   shift $((OPTIND - 1))
168
169   #
170   # Configuración por defecto.
171   #
172   : "${FESH_CAPSULE=fesh}"
173   : "${FESH_AUTHOR:=$FESH_CAPSULE}"
174   #: "${FESH_DOMAIN:=gemini://localhost:1965}" 
175   : "${FESH_DOMAIN:=gemini://localhost}" # sin :1965 por Fabián
176   : "${FESH_NUMBER:=15}"
177   : "${FESH_OUTPUT:=.}"
178
179   :> "$FESH_OUTPUT/atom.xml" ||
180     errors "Couldn't create Atom feed file"
181
182   cd "${FESH_ROOT:-.}" ||
183     errors "Couldn't access capsule root directory"
184
185   render > "$FESH_OUTPUT/atom.xml"
186 }
187
188 main "$@"