Files
home-scripts/markdownify.sh
2026-06-04 12:35:45 -04:00

132 lines
2.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
printf 'Usage: %s INPUT_DIR [OUTPUT_DIR]\n' "$(basename "$0")" >&2
exit 2
}
[[ $# -ge 1 && $# -le 2 ]] || usage
input_dir=${1%/}
[[ -d "$input_dir" ]] || {
printf 'error: input is not a directory: %s\n' "$input_dir" >&2
exit 1
}
parent_dir=$(dirname "$input_dir")
input_base=$(basename "$input_dir")
output_dir=${2:-"$parent_dir/${input_base}-markdown"}
output_dir=${output_dir%/}
lang_for_file() {
local file=$1
local base ext shebang
base=$(basename "$file")
case "$base" in
flake.lock) echo json; return ;;
Dockerfile|Containerfile) echo dockerfile; return ;;
Makefile) echo makefile; return ;;
esac
ext="${base##*.}"
case "$ext" in
nix) echo nix ;;
sh|bash) echo bash ;;
zsh) echo zsh ;;
fish) echo fish ;;
py) echo python ;;
js|mjs|cjs) echo javascript ;;
ts|mts|cts) echo typescript ;;
json) echo json ;;
jsonc) echo jsonc ;;
yaml|yml) echo yaml ;;
toml) echo toml ;;
md|markdown) echo markdown ;;
lua) echo lua ;;
vim) echo vim ;;
conf|cfg|ini) echo ini ;;
service|timer|socket|mount|target) echo systemd ;;
env) echo dotenv ;;
css) echo css ;;
html|htm) echo html ;;
rs) echo rust ;;
go) echo go ;;
c) echo c ;;
h) echo c ;;
cpp|cc|cxx|hpp|hh|hxx) echo cpp ;;
java) echo java ;;
sql) echo sql ;;
xml) echo xml ;;
*)
if IFS= read -r shebang < "$file" && [[ "$shebang" == '#!'* ]]; then
case "$shebang" in
*bash*) echo bash ;;
*zsh*) echo zsh ;;
*sh*) echo sh ;;
*python*) echo python ;;
*node*) echo javascript ;;
*) echo text ;;
esac
else
echo text
fi
;;
esac
}
# Pick a fence longer than any run of backticks in the file.
fence_for_file() {
local file=$1
local max_ticks=2
# Finds all runs of backticks, sorts by length, returns longest length.
local longest
longest=$(
grep -ao '`*' "$file" 2>/dev/null \
| awk '{ print length }' \
| sort -nr \
| head -n1
)
if [[ -n "${longest:-}" && "$longest" -gt "$max_ticks" ]]; then
max_ticks=$longest
fi
printf '%*s' "$((max_ticks + 1))" '' | tr ' ' '`'
}
mkdir -p "$output_dir"
find "$input_dir" -type f -print0 |
while IFS= read -r -d '' src; do
rel=${src#"$input_dir"/}
dest="$output_dir/$rel.md"
dest_dir=$(dirname "$dest")
mkdir -p "$dest_dir"
# Skip likely-binary files.
if ! grep -Iq . "$src"; then
printf 'skip binary: %s\n' "$rel" >&2
continue
fi
lang=$(lang_for_file "$src")
fence=$(fence_for_file "$src")
# Backticks are forbidden in CommonMark info strings.
safe_rel=${rel//\`/\'}
{
printf '%s%s %s\n' "$fence" "$lang" "$safe_rel"
cat "$src"
printf '\n%s\n' "$fence"
} > "$dest"
printf 'wrote: %s\n' "$dest"
done