#!/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\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 \ "^''$" \ "^'# 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)" 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: # € " ((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: ## € " ((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: - € [Issue #]($(issue_url "")) " [[ "$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