]> git.plutz.net Git - shellwiki/blob - tools.sh
Merge commit '6bc502434737d7f08379e79b94fc6fda424ef779'
[shellwiki] / tools.sh
1 #!/bin/sh
2
3 [ "$include_tools" ] && return 0
4 include_tools="$0"
5
6 # Copyright 2022 - 2024 Paul Hänsch
7
8 # Permission to use, copy, modify, and/or distribute this software for any
9 # purpose with or without fee is hereby granted, provided that the above
10 # copyright notice and this permission notice appear in all copies.
11
12 # THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
15 # SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
18 # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20 . "${_EXEC}/cgilite/storage.sh"
21
22 CACHE_AGE=${CACHE_AGE:-300}
23 export MD_MACROS="$_EXEC/macros"
24 export MD_HTML="${MD_HTML:-false}"
25
26 md(){
27   local parser
28
29   if [ "$#" = 0 ]; then
30     md "${_EXEC}"/parsers/*
31   elif [ "$#" = 1 ]; then
32     "$1"
33   else
34     parser="$1"
35     shift 1
36     "$parser" |md "$@"
37   fi
38 }
39
40 mdfile(){
41   #  Check if page exists, if possible fall
42   #  back to default page from installation
43   local page="$(PATH "$1")"
44   page="${page%/}"
45
46   # Regular processing, keep in sync with tools.sh
47   if   [ -f "$_DATA/pages/$page/:$LANGUAGE/#page.md"  ]; then
48     printf %s\\n "$_DATA/pages/$page/:$LANGUAGE/#page.md"
49   elif [ -f "$_DATA/pages/$page/#page.md" ]; then
50     printf %s\\n "$_DATA/pages/$page/#page.md"
51   elif [ -f "$_EXEC/pages/$page/:$LANGUAGE/#page.md"  ]; then
52     printf %s\\n "$_EXEC/pages/$page/:$LANGUAGE/#page.md"
53   elif [ -f "$_EXEC/pages/$page/#page.md" ]; then
54     printf %s\\n "$_EXEC/pages/$page/#page.md"
55   else
56     return 1
57   fi 2>&-
58   #  ^^ suppress error messages produced
59   #     by printf when stdout was closed
60
61   return 0
62 }
63
64 wiki() {
65   # Print content of a wiki page
66   # Get page from data or underlay dir, handle caching
67   local page="$(PATH "$1")" mdfile cache cachetime
68
69   cache="$_DATA/pages/$page/#page:${LANGUAGE}.${USER_ID}.cache"
70
71   mdfile="$(mdfile "$page")" || return 4
72   acl_read "$page" || return 3
73
74   cachetime="$(stat -c %Y -- "$mdfile" "$cache" 2>/dev/null)"
75
76   if [ "${cachetime#*${BR}}" -gt "${cachetime%${BR}*}" \
77     -a "${cachetime#*${BR}}" -gt "$((_DATE - CACHE_AGE))" ]; then
78     cat "${cache}"
79   else
80     mkdir -p -- "$_DATA/pages/$page/"
81     # Macros expect to find page directory as working dir
82     ( cd -- "$_DATA/pages/$page/";
83       md <"$mdfile" \
84       | tee -- "${cache}.$$"
85     )
86     grep -q '^%nocache' "$mdfile" \
87     && rm -- "${cache}.$$" \
88     || mv -- "${cache}.$$" "${cache}"
89   fi
90 }
91
92 size_human(){
93   local size="$1"
94
95   if [ $size -gt $((1024 * 1024 * 1024)) ]; then
96     size=$((size / 1024 / 1024 / 1024 * 10 + size / 1024 / 1024 % 1024 / 100))
97     printf "%i.%i GB" "$((size / 10))" "$((size % 10))"
98
99   elif [ $size -gt $((1024 * 1024)) ]; then
100     size=$((size / 1024 / 1024 * 10 + size / 1024 % 1024 / 100))
101     printf "%i.%i MB" "$((size / 10))" "$((size % 10))"
102
103   elif [ $size -gt $((1024)) ]; then
104     size=$((size / 1024 * 10 + size % 1024 / 100))
105     printf "%i.%i KB" "$((size / 10))" "$((size % 10))"
106
107   else
108     printf "%i B" "$size"
109   fi
110 }
111
112 attachment_glob(){
113   local pattern="${1%/}" IFS=''
114   local glob page pagedir
115
116   page="${pattern%/*}"
117   [ "$page" = "$pattern" ] && page=.
118   [ ! "$page" ] && page=/
119   pattern="${pattern##*/}"
120   [ ! "$pattern" ] && pattern="*"
121
122   case $page in
123   /*)
124     for glob in "$_DATA/pages/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_DATA/pages"}"; done
125     for glob in "$_EXEC/pages/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_EXEC/pages"}"; done
126     ;;
127   *)
128     for glob in "$_DATA/pages/$PATH_INFO/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_DATA/pages/$PATH_INFO/"}"; done
129     for glob in "$_EXEC/pages/$PATH_INFO/$page/#attachments"/$pattern; do printf '%s\n' "${glob#"$_EXEC/pages/$PATH_INFO/"}"; done
130     ;;
131   esac \
132   | sort -u \
133   | while read -r glob; do
134     [ -e "$glob" ] || continue
135     pagedir="$(page_abs "${glob%%/#attachments/*}/")"
136     [ -d "$_DATA/pages/$pagedir" -o -d "$_EXEC/pages/$pagedir" ] \
137     && printf '%s\n' "${glob%%/#attachments/*}/${glob#*/#attachments/}"
138   done
139 }
140
141 page_glob(){
142   local pattern="${1%/}/" depth="${2:-0}" IFS=''
143   local glob page pagedir
144
145   case $pattern in
146   /*)
147     for glob in "$_DATA/pages"$pattern; do printf '%s\n' "${glob#"$_DATA/pages"}"; done
148     for glob in "$_EXEC/pages"$pattern; do printf '%s\n' "${glob#"$_EXEC/pages"}"; done
149     ;;
150   *)
151     for glob in "$_DATA/pages/$PATH_INFO"/$pattern; do printf '%s\n' "${glob#"$_DATA/pages/$PATH_INFO/"}"; done
152     for glob in "$_EXEC/pages/$PATH_INFO"/$pattern; do printf '%s\n' "${glob#"$_EXEC/pages/$PATH_INFO/"}"; done
153     ;;
154   esac \
155   | sort -u \
156   | while read -r page; do
157     # Not a page directory (just a metadata dir)
158     [ ! "${page%%#*}" -o ! "${page%%*/#*}" ] && continue
159
160     # Omit "system" pages unless explicitly wanted
161     [ ! "${page%%\[*\]/*}" -o ! "${page%%*/\[*\]/*}" ] && [ "$glob_system_pages" != true ] && continue
162
163     # Omit translation pages if translations are enabled
164     [ ! "${page%%:*}" -o ! "${page%%*/:*}" ] && [ "$LANGUAGE_DEFAULT" ] && continue
165
166     pagedir="$(page_abs "$page")"
167
168     if [ -d "$_DATA/pages/$pagedir" -o -d "$_EXEC/pages/$pagedir" ]; then
169       printf '%s\n' "$page"
170       if ! [ "$depth" -eq 0 ]; then
171         PATH_INFO="$pagedir" page_glob "*" "$((depth - 1))" \
172         | while read -r glob; do printf %s%s\\n "$page" "$glob"; done
173       fi
174     fi
175   done
176 }
177
178 page_abs(){
179   case $1 in
180     /*) PATH "${1%/}/";;
181     *)  PATH "${PATH_INFO%/*}/${1%/}/";;
182   esac
183 }
184
185 has_tags() {
186   # true if PAGE is tagges with all TAGS
187   local page="$(page_abs "$1")"; shift 1;
188   local tdir="$_DATA/tags" tag dt df
189
190   for tag in "$@"; do
191     tag="$(printf %s "$tag" |awk '{ sub(/^[#!]/, ""); gsub(/[^[:alnum:]]/, "_"); print toupper($0); }')"
192     dt="$(DBM "${tdir}/${tag}" get "${page}")" || return 1
193     df="$(stat -c %Y "$(mdfile "$page")")" || return 1
194     if [ "$df" -gt "$dt" ]; then
195       DBM "${tdir}/${tag}" remove "${page}"
196       return 1
197     fi
198   done
199   return 0
200 }
201
202 has_tag() {
203   # true if PAGE is tagged with any of TAGS
204   local page="$(page_abs "$1")"; shift 1;
205   local tdir="$_DATA/tags" tag dt df
206
207   for tag in "$@"; do
208     tag="$(printf %s "$tag" |awk '{ sub(/^[#!]/, ""); gsub(/[^[:alnum:]]/, "_"); print toupper($0); }')"
209     dt="$(DBM "${tdir}/${tag}" get "${page}")" || continue
210     df="$(stat -c %Y "$(mdfile "$page")")" || return 1
211     if [ "$df" -gt "$dt" ]; then
212       DBM "${tdir}/${tag}" remove "${page}"
213       continue
214     else
215       return 0
216     fi
217   done
218   return 1
219 }
220
221 page_title() {
222   local mdfile='' PAGE_TITLE=''
223
224   if mdfile="$(mdfile "${1}")"; then
225     PAGE_TITLE="$(
226       # pick title from %title pragma
227       sed -nE '
228         s;^%title[ \t]+([[:graph:]][[:print:]]+)\r?$;\1;p; tQ;
229         b; :Q q;
230       ' "$mdfile"
231     )"
232     [ ! "${PAGE_TITLE}" ] && PAGE_TITLE="$(
233       # pick title from first h1/h2 headline
234       MD_MACROS="" md <"$mdfile" \
235       | sed -nE '
236         s;^.*<h1[^>]*>(.*>)?([^<]+)(<.*)?</h1>.*$;\2;; tQ;
237         s;^.*<h2[^>]*>(.*>)?([^<]+)(<.*)?</h2>.*$;\2;; tQ;
238         b; :Q
239         # reverse escapes of cgilite HTML function,
240         # to prevent later double escaping
241         # later escaping must not be omited
242         s/&lt;/</g; s/&gt;/>/g;  s/&quot;/'\"'/g; s/&#x27;/'\''/g;
243         s/&#x5B;/[/g; s/&#x5D;/]/g;  s/&#x0D;/\r/g; s/&amp;/\&/g;
244         p; q;
245       '
246     )"
247   fi
248   if [ ! "${PAGE_TITLE}" ]; then
249     # use last part of page URL as title
250     PAGE_TITLE="${1%/}"
251     PAGE_TITLE="${PAGE_TITLE##*/}"
252   fi
253   printf %s\\n "$PAGE_TITLE"
254 }