295 lines
11 KiB
Bash
Executable file
295 lines
11 KiB
Bash
Executable file
#!/bin/bash
|
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
# See Notices.txt for copyright information
|
|
set -e
|
|
export ANTLR4_TOOLS_ANTLR_VERSION=4.13.2
|
|
cd "$(dirname $0)/.."
|
|
pip -q --require-virtualenv install \
|
|
antlr4-python3-runtime=="$ANTLR4_TOOLS_ANTLR_VERSION" \
|
|
antlr4-tools==0.2.2
|
|
(cd takentaal; antlr4 -Dlanguage=Python3 -o takentaal takentaal.g4)
|
|
cat <<'EOF' > takentaal/takentaal/__main__.py
|
|
from antlr4 import *
|
|
from antlr4.error.ErrorListener import ErrorListener
|
|
from .takentaalLexer import takentaalLexer
|
|
from .takentaalParser import takentaalParser
|
|
import sys
|
|
|
|
class MyErrorListener(ErrorListener):
|
|
def __init__(self, filename):
|
|
self.filename = filename
|
|
|
|
def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
|
|
print(f"{self.filename}:{line}:{column} {msg}", file=sys.stderr)
|
|
|
|
def main():
|
|
filename = sys.argv[2]
|
|
parser = takentaalParser(CommonTokenStream(takentaalLexer(
|
|
FileStream(filename, encoding="utf-8"))))
|
|
parser.removeErrorListeners()
|
|
parser.addErrorListener(MyErrorListener(filename))
|
|
getattr(parser, sys.argv[1])()
|
|
if parser.getNumberOfSyntaxErrors() > 0:
|
|
sys.exit(1)
|
|
main()
|
|
EOF
|
|
touch takentaal/takentaal/__init__.py
|
|
|
|
function check_plan()
|
|
{
|
|
python3 -m takentaal.takentaal takentaal_v1_0 "$1"
|
|
}
|
|
|
|
function check_amendment()
|
|
{
|
|
python3 -m takentaal.takentaal amendment_v1_0 "$1"
|
|
}
|
|
|
|
function matches_split_prefix() {
|
|
local expected="$1" text="$2"
|
|
shift 2
|
|
local split_token text_prefix next_text_prefix
|
|
for split_token in "$@"; do
|
|
text_prefix="$text"
|
|
until [[ -z "$text_prefix" ]]; do
|
|
if [[ "$expected" = "$text_prefix" ]]; then
|
|
return 0
|
|
else
|
|
next_text_prefix="${text_prefix%"$split_token"*}"
|
|
if [[ "$text_prefix" = "$next_text_prefix" ]]; then
|
|
break
|
|
else
|
|
text_prefix="$next_text_prefix"
|
|
fi
|
|
fi
|
|
done
|
|
done
|
|
return 1
|
|
}
|
|
|
|
function issue_url() {
|
|
local issue_number="$1"
|
|
echo "https://git.libre-chip.org/libre-chip/grant-tracking/issues/$issue_number"
|
|
}
|
|
|
|
function grant_url() {
|
|
local nlnet_id="$1"
|
|
if [[ "$nlnet_id" == "YYYY-MM-ID" ]]; then
|
|
nlnet_id=template
|
|
fi
|
|
echo "https://git.libre-chip.org/libre-chip/grant-tracking/src/branch/master/nlnet-$nlnet_id/progress.md"
|
|
}
|
|
|
|
function check_issue()
|
|
(
|
|
issue_number="$1"
|
|
nlnet_id="$2"
|
|
subtask_title="$3"
|
|
subtask_body="$4"
|
|
if [[ -z "$FORGEJO_TOKEN" ]]; then
|
|
return
|
|
fi
|
|
example_title="NLnet $nlnet_id $subtask_title"
|
|
body_prefix="Issue for tracking progress of a subtask of [NLnet grant $nlnet_id]($(grant_url "$nlnet_id")):
|
|
$subtask_title
|
|
$subtask_body
|
|
"
|
|
body_suffix="" # TODO
|
|
example_body_middle=$'\n<!-- add additional content here if you like -->\n'
|
|
function error() {
|
|
# emit escape sequence for a hyperlink to the issue
|
|
bar="---------------------------------------------------------------------------"
|
|
body="$body_prefix"
|
|
body_middle="${example_body_middle%$'\n'}"
|
|
body+="${body_middle#$'\n'}"
|
|
body+="$body_suffix"
|
|
printf '\n\n\e]8;;%s\e\\Issue #%s\e]8;;\e\\: error: %s\nExample Issue:\nTitle: %s\nDescription:\n%s\n%s\n%s\n' \
|
|
"$(issue_url "$issue_number")" "$issue_number" "$*" \
|
|
"$example_title" "$bar" "$body" "$bar" >&2
|
|
exit 1
|
|
}
|
|
if [[ "$issue_number" =~ ^'N'?$ ]]; then
|
|
error "issue number must be set"
|
|
fi
|
|
[[ "$issue_number" =~ ^[0-9]+$ ]] || error "invalid issue number"
|
|
if [[ "$FORGEJO_TOKEN" ]]; then
|
|
issue_json="$(curl -sS "https://git.libre-chip.org/api/v1/repos/libre-chip/grant-tracking/issues/$issue_number" \
|
|
-H "Accept: application/json" \
|
|
-H "Authorization: token $FORGEJO_TOKEN")" || error "can't retrieve issue"
|
|
else
|
|
error "FORGEJO_TOKEN is not set, can't check issue (this is expected outside of CI)"
|
|
fi
|
|
issue_title="$(echo "$issue_json" | jq -r '.title')"
|
|
issue_body="$(echo "$issue_json" | jq -r '.body')"
|
|
[[ "$issue_title" =~ ^"NLnet $nlnet_id "(.*)$ ]] || error "title must start with \`NLnet $nlnet_id \`"
|
|
issue_title_tail="${BASH_REMATCH[1]}"
|
|
split_punct=(',' '-' ';' ':')
|
|
matches_split_prefix "$issue_title_tail" "$subtask_title" "${split_punct[@]}" || \
|
|
error "issue title must be \`NLnet $nlnet_id \` followed by the subtask title, or a prefix of the subtask title that ends right before one of:$(printf ' `%s`' "${split_punct[@]}")"
|
|
if [[ "$issue_body" =~ ^"$body_prefix".*"$body_suffix"$ ]]; then
|
|
: # all good
|
|
elif [[ "$issue_body" =~ ^"$body_prefix"(.*)"$example_body_middle"(.*)$ || \
|
|
"$issue_body" =~ ^"$body_prefix"(.*)()$ ]]; then
|
|
example_body_middle="${BASH_REMATCH[1]}$example_body_middle${BASH_REMATCH[2]}"
|
|
error "issue description is missing required suffix"
|
|
elif [[ "$issue_body" =~ ^(.*)"$example_body_middle"(.*)"$body_suffix"$ || \
|
|
"$issue_body" =~ ^(.*)()"$body_suffix"$ ]]; then
|
|
example_body_middle="${BASH_REMATCH[1]}$example_body_middle${BASH_REMATCH[2]}"
|
|
error "issue description is missing required prefix"
|
|
elif [[ "$issue_body" =~ ^(.*)"$example_body_middle"(.*)$ || \
|
|
"$issue_body" =~ ^(.*)()$ ]]; then
|
|
example_body_middle="${BASH_REMATCH[1]}$example_body_middle${BASH_REMATCH[2]}"
|
|
error "issue description is missing both the required prefix and suffix"
|
|
fi
|
|
)
|
|
|
|
function check_progress()
|
|
(
|
|
set -E
|
|
filename="$1"
|
|
mapfile -t lines < "$filename"
|
|
function next_line() {
|
|
eof=$((++line_index >= ${#lines[@]}))
|
|
line="${lines[line_index]}"
|
|
}
|
|
line_index=-1
|
|
function diag() {
|
|
local msg_line_index kind="$1"
|
|
if [[ "$2" =~ ^"-line_index="([0-9]+)$ ]]; then
|
|
((msg_line_index = ${BASH_REMATCH[1]}))
|
|
shift
|
|
else
|
|
((msg_line_index = line_index))
|
|
fi
|
|
shift
|
|
echo "$filename:$((msg_line_index + 1)): $kind $@" >&2
|
|
}
|
|
function error() {
|
|
diag "error:" "$@" >&2
|
|
exit 1
|
|
}
|
|
any_errors=0
|
|
function nonfatal_error() {
|
|
diag "error:" "$@"
|
|
any_errors=1
|
|
}
|
|
trap error ERR
|
|
next_line
|
|
function match_lines() {
|
|
for regex in "$@"; do
|
|
eval '[[ "$line" =~ '"$regex"' ]]' || error "expected $regex"
|
|
next_line
|
|
done
|
|
}
|
|
function skip_until_item() {
|
|
local missing="$1"
|
|
until [[ "$line" =~ ^[[:space:]]*[-#] ]]; do
|
|
if ((eof)); then
|
|
[[ -z "$missing" ]] || error "missing $missing"
|
|
return
|
|
fi
|
|
next_line
|
|
done
|
|
}
|
|
|
|
[[ "$filename" =~ (^|'/')'nlnet-'('template'|2[0-9][0-9][0-9]-[0-9][0-9]-[0-9]+)'/progress.md'$ ]] || error "bad filename"
|
|
nlnet_id="${BASH_REMATCH[2]}"
|
|
[[ "$nlnet_id" != "template" ]] || nlnet_id="YYYY-MM-ID"
|
|
|
|
match_lines \
|
|
"^'<!--'$" \
|
|
"^'SPDX-License-Identifier: LGPL-3.0-or-later'$" \
|
|
"^'See Notices.txt for copyright information'$" \
|
|
"^'-->'$" \
|
|
"^'# NLnet $nlnet_id Grant Progress'$" \
|
|
"^$"
|
|
|
|
project_line_prefix='[Forgejo Project for this grant.](https://git.libre-chip.org/libre-chip/grant-tracking/projects/'
|
|
if [[ "$nlnet_id" != "YYYY-MM-ID" ]]; then
|
|
[[ "$line" =~ ^"$project_line_prefix"[0-9]+')'$ ]] || \
|
|
nonfatal_error "expected: $project_line_prefix<the-project's-id>)"
|
|
else
|
|
[[ "$line" = "${project_line_prefix}ID)" ]] || \
|
|
nonfatal_error "expected: ${project_line_prefix}ID)"
|
|
fi
|
|
|
|
skip_until_item "top level grant header"
|
|
[[ "$line" =~ ^"# € "([0-9]+)" "[^[:space:]] ]] || error "expected: # € <number> <grant name>"
|
|
((grant_amount = ${BASH_REMATCH[1]}))
|
|
((grant_line_index = line_index))
|
|
tasks_total=0
|
|
next_line
|
|
skip_until_item "first task"
|
|
until ((eof)); do
|
|
[[ "$line" =~ ^"## € "([0-9]+)" "[^[:space:]] ]] || error "expected: ## € <number> <task name>"
|
|
((task_amount = ${BASH_REMATCH[1]}))
|
|
((tasks_total += task_amount))
|
|
((task_line_index = line_index))
|
|
next_line
|
|
skip_until_item
|
|
subtasks_total=0
|
|
until ((eof)); do
|
|
subtask_expected="expected: - € <number> [Issue #<N>]($(issue_url "<N>")) <subtask name>"
|
|
[[ "$line" =~ ^([#-]+)" € "([0-9]+)" "[^[:space:]] ]] || error "$subtask_expected"
|
|
[[ "${BASH_REMATCH[1]}" =~ ^[^'#'] ]] || break
|
|
[[ "$line" =~ ^"- € "([0-9]+)" [Issue #"([0-9]+|"N")"](https://git.libre-chip.org/libre-chip/grant-tracking/issues"('/'([0-9]*|"N"))?") "([^[:space:]].*)$ ]] || \
|
|
error "$subtask_expected"
|
|
((subtask_amount = ${BASH_REMATCH[1]}))
|
|
((subtasks_total += subtask_amount))
|
|
issue_number="${BASH_REMATCH[2]}"
|
|
issue_number2="${BASH_REMATCH[4]}"
|
|
subtask_title="${BASH_REMATCH[5]}"
|
|
[[ ( "$issue_number" = "N" && -z "$issue_number2" ) || "$issue_number" = "$issue_number2" ]] || \
|
|
nonfatal_error "issue number in link name must match issue number in url"
|
|
((subtask_line_index = line_index))
|
|
next_line
|
|
skip_until_item
|
|
subtask_body=""
|
|
subtask_body_trailing_lines=""
|
|
# trim leading and trailing blank lines
|
|
for ((i = subtask_line_index + 1; i < line_index; i++)); do
|
|
if [[ "${lines[i]}" =~ ^[[:space:]]*$ ]]; then
|
|
subtask_body_trailing_lines+="${lines[i]}"$'\n'
|
|
elif [[ -z "$subtask_body" ]]; then
|
|
subtask_body+="${lines[i]}"$'\n'
|
|
subtask_body_trailing_lines="" # ignore any leading blank lines
|
|
else
|
|
# add subtask_body_trailing_lines since they're lines in between non-blank lines
|
|
subtask_body+="$subtask_body_trailing_lines${lines[i]}"$'\n'
|
|
subtask_body_trailing_lines=""
|
|
fi
|
|
done
|
|
if [[ "$nlnet_id" != "YYYY-MM-ID" ]]; then
|
|
check_issue "$issue_number2" "$nlnet_id" "$subtask_title" "$subtask_body" || diag "note:" -line_index="$subtask_line_index" "issue is linked from here"
|
|
fi
|
|
done
|
|
(( subtasks_total == task_amount )) || \
|
|
nonfatal_error -line_index="$task_line_index" "task's amount ($task_amount) doesn't match sum of subtasks' amounts ($subtasks_total)"
|
|
done
|
|
(( tasks_total == grant_amount )) || \
|
|
nonfatal_error -line_index="$grant_line_index" "grant's amount ($grant_amount) doesn't match sum of tasks' amounts ($tasks_total)"
|
|
((eof)) || error "extra tokens"
|
|
exit $any_errors
|
|
)
|
|
|
|
any_errors=0
|
|
for nlnet_dir in nlnet-*/; do
|
|
for file in "${nlnet_dir}"*; do
|
|
case "${file##*/}" in
|
|
plan.txt)
|
|
check_plan "$file" || any_errors=1
|
|
;;
|
|
plan-amendment-*.txt)
|
|
check_amendment "$file" || any_errors=1
|
|
;;
|
|
progress.md)
|
|
check_progress "$file" || any_errors=1
|
|
;;
|
|
*)
|
|
echo "unknown file: $file" >&2
|
|
any_errors=1
|
|
esac
|
|
done
|
|
done
|
|
exit $any_errors
|
|
|