lumiera_/admin/rfc.sh
Ichthyostega 23f6f731f1 DOC: update technical docs to reflect recent development
At various places, concepts and drafts from the early stage of the
Lumiera Project are still reflected in the online documentation pages.
During the last months, development focussed on the Render Engine,
causing a shift in some parts of the design, and obsoleting other
parts altogether (notably we consider to use IO_URING for async IO)
2023-10-25 00:02:08 +02:00

493 lines
11 KiB
Bash
Executable file

#!/bin/bash
shopt -s extglob
#
# RFC's are created from ./doc/devel/template/new_rfc.sh and stored in
# ./doc/devel/rfc/ . There are directories for the various states
# ./doc/devel/rfc_final, ./doc/devel/rfc_pending, ./doc/devel/rfc_parked,
# ./doc/devel/rfc_dropped . Which contain symlinks back to ../rfc/
#
function usage()
{
grep -v '^// ' <<"EOF" | less -F
Lumiera RFC maintenance script
==============================
// Note: the source of this documentation is maintained
// directly admin/rfc.sh in its usage() function
// edit it only there and then regenerate
// doc/devel/technical/infra/ with:
// ./admin/rfc.sh help >doc/technical/infra/rfcsh.txt
Usage
-----
./admin/rfc.sh <command> [options]
Commands (with <mandatory> and [optional] parameters)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
find <rfcs> [regex]:: List all matching RFC's (matching `regex`)
show <rfcs> [regex]:: Read RFC's (matching `regex`)
create <title>:: Create a new RFC
edit <rfc> [chapter]:: Edit RFC at chapter
asciidoc <rfc>:: pass the rfc.txt to `asciidoc` command
comment <rfc>:: Add a new comment to a RFC
draft <rfc>:: Change RFC to Draft state
final <rfc>:: Change RFC to Final state
park <rfc>:: Change RFC to Parked state
drop <rfc>:: Change RFC to Dropped state
supersede <rfc> <new>:: Supersede RFC with a new RFC
discard <rfc>:: Delete an RFC
help:: Show this help
process:: Do automatic maintenance work
wrap <rfcs>:: canonical reformatting
Option types
~~~~~~~~~~~~
title:: Quoted string used as RFC title
rfc:: Name of the RFC, smart matched, unique
rfcs:: Name of the RFC, smart matched, multiple
regex:: Regex matched against the content of a RFC
chapter:: Heading of a section
Smart matching
--------------
RFC names don't need to be given exactly, they use a globbing pattern.
This is:
* case insensitive
* whitespaces are ignored
* `*` stands for any number of parameters
* `?` is any single character
* when starting with `/` they are matched against the begin of the name
* some regex operators work too
`find` and `show` can operate on many matches so the given rfc name doesn't
need to be unique. The other commands will complain when the RFC name given
doesn't resolve to one unique RFC.
When `less` presents multiple files one can go forth and back with the `:n`
and `:p` commands.
The tile for `create` should be a normal document title. Possibly quoted
since it may contain spaces and not too long. The filename is this title
in CamelCase with all spaces and special characters removed.
Chapter machching single lines containing this word, special asciidoc
comments in the form `//word:.*` and asciidoc block attributes `[word.*]`
on a single line. When a chapter pattern is not unique, the last one is
picked.
rfc.sh executes git add/rm/mv commands, but never commits. One should do a
commit as soon he finished editing.
EOF
}
function camel_case()
{
sed -e 's/[^[:alnum:]]/ /g;s/ \+\(.\)/\U\1/g' <<<" $1"
}
function untabify()
{
local in="$1"
local tmp
local out
while [[ "$in" ]]; do
tmp="${in%%$'\t'*}"
in="${in:${#tmp}+1}"
out="$out$tmp "
out="${out:0:${#out}/8*8}"
done
out="${out%%*( )}"
echo "$out"
}
function word_wrap()
{
local linelength="${1:-80}"
local indent=""
local text
local tab=$'\t'
local rest
local oldindent
local coderx='//|/\*|#'
local meld=yes
while IFS='' read -r line; do
line="$(untabify "$line")"
# remove DOS linebreaks
line="${line//$'\r'/}"
text="${line##*([![:alnum:]])}"
oldindent="$indent"
indent="${line:0:${#line}-${#text}}"
if [[ "$text" ]]; then
# avoid melding text after // /* and # comments
if [[ "$indent" =~ $coderx ]]; then
meld=no
fi
if [[ $meld = yes && ! "${indent// /}" && ${#oldindent} -eq ${#indent} ]]; then
line="${rest:+$rest }$text"
else
if [[ "$rest" ]]; then
echo "$rest"
fi
fi
indent="${indent//?/ }"
while [[ "${#line}" -ge "${linelength}" ]]; do
local wrapped
local tmpline="${line:0:$linelength}"
wrapped="${tmpline% *}"
if [[ "${#wrapped}" -gt "${#indent}" ]]; then
line="${indent}${line:${#wrapped}+1}"
else
wrapped="$line"
line=''
fi
echo "$wrapped"
done
rest="${line}"
else
meld=yes
if [[ "$rest" ]]; then
echo "$rest"
rest=""
fi
echo "$line"
fi
done
if [[ "$rest" ]]; then
echo "$rest"
fi
}
function smart_wrap()
{
# calling on a file
word_wrap $2 <"$1" >"$1.$$.tmp"
if ! cmp "$1" "$1.$$.tmp" &>/dev/null ; then
cat "$1.$$.tmp" >"$1"
git add "$1"
fi
rm "$1.$$.tmp"
}
function find_rfc()
{
# find rfc by shortname
local file
local match="$1"
match="${match// /}"
match="${match//./\.}"
match="${match//\?/.}"
match="${match//\*/.*}"
local globstate=$(shopt -p nocasematch)
shopt -s nocasematch
for file in $(find ./doc/devel/rfc/ -name '*.txt');
do
local name="/${file##*/}"
if [[ "$name" =~ $match ]]; then
echo "$file"
fi
done
$globstate
}
function find_chapter()
{
# find chapter
local file="$1"
local chapter="$2"
local found=$(grep -n -i "^\($chapter\|//$chapter:.*\|\[$chapter.*\]\)\$" "$file" | tail -1)
if [[ "$found" ]]; then
echo "${found%%:*}"
fi
}
function process_file()
{
local file="$1"
local path="${1%/*}"
local basename="${1##*/}"
local linkdest="$path"
local state=$(grep '^\*State\* *' "$file")
case "$state" in
*Final*)
linkdest="./doc/devel/rfc_final"
;;
*Idea*|*Draft*)
linkdest="./doc/devel/rfc_pending"
;;
*Parked*)
linkdest="./doc/devel/rfc_parked"
;;
*Dropped*)
linkdest="./doc/devel/rfc_dropped"
;;
*)
echo "Unknown State: '$state'" >&2
exit 1
esac
local oldpath
for oldpath in ./doc/devel/rfc_*/$basename; do :; done
if [[ -h "$oldpath" ]]; then
if [[ "$oldpath" != "$linkdest/$basename" ]]; then
git mv "$oldpath" "$linkdest/$basename"
fi
elif [[ ! -s "$linkdest/$basename" ]]; then
ln -s "../rfc/$basename" "$linkdest/"
git add "$linkdest/$basename"
fi
}
function edit()
{
# filename lineoffset chapter
EDITOR="${EDITOR:-$(git config --get core.editor)}"
EDITOR="${EDITOR:-$VISUAL}"
if [ -z "$EDITOR" ]; then
echo -e "\nFATAL\n\$EDITOR undefined\n\n"
exit -1
fi
local file="$1"
local line=0
if [[ "$3" ]]; then
line=$(find_chapter "$file" "$3")
fi
$EDITOR +$(($line+${2:-1})) $file
}
function unique_name()
{
local files=($(find_rfc "$1"))
if [[ ${#files[*]} -gt 1 ]]; then
echo "multiple matches:" >&2
(
IFS=$'\n'
echo "${files[*]}" >&2
)
elif [[ ${#files[*]} -eq 0 ]]; then
echo "no matches" >&2
else
echo ${files[0]}
fi
}
function add_comment()
{
local name="$1"
local nl=$'\n'
local comment="//edit comment$nl$nl$(git config --get user.name):: '$(date +%c)' ~<$(git config --get user.email)>~$nl"
ed "$name" 2>/dev/null <<EOF
/endof_comments:/-1i
$comment
.
wq
EOF
edit "$name" -4 "endof_comments"
}
function edit_state()
{
local name="$1"
local state="$2"
local comment="$3"
ed "$name" 2>/dev/null <<EOF
/^\*State\*/s/\(\*State\* *_\).*_/\1${state}_/
wq
EOF
if [[ "$comment" ]]; then
ed "$name" 2>/dev/null <<EOF
/endof_comments:/-1i
$comment
.
wq
EOF
fi
}
function change_state()
{
#rfcname state
local name="$1"
local state="$2"
local nl=$'\n'
local comment=".State -> $state$nl//add reason$nl$nl$(git config --get user.name):: '$(date +%c)' ~<$(git config --get user.email)>~$nl"
edit_state "$name" "$state" "$comment"
edit "$name" -4 "endof_comments"
process_file "$name"
}
command="$1"
shift
case "$command" in
process)
# for all rfc's
for file in $(find ./doc/devel/rfc -name '*.txt');
do
echo "process $file"
process_file "$file"
done
:
;;
search)
grep -r -C3 -n "$1" ./doc/devel/rfc | less -F
;;
find|list|ls)
if [[ "$2" ]]; then
find_rfc "$1" | xargs grep -i -C3 -n "$2"
else
find_rfc "$1"
fi
;;
show|less|more)
if [[ "$2" ]]; then
less $(find_rfc "$1" | xargs grep -i -l "$2")
else
less $(find_rfc "$1")
fi
;;
create)
TITLE="$@"
name=$(camel_case "$TITLE")
if [[ -f "./doc/devel/rfc/${name}.txt" ]]; then
echo "$name.txt exists already"
else
source ./doc/devel/template/new_rfc.sh >"./doc/devel/rfc/${name}.txt"
edit "./doc/devel/rfc/${name}.txt" 2 abstract
git add "./doc/devel/rfc/${name}.txt"
process_file "./doc/devel/rfc/${name}.txt"
fi
;;
edit)
name=$(unique_name "$1")
if [[ "$name" ]]; then
edit "${name}" 2 "$2"
process_file "$name"
fi
;;
asciidoc)
name=$(unique_name "$1")
if [[ "$name" ]]; then
asciidoc "${name}"
fi
;;
draft)
name=$(unique_name "$1")
if [[ "$name" ]]; then
change_state "$name" Draft
fi
;;
park)
name=$(unique_name "$1")
if [[ "$name" ]]; then
change_state "$name" Parked
fi
;;
final)
name=$(unique_name "$1")
if [[ "$name" ]]; then
change_state "$name" Final
fi
;;
supersede)
name=$(unique_name "$1")
newname=$(unique_name "$2")
newname="${newname##*/}"
newname="${newname%.txt}"
if [[ "$name" && "$newname" ]]; then
change_state "$name" "Superseded by $newname"
fi
;;
drop)
name=$(unique_name "$1")
if [[ "$name" ]]; then
change_state "$name" Dropped
fi
;;
comment)
name=$(unique_name "$1")
if [[ "$name" ]]; then
add_comment "${name}"
process_file "$name"
fi
;;
discard)
name=$(unique_name "$1")
if [[ "$name" ]]; then
for link in ./doc/devel/rfc_*/${name##*/}; do :; done
if [[ -h "$link" ]]; then
git rm -f "${link}" || rm "${link}"
fi
git rm -f "${name}" || rm "${name}"
fi
;;
wrap)
find_rfc "$1" | while read file; do
smart_wrap "$file" 80
process_file "$file"
done
;;
smart_wrap)
# multipurpose smart wrap
smart_wrap "$1" ${2:-80}
;;
help|*)
usage
;;
esac