subroutines 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. #
  2. # subroutines
  3. #
  4. # Common subroutines for the Dragora GNU/Linux-Libre website scripts
  5. #
  6. #
  7. # Copyright (C) 2021, 2022 Michael Siegel
  8. #
  9. # Licensed under the Apache License, Version 2.0 (the "License");
  10. # you may not use this file except in compliance with the License.
  11. # You may obtain a copy of the License at
  12. #
  13. # http://www.apache.org/licenses/LICENSE-2.0
  14. #
  15. # Unless required by applicable law or agreed to in writing, software
  16. # distributed under the License is distributed on an "AS IS" BASIS,
  17. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. # See the License for the specific language governing permissions and
  19. # limitations under the License.
  20. ## Error handling
  21. _abort() {
  22. ## Run _cleanup if "$TMP_DIR" exists and exit the script with a return code
  23. ## indicating an error
  24. _cleanup
  25. exit 1
  26. }
  27. _cleanup() {
  28. ## Remove any temporary files
  29. [[ -d "$TMP_DIR" ]] && rm -rf -- "${TMP_DIR:?}"/*
  30. }
  31. _perr() {
  32. ## Print a message to stderr
  33. printf '%s\n' "$PROGNAME: $1" >&2
  34. # Don't print PROGNAME for error messages in interactive mode
  35. }
  36. ## Environment checks
  37. _command_available() {
  38. ## Tell whether the given external commands are available
  39. local cmd=
  40. local cmd_path=
  41. local err=0
  42. while [[ "$#" -gt 0 ]]
  43. do
  44. cmd="$1"
  45. cmd_path="$(command -v -- "$cmd")"
  46. if [[ -z "$cmd_path" ]]
  47. then
  48. _perr "command not found -- '$cmd'"
  49. err=1
  50. elif [[ ! -x "$cmd_path" ]]
  51. then
  52. _perr "command not executable -- '$cmd_path'"
  53. err=1
  54. fi
  55. shift
  56. done
  57. [[ "$err" -eq 1 ]] && return 1
  58. return 0
  59. }
  60. _command_is_gnu() {
  61. ## Tell whether the given external commands are from GNU
  62. local cmd=
  63. local err=0
  64. local gnu_version=
  65. while [[ "$#" -gt 0 ]]
  66. do
  67. cmd="$1"
  68. case "$cmd" in
  69. find)
  70. gnu_version='^find (GNU findutils)'
  71. ;;
  72. sed)
  73. gnu_version='^sed (GNU sed)'
  74. ;;
  75. stat)
  76. gnu_version='^stat (GNU coreutils)'
  77. ;;
  78. esac
  79. "$cmd" --version | sed 1q | grep -q -- "$gnu_version" || \
  80. { _perr "GNU version of command required -- '$cmd'"; err=1; }
  81. shift
  82. done
  83. [[ "$err" -gt 0 ]] && return 1
  84. return 0
  85. }
  86. _probe_dirs_files() {
  87. ## Probe for existence of needed directories and files
  88. # Printing error messages right away and only returning at the end because we
  89. # want to catch all missing files and directories at once.
  90. local f= # init later?
  91. local err=0
  92. # Probe for top-level source directory
  93. if [[ ! -d "$SOURCE_DIR" ]]
  94. then
  95. _perr "directory not found -- '$SOURCE_DIR'"
  96. err=1
  97. else
  98. # Probe for sub-directories and files
  99. local d=
  100. for d in "$COMMON_DIR" "$PAGES_DIR"
  101. do
  102. if [[ ! -d "$d" ]]
  103. then
  104. _perr "directory not found -- '$d'"
  105. err=1
  106. continue
  107. fi
  108. case "$d" in
  109. "$COMMON_DIR")
  110. local sd=
  111. for sd in "$d/$CSS_DIR" "$d/$IMG_DIR"
  112. do
  113. if [[ ! -d "$sd" ]]
  114. then
  115. _perr "directory not found -- '$sd'"
  116. err=1
  117. continue
  118. fi
  119. case "$sd" in
  120. "$d/$CSS_DIR")
  121. f="$d/$CSS_DIR/$STYLESHEET"
  122. [[ -f "$f" ]] || { _perr "stylesheet not found -- '$f'"; err=1; }
  123. ;;
  124. "$d/$IMG_DIR")
  125. f="$d/$IMG_DIR/$FAVICON"
  126. [[ -f "$f" ]] || { _perr "favicon not found -- '$f'"; err=1; }
  127. f="$d/$IMG_DIR/$LOGO"
  128. [[ -f "$f" ]] || { _perr "logo not found -- '$f'"; err=1; }
  129. ;;
  130. esac
  131. done
  132. unset sd
  133. if [[ ! -f "$NAVTREE_FILE" ]]
  134. then
  135. _perr "navtree not found -- '$NAVTREE_FILE'"
  136. err=1
  137. fi
  138. ;;
  139. "$PAGES_DIR")
  140. [[ -z "$(find -- "$PAGES_DIR" -mindepth 1 -maxdepth 1)" ]] && \
  141. { _perr "directory must not be empty -- '$PAGES_DIR'"; err=1; }
  142. if [[ ! -d "$PAGES_DIR_MASTER" ]]
  143. then
  144. _perr "master page directory not found -- '$PAGES_DIR_MASTER'"
  145. err=1
  146. else
  147. # Check whether master tree is basically sane
  148. for f in "$HEADER_PARAMS" "$FOOTER_PARAMS" "$HOME_PAGE"
  149. do
  150. f="$PAGES_DIR_MASTER/$f"
  151. [[ -f "$f" ]] || { _perr "file not found -- '$f'"; err=1; }
  152. # Improve error message.
  153. done
  154. # Determine if subtrees of all page directories are identical to
  155. # master subtree.
  156. mkdir -p -- "$TMP_DIR" || err=1
  157. find -- "$PAGES_DIR_MASTER" -printf '%P\n' | sort \
  158. > "$TMP_DIR"/treemaster
  159. local pd=
  160. for pd in $(_get_lang_dirs)
  161. do
  162. find -- "$PAGES_DIR/$pd" -printf '%P\n' | sort \
  163. > "$TMP_DIR"/treecopy
  164. cmp -s -- "$TMP_DIR"/treemaster "$TMP_DIR"/treecopy || { _perr \
  165. "'$PAGES_DIR/$pd': subtree not identical to '$PAGES_DIR_MASTER'";
  166. err=1; }
  167. # Improve error message
  168. done
  169. unset pd
  170. fi
  171. ;;
  172. esac
  173. done
  174. fi
  175. mkdir -p -- "$OUTPUT_DIR" || err=1
  176. [[ "$err" -gt 0 ]] && return 1
  177. return 0
  178. }
  179. _env_checks() {
  180. ## Perform environment checks
  181. _command_available $TOOLS $GNU_TOOLS || return 1
  182. _command_is_gnu $GNU_TOOLS || return 1
  183. _probe_dirs_files || return 1
  184. }
  185. ## Retrieval
  186. _get_lang_dirs() {
  187. ## Find all language-specific page directories
  188. find -- "$PAGES_DIR" -maxdepth 1 -type d -not -name '.*' -name '??' | \
  189. sed 's/^.*\///'
  190. # `-not` is not POSIX, but widely supported.
  191. }
  192. _get_navtree() {
  193. ## Read all site navigation items into a global immutable array
  194. navtree=()
  195. local line=
  196. while read -r line
  197. do
  198. [[ "$line" =~ (^#)|(^[[:space:]]*$) ]] || navtree+=("$line")
  199. done < "$NAVTREE_FILE"
  200. unset line
  201. declare -r navtree
  202. }
  203. _get_pagetree() {
  204. ## Read page tree into a global immutable array
  205. pagetree=()
  206. local page_dir=
  207. while read -r page_dir
  208. do
  209. pagetree+=("${page_dir}/")
  210. # Append a slash so that the amount of slashes immediatley indicates the
  211. # sublevel of a page directory.
  212. # ($PAGES_DIR_MASTER).
  213. done < <(find -- "$PAGES_DIR_MASTER" -type d | sort | \
  214. sed -e "s,${PAGES_DIR_MASTER}/*,," -e '/^doc\/handbook/ d')
  215. unset page_dir
  216. declare -r pagetree
  217. }
  218. _get_sublevel() {
  219. local slashes="${1//[^\/]}"
  220. printf '%s' "${#slashes}"
  221. }
  222. _get_tree_flow() {
  223. ## [Add description]
  224. tree_flow=()
  225. local item=
  226. for item in "$@"
  227. do
  228. tree_flow+=("$(_get_sublevel "$item")")
  229. done
  230. unset item
  231. # echo "${tree_flow[@]}" ##TESTING
  232. declare -r tree_flow # Why on earth does this prevent echo-ing the whole
  233. # array later (like above)?!
  234. }
  235. ## Display
  236. _mk_tree_item_indent() {
  237. ## Compile the indentation string for a tree item
  238. local -r indent_base_cont='├─ ' # Find better name?
  239. local -r indent_base_end='└─ ' # Find better name?
  240. local -r indent_space=' ' # 3 spaces
  241. local -r indent_trunk='│ '
  242. local -r item="$1"
  243. local -r index_item="$2"
  244. local -r index_start="$(($index_item + 1))"
  245. local -r index_end="$((${#tree_flow[@]} - 1))"
  246. local indent=
  247. local indent_base=
  248. local next_lower_pos=
  249. local next_prev_pos=
  250. local next_same_pos=
  251. local prev=
  252. local sublevel=
  253. sublevel="${tree_flow[index_item]}"
  254. # Using tree_flow because it can represent pagetree as well as navtree.
  255. # Determine indent_base
  256. prev="$((sublevel - 1))"
  257. local i=
  258. for ((i="$index_start"; i<="$index_end"; ++i))
  259. do
  260. if [[ -z "$next_same_pos" ]] && [[ "${tree_flow[i]}" -eq "$sublevel" ]]
  261. then
  262. next_same_pos="$i"
  263. elif [[ -z "$next_prev_pos" ]] && [[ "${tree_flow[i]}" -eq "$prev" ]]
  264. then
  265. next_prev_pos="$i"
  266. fi
  267. done
  268. unset i
  269. if [[ -z "$next_same_pos" ]]
  270. then
  271. indent_base="$indent_base_end"
  272. elif [[ -n "$next_prev_pos" && "$next_prev_pos" -lt "$next_same_pos" ]]
  273. then
  274. indent_base="$indent_base_end"
  275. else
  276. indent_base="$indent_base_cont"
  277. fi
  278. indent="$indent_base"
  279. # Compile additonal indentation string for sublevels greater than 1 (prepend
  280. # to $indent_base)
  281. local s=
  282. for ((s="$sublevel"; s > 1; --s))
  283. do
  284. prev="$((s - 1))"
  285. next_prev_pos=
  286. next_lower_pos=
  287. # Determine whether and where the previous sublevel and any sublevel lower
  288. # than that occurs later in the tree.
  289. local i=
  290. for ((i="$index_start"; i<="$index_end"; ++i))
  291. do
  292. if [[ -z "$next_prev_pos" ]] && [[ "${tree_flow[i]}" -eq "$prev" ]]
  293. then
  294. next_prev_pos="$i"
  295. elif [[ -z "$next_lower_pos" ]] && [[ "${tree_flow[i]}" -lt "$prev" ]]
  296. then
  297. next_lower_pos="$i"
  298. fi
  299. done
  300. unset i
  301. if [[ -z "$next_prev_pos" ]]
  302. # No further parent occurs
  303. then
  304. indent="${indent_space}${indent}"
  305. elif [[ -n "$next_prev_pos" ]] && [[ -z "$next_lower_pos" ]]
  306. # Further parent occurs, but no further ancestor occurs
  307. then
  308. indent="${indent_trunk}${indent}"
  309. elif [[ -n "$next_prev_pos" ]] && [[ -n "$next_lower_pos" ]]
  310. # Further parent occurs and further ancestor occurs
  311. then
  312. # Next parent occurs before next ancestor:
  313. if [[ "$next_prev_pos" -lt "$next_lower_pos" ]]
  314. then
  315. indent="${indent_trunk}${indent}"
  316. # Next parent occurs after next ancestor:
  317. elif [[ "$next_prev_pos" -gt "$next_lower_pos" ]]
  318. then
  319. indent="${indent_space}${indent}"
  320. fi
  321. fi
  322. done
  323. unset s
  324. printf '%s' "$indent"
  325. }
  326. _show_pagetree() {
  327. local -r index_start=0
  328. local indent=
  329. local item=
  330. local tree_mode=
  331. local -r index_end="$((${#pagetree[@]} - 1))"
  332. local -r index_width="${#index_end}"
  333. case "$1" in
  334. -add)
  335. tree_mode=add
  336. ;;
  337. -del)
  338. tree_mode=del
  339. ;;
  340. *)
  341. _perr "invalid option -- '$1'"
  342. return 1
  343. ;;
  344. esac
  345. _get_tree_flow "${pagetree[@]}" # _mk_tree_item_indent needs this
  346. local i=
  347. for ((i="$index_start"; i<="$index_end"; ++i))
  348. do
  349. if [[ "$i" -eq 0 ]]
  350. then
  351. case "$tree_mode" in
  352. add)
  353. item='[new top-level page]'
  354. ;;
  355. del)
  356. continue
  357. ;;
  358. esac
  359. else
  360. item="${pagetree[i]%/}" # Omit trailing slash before omitting
  361. # everything up until the last slash below.
  362. fi
  363. indent="$(_mk_tree_item_indent "$item" "$i")"
  364. printf " %${index_width}s %s%s%s\n" "$i" "$indent" "${item##*/}"
  365. done
  366. unset i
  367. }