#!/bin/sh # # dirindex - print a directory listing for Gemini, HTML, or Gopher # # SPDX-FileCopyrightText: 2023 Daniel Kalak # SPDX-License-Identifier: GPL-3.0-or-later # # This program prints a directory listing for a Gemini index file, an # HTML index file, or a Gophermap. Lines consist of the name of a file # (left-aligned), its human-readable size (right-aligned), and its # modification time in UTC (down to 1 minute), and are padded to be 64 # characters wide. (No alignment or padding is done for the HTML table # output.) The size of a directory is defined as the sum of the sizes of # all files in it, and its modification time is defined as the last # modification time of any file or directory in it (including itself). # Hidden files and directories as well as index files ("index.gmi", # "index.html", "gophermap") are ignored completely. # # Note that the Gemini and Gopher output is ready to be written to # "index.gmi" or "gophermap" as-is, but the HTML output needs to be # wrapped and embedded into a valid HTML document before writing it to # "index.html": the standard HTML output is just lines of text # (including elements) which need to be wrapped in
 tags, and
# the HTML table output is lines of  elements which need to be
# wrapped in  tags.
#
# The program returns 0 on success, 2 on bad usage (no output), and 1 if
# the type of a file is unknown when creating a Gophermap (binary is
# assumed) or if the name of a file is too wide.
#
# The program depends on the GNU coreutils, GNU find, and GNU awk.

usage_quit() {
	printf 'Usage:\n' >&2
	printf '  %s gemini|gmi\n' "$0" >&2
	printf '  %s web|html\n' "$0" >&2
	printf '  %s web_table|html_table|table\n' "$0" >&2
	printf '  %s gopher|gophermap CURRENT_SELECTOR SERVER\n' "$0" >&2
	exit 2
}

checklen() {
	if [ "${#1}" -gt 42 ]
	then
		printf 'Name is wider than 42 characters: %s\n' "$1" >&2
		return 1
	fi
}

# Extend or change the following function as you see fit.

gophertype() {
	case "$1" in
	*/)
		echo 1
		;;
	*.asc|*.ass|*.diff|*.gmi|*.html|*.py|*.sh|*.txt)
		echo 0
		;;
	*)
		printf 'Unknown file ending: %s\n' "$1" >&2
		echo 9
		return 1
		;;
	esac
}

# Besides doing argument handling, this prints the ".." line. If you
# don't want it, pipe the output of this program to "sed 1d". Note that
# ${var#prefix} strips a prefix and ${var%suffix} strips a suffix from a
# variable.

case "$1" in
gemini|gmi)
	[ "$#" -eq 1 ] || usage_quit
	printf '=> ../ ..\n'
	;;
web|html)
	[ "$#" -eq 1 ] || usage_quit
	printf '..\n'
	;;
web_table|html_table|table)
	[ "$#" -eq 1 ] || usage_quit
	printf '\n'
	;;
gopher|gophermap)
	[ "$#" -eq 3 ] || usage_quit
	[ "${dir:="$2"}" ] || usage_quit
	[ "${server:="$3"}" ] || usage_quit
	dir="/${dir#/}"
	parentdir="$(dirname "$dir")"
	printf '1..\t%s\t%s\t70\r\n' "${parentdir%/}/" "$server"
	;;
*)
	usage_quit
	;;
esac

TZ= find -depth -name . -o -name '.*' -prune -o \
	-name index.gmi -o -name index.html -o -name gophermap -o \
	-type d -printf '0\t%TY-%Tm-%Td %TH:%TM\t%p/\n' -o \
	-printf '%s\t%TY-%Tm-%Td %TH:%TM\t%p\n' |
awk '
	BEGIN { FS = OFS = "\t" }
	{ s += $1 }
	$2 > t { t = $2 }
	$3 ~ /^\.\/[^\/]*\/?$/ { print s, t, substr($3, 3); s = t = 0 }' |
LC_ALL=C numfmt --to=iec |

# The intermediate format in the pipeline consists of the human-readable
# size, the modification date, the modification time, and the file name
# (similar to "du -h --time"). "-k4" sorts by the names. Use "-h" to
# sort by the sizes (smallest first), "-k2" to sort by the times (oldest
# first), and "-r" to reverse the order.

LC_ALL=C sort -k4 | {

while read -r size date time name
do
	# Directory names end in "/" in the intermediate format; you can
	# hide directory sizes with [ "${name#"${name%/}"}" ] && size=

	desc="$(printf '%-42s %4s %s %s\n' "$name" "$size" "$date" "$time")"
	namelink="$(printf '%s\n' "$name" "$name")"

	case "$1" in
	gemini|gmi)
		checklen "$name" || err=1
		printf '=> %s %s\n' "$name" "$desc"
		;;
	web|html)
		checklen "$name" || err=1
		printf '%s%s\n' "$namelink" "${desc#"$name"}"
		;;
	web_table|html_table|table)
		printf ''
		printf '' "$namelink"
		printf '' "$size"
		printf '' "$date" "$time"
		printf '\n'
		;;
	gopher|gophermap)
		checklen "$name" || err=1
		type="$(gophertype "$name")" || err=1
		printf '%s%s\t%s\t%s\t70\r\n' \
			"$type" "$desc" "${dir%/}/$name" "$server"
		;;
	esac
done

# Since it is part of a pipeline, the while loop would have been a
# subprocess of its own if it wasn't surrounded by {}, and any
# assignments to $err would have been lost outside of it. With {}, {}
# forms the subprocess, and assignments to $err are kept until }. The
# following command exits the {} subprocess with $err as its exit value,
# and the exit value of the {} subprocess is used implicitly as the exit
# value of the entire program, as it is its last command. You can make
# it explicit with "exit $?" after }.

exit "${err:-0}" ;}
gemini://dkalak.de/software/dirindex.sh

-- Leo's gemini proxy

-- Connecting to dkalak.de:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/plain

-- Response ended

-- Page fetched on Wed Jun 5 13:05:50 2024

..
%s%s