mirror of
https://gitea.com/gitea/docs.git
synced 2026-11-05 17:45:46 +00:00
update_cli_docs.sh runs gt's own generator and lays the pages out for the site: one directory per command group, front matter, titles, sidebar order, fenced help text and rewritten links. --released succeeds while no series is documented. A weekly workflow proposes updates like the runner's. Signed-off-by: bircni <[email protected]>
316 lines
10 KiB
Bash
Executable File
316 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Regenerates the generated pages of the gt (Gitea CLI) documentation from the
|
|
# gitea/cli sources: one command reference page per command and help topic.
|
|
#
|
|
# Usage:
|
|
#
|
|
# ./update_cli_docs.sh develop docs, from the main branch
|
|
# ./update_cli_docs.sh --released every documented release series
|
|
# ./update_cli_docs.sh <git ref> <dir> one ref into one directory
|
|
#
|
|
# --released needs no version list: the documented series are the
|
|
# cli-docs_versioned_docs/version-<series>/reference directories, and the tag
|
|
# each one is generated from is the newest stable v<series>.x tag of gitea/cli,
|
|
# looked up through the Gitea API. A new series is documented by running
|
|
# `make cut-version PRODUCT=cli VERSION=<series>`, nothing here has to be edited.
|
|
|
|
set -euo pipefail
|
|
|
|
CLI_REMOTE="${CLI_REMOTE:-https://gitea.com/gitea/cli.git}"
|
|
CLI_API="${CLI_API:-https://gitea.com/api/v1/repos/gitea/cli}"
|
|
VERSIONED_DOCS="cli-docs_versioned_docs"
|
|
SRC_DIR=".tmp/upstream-cli"
|
|
OUT_DIR="$PWD/.tmp/cli-generated"
|
|
TAGS_FILE=".tmp/cli-tags.txt"
|
|
MARKER='Generated by update_cli_docs.sh from the gitea/cli sources, do not edit.'
|
|
|
|
mkdir -p .tmp
|
|
|
|
# prints every tag name of the cli repository, one per line
|
|
list_cli_tags() {
|
|
local page=1 names
|
|
while :; do
|
|
names="$(curl --silent --show-error --fail \
|
|
"$CLI_API/tags?limit=50&page=$page" |
|
|
grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | cut -d '"' -f 4)"
|
|
[ -n "$names" ] || return 0
|
|
printf '%s\n' "$names"
|
|
page=$((page + 1))
|
|
done
|
|
}
|
|
|
|
# latest_stable_tag <series>, e.g. "0.1" -> "v0.1.2", empty if the series has
|
|
# no release yet. Pre-release tags (v0.1.0-rc1) are ignored on purpose.
|
|
latest_stable_tag() {
|
|
local series="$1"
|
|
if [ ! -s "$TAGS_FILE" ]; then
|
|
list_cli_tags > "$TAGS_FILE"
|
|
fi
|
|
{ grep -E "^v${series//./\\.}(\.[0-9]+)+$" "$TAGS_FILE" || true; } |
|
|
sed 's/^v//' |
|
|
sort -t . -k1,1n -k2,2n -k3,3n |
|
|
tail -n 1 |
|
|
sed 's/^/v/'
|
|
}
|
|
|
|
# generate_pages <git ref>, writes gt's markdown reference to $OUT_DIR/reference
|
|
generate_pages() {
|
|
local ref="$1" format=sha1 head
|
|
|
|
# gitea/cli uses sha256 object names, which a fetch into a sha1 repository rejects
|
|
head="$(git ls-remote "$CLI_REMOTE" HEAD | awk '$2 == "HEAD" { print $1; exit }')"
|
|
[ "${#head}" -ne 64 ] || format=sha256
|
|
|
|
if [ -d "$SRC_DIR/.git" ] && [ "$(git -C "$SRC_DIR" rev-parse --show-object-format)" = "$format" ]; then
|
|
git -C "$SRC_DIR" remote set-url origin "$CLI_REMOTE"
|
|
else
|
|
rm -rf "$SRC_DIR"
|
|
git init --quiet --object-format="$format" "$SRC_DIR"
|
|
git -C "$SRC_DIR" remote add origin "$CLI_REMOTE"
|
|
fi
|
|
git -C "$SRC_DIR" fetch --quiet --depth 1 origin "$ref"
|
|
git -C "$SRC_DIR" checkout --quiet --detach FETCH_HEAD
|
|
|
|
rm -rf "$OUT_DIR"
|
|
(cd "$SRC_DIR" && go run ./tools/cli-docs --out "$OUT_DIR")
|
|
}
|
|
|
|
# Cobra names a page after the command path, gt_pr_review_list.md. On the site
|
|
# a command with subcommands becomes a directory with an index page, so the
|
|
# sidebar groups it, and the help topics, which gt.md does not list, go to help/.
|
|
|
|
# command path of a cobra page name: gt_pr_review_list.md -> "pr review list"
|
|
command_of() {
|
|
local name="${1%.md}"
|
|
name="${name#gt}"
|
|
name="${name#_}"
|
|
printf '%s\n' "${name//_/ }"
|
|
}
|
|
|
|
is_topic() {
|
|
[[ " $TOPICS " == *" $1 "* ]]
|
|
}
|
|
|
|
# page path of a command, relative to the reference directory
|
|
page_of() {
|
|
local command="$1"
|
|
if [ -z "$command" ]; then
|
|
echo index.md
|
|
elif is_topic "$command"; then
|
|
echo "help/$command.md"
|
|
elif grep -qx "$command .*" <<< "$COMMANDS_LIST"; then
|
|
echo "${command// //}/index.md"
|
|
else
|
|
echo "${command// //}.md"
|
|
fi
|
|
}
|
|
|
|
# sidebar position of a command among its siblings, in the alphabetical order of gt --help
|
|
position_of() {
|
|
local command="$1" parent
|
|
if [ -z "$command" ]; then
|
|
echo 0
|
|
return
|
|
fi
|
|
if is_topic "$command"; then
|
|
awk -v topic="$command" '$0 == topic { print NR }' <<< "$TOPICS_LIST"
|
|
return
|
|
fi
|
|
parent="${command% *}"
|
|
[ "$parent" != "$command" ] || parent=""
|
|
awk -v parent="$parent" -v command="$command" '
|
|
{
|
|
p = $0
|
|
if (!sub(/ [^ ]+$/, "", p)) p = ""
|
|
if (p == parent) {
|
|
count++
|
|
if ($0 == command) { print count; exit }
|
|
}
|
|
}' <<< "$COMMANDS_LIST"
|
|
}
|
|
|
|
# write_page <cobra page> <target directory>
|
|
write_page() {
|
|
local source="$1" target="$2" command page depth up title position description
|
|
|
|
command="$(command_of "$(basename "$source")")"
|
|
page="$(page_of "$command")"
|
|
depth="$(tr -cd / <<< "$page" | wc -c | tr -d ' ')"
|
|
up=""
|
|
for ((i = 0; i < depth; i++)); do up="../$up"; done
|
|
position="$(position_of "$command")"
|
|
# the line below the heading is the short description of the command
|
|
description="$(sed -n '3p' "$source")"
|
|
|
|
if [ -z "$command" ]; then
|
|
title="Command reference"
|
|
elif is_topic "$command"; then
|
|
title="gt help $command"
|
|
else
|
|
title="gt $command"
|
|
fi
|
|
|
|
mkdir -p "$target/$(dirname "$page")"
|
|
{
|
|
printf -- '---\nsidebar_position: %s\ndescription: "%s"\n---\n\n' "$position" "${description//\"/\\\"}"
|
|
printf '# %s\n\n<!-- %s -->\n\n' "$title" "$MARKER"
|
|
if [ -z "$command" ]; then
|
|
cat <<'EOF'
|
|
Every `gt` command and flag, generated from the `--help` output of gt's main branch
|
|
or release. Global flags work with every command; [gt help environment](help/environment.md)
|
|
describes them together with the environment variables gt reads.
|
|
|
|
EOF
|
|
fi
|
|
# drop the cobra heading, move the other headings one level up, fence the
|
|
# synopsis and point the links between pages at their new paths. The
|
|
# synopsis is help text written for a terminal: its tables are indented by
|
|
# two spaces and its placeholders, such as <url>, would be read as HTML.
|
|
COMMANDS_LIST="$COMMANDS_LIST" awk -v up="$up" -v topics="$TOPICS" '
|
|
BEGIN {
|
|
n = split(ENVIRON["COMMANDS_LIST"], list, "\n")
|
|
for (i = 1; i <= n; i++) {
|
|
c = list[i]
|
|
sub(/ [^ ]+$/, "", c)
|
|
if (c != list[i]) parents[c] = 1
|
|
}
|
|
m = split(topics, names, " ")
|
|
for (i = 1; i <= m; i++) topic[names[i]] = 1
|
|
}
|
|
function target(file, c, index_page) {
|
|
c = file
|
|
sub(/\.md$/, "", c)
|
|
sub(/^gt_?/, "", c)
|
|
gsub(/_/, " ", c)
|
|
if (c == "") return up "index.md"
|
|
if (c in topic) return up "help/" c ".md"
|
|
index_page = c in parents
|
|
gsub(/ /, "/", c)
|
|
return up c (index_page ? "/index.md" : ".md")
|
|
}
|
|
function flush_synopsis( first, last, i) {
|
|
first = 1
|
|
while (first <= lines && synopsis[first] == "") first++
|
|
last = lines
|
|
while (last >= first && synopsis[last] == "") last--
|
|
if (first <= last) {
|
|
print "```text"
|
|
for (i = first; i <= last; i++) print synopsis[i]
|
|
print "```"
|
|
print ""
|
|
}
|
|
in_synopsis = 0
|
|
}
|
|
NR == 1 && /^## / { skip = 1; next }
|
|
skip && /^$/ { skip = 0; next }
|
|
in_synopsis && !/^```/ && !/^### (Examples|Options|Options inherited from parent commands|SEE ALSO)$/ {
|
|
synopsis[++lines] = $0
|
|
next
|
|
}
|
|
in_synopsis { flush_synopsis() }
|
|
/^### Synopsis$/ { print "## Synopsis"; print ""; in_synopsis = 1; lines = 0; next }
|
|
/^```/ { fenced = !fenced }
|
|
!fenced && /^#+ / { sub(/^#/, "") }
|
|
/^\* \[/ { sub(/\)\t - /, "): ") }
|
|
{
|
|
line = $0
|
|
out = ""
|
|
while (match(line, /\]\(gt[^)]*\.md\)/)) {
|
|
file = substr(line, RSTART + 2, RLENGTH - 3)
|
|
out = out substr(line, 1, RSTART + 1) target(file) ")"
|
|
line = substr(line, RSTART + RLENGTH)
|
|
}
|
|
print out line
|
|
}
|
|
END { if (in_synopsis) flush_synopsis() }
|
|
' "$source"
|
|
} > "$target/$page"
|
|
}
|
|
|
|
# generate_reference <git ref> <target directory>
|
|
generate_reference() {
|
|
local ref="$1" target="$2" source command
|
|
|
|
generate_pages "$ref"
|
|
local reference="$OUT_DIR/reference"
|
|
if [ ! -s "$reference/gt.md" ]; then
|
|
echo "$CLI_REMOTE@$ref did not generate $reference/gt.md" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# every command, in the alphabetical order gt --help lists them in
|
|
COMMANDS_LIST="$(for source in "$reference"/gt_*.md; do command_of "$(basename "$source")"; done | LC_ALL=C sort)"
|
|
# the help topics are the pages gt.md does not link to
|
|
TOPICS_LIST="$(for source in "$reference"/gt_*.md; do
|
|
command="$(command_of "$(basename "$source")")"
|
|
[[ "$command" == *" "* ]] && continue
|
|
grep -qF "](gt_$command.md)" "$reference/gt.md" || echo "$command"
|
|
done)"
|
|
TOPICS="$(tr '\n' ' ' <<< "$TOPICS_LIST")"
|
|
COMMANDS_LIST="$(grep -vxF -f <(printf '%s\n' $TOPICS) <<< "$COMMANDS_LIST" || true)"
|
|
|
|
# remove the pages of an earlier run, so a removed command does not linger
|
|
if [ -d "$target" ]; then
|
|
{ grep -rlF --include='*.md' "$MARKER" "$target" || true; } | while IFS= read -r source; do rm "$source"; done
|
|
find "$target" -depth -type d -empty -delete
|
|
fi
|
|
mkdir -p "$target"
|
|
|
|
write_page "$reference/gt.md" "$target"
|
|
for source in "$reference"/gt_*.md; do
|
|
write_page "$source" "$target"
|
|
done
|
|
|
|
if [ -n "$TOPICS" ]; then
|
|
{
|
|
printf -- '---\nsidebar_position: %s\ndescription: "%s"\n---\n\n' \
|
|
"$(($(grep -cv ' ' <<< "$COMMANDS_LIST") + 1))" \
|
|
"Guides built into gt, read with gt help <topic>."
|
|
printf '# Help topics\n\n<!-- %s -->\n\n' "$MARKER"
|
|
printf 'Guides built into gt, read with `gt help <topic>`:\n\n'
|
|
for command in $TOPICS; do
|
|
printf -- '- [gt help %s](%s.md): %s\n' "$command" "$command" "$(sed -n '3p' "$reference/gt_$command.md")"
|
|
done
|
|
} > "$target/help/index.md"
|
|
fi
|
|
|
|
echo "wrote $(grep -rlF --include='*.md' "$MARKER" "$target" | wc -l | tr -d ' ') pages to $target from $CLI_REMOTE@$ref"
|
|
}
|
|
|
|
# regenerates every documented release series from its newest stable tag
|
|
generate_released() {
|
|
local dir series tag found=0
|
|
|
|
for dir in "$VERSIONED_DOCS"/version-*/reference; do
|
|
[ -d "$dir" ] || continue
|
|
found=1
|
|
series="${dir#"$VERSIONED_DOCS"/version-}"
|
|
series="${series%/reference}"
|
|
|
|
tag="$(latest_stable_tag "$series")"
|
|
if [ -z "$tag" ]; then
|
|
echo "no stable v$series tag in $CLI_API, skipping $dir" >&2
|
|
continue
|
|
fi
|
|
generate_reference "$tag" "$dir"
|
|
done
|
|
|
|
# before the first release only the develop docs exist, which is not an error
|
|
if [ "$found" -eq 0 ]; then
|
|
echo "no $VERSIONED_DOCS/version-*/reference directory, no released series to regenerate"
|
|
fi
|
|
}
|
|
|
|
case "${1:-}" in
|
|
--released)
|
|
generate_released
|
|
;;
|
|
-h | --help)
|
|
sed -n '3,17s/^#\{1,2\} \{0,1\}//p' "$0"
|
|
;;
|
|
*)
|
|
generate_reference "${1:-main}" "${2:-cli-docs/reference}"
|
|
;;
|
|
esac
|