SeekquarrySeekquarry - Yioop Repo - Seekquarry

/ devlog / devscripts / check_devlog.sh⤓
#!/bin/sh

# The arc's own sections are Chris's to set. A new one added by this
# session was refused work he never asked for, and a fix found while
# implementing an item is part of that item rather than a section or an
# entry of its own. So a patch may not add a top-level entry to the
# plan: the number of them has to be what gold already holds.
top_level_count()
{
    awk '/^<li>/ {count++} END {print count + 0}' "$1"
}
check_top_level()
{
    plan="$1"
    gold_plan="$GOLD/$plan"
    if [ ! -f "$gold_plan" ]; then
        return 0
    fi
    here=`top_level_count "$plan"`
    there=`top_level_count "$gold_plan"`
    allowed=${SANCTIONED_ENTRIES:-0}
    if [ "$here" -gt "`expr $there + $allowed`" ]; then
        echo "check_devlog: FAIL the plan grew by `expr $here - $there`"
        echo "  entry(s) written at its outer edge. Those are Chris's to"
        echo "  add: work belongs under the item it is part of, and a"
        echo "  fix found while doing an item is part of that item."
        return 1
    fi
    return 0
}
# Refuses a plan that carries a checkmark this session put there, or a
# typed outline label, or unbalanced list tags. Only Chris sets a
# checkmark; a plan file is compared against gold to find any that were
# added since.
#
# Where Chris has asked in so many words for entries to be added at the
# outer edge, run this with SANCTIONED_ENTRIES set to how many he asked
# for, and that many are allowed. Any beyond that are still refused, so
# the number has to be written down and shown in the round's output.
#
# Where Chris has said in so many words which items to check, run this
# with CONFIRMED_CHECKS set to how many he asked for, and that many are
# allowed. Any beyond that number are still refused, so the count has to
# be written down and shown in the round's output rather than the guard
# being stepped around.
GOLD=${GOLD:-/home/claude/gold}
CONFIRMED_CHECKS=${CONFIRMED_CHECKS:-0}
plan="$1"
# With no plan named, take the newest dated arc folder that holds one.
# Naming one arc here meant the check went on reading that arc long
# after the work had moved to the next, so it passed a plan nobody was
# writing in.
if [ -z "$plan" ]; then
    plan=`ls -d devlog/v10/*/plan.html 2>/dev/null | sort | tail -1`
fi
if [ -z "$plan" ] || [ ! -f "$plan" ]; then
    echo "check_devlog: FAIL no plan to read"
    exit 1
fi
fail=0
# A checkmark is written with either class the plans use, so both are
# counted. Counting one of them let eighteen checkmarks through unseen.
marks='class="\(check\|done\)">&#10003;'
here=`grep -c "$marks" "$plan"`
there=`git -C "$GOLD" show "confirmed-head:$plan" 2>/dev/null | \
    grep -c "$marks"`
if [ "$there" = "" ]; then
    there=0
fi
added=$((here - there))
if [ "$added" -gt "$CONFIRMED_CHECKS" ]; then
    echo "check_devlog: FAIL added $added checkmark(s), $CONFIRMED_CHECKS"
    echo "  confirmed; only Chris sets those. Use the waiting marker."
    fail=1
elif [ "$added" -gt 0 ]; then
    echo "check_devlog: $added checkmark(s) added, which Chris asked for"
fi
opens=`grep -o '<li' "$plan" | wc -l`
closes=`grep -o '</li>' "$plan" | wc -l`
if [ "$opens" != "$closes" ]; then
    echo "check_devlog: FAIL $opens <li> against $closes </li>"
    fail=1
fi
opens=`grep -o '<ol' "$plan" | wc -l`
closes=`grep -o '</ol>' "$plan" | wc -l`
if [ "$opens" != "$closes" ]; then
    echo "check_devlog: FAIL $opens <ol> against $closes </ol>"
    fail=1
fi
typed=`grep -cE '<li[^>]*>[[:space:]]*(<[^>]+>)*[[:space:]]*([IVX]+|[A-Z]|[0-9]+)\.' "$plan"`
if [ "$typed" -gt 0 ]; then
    echo "check_devlog: FAIL $typed typed outline label(s)"
    fail=1
fi
# An item marked implemented or done stands for all of its parts, so
# each part carries a mark of its own and none is still planned or being
# worked on. Plans had items marked done over parts with no mark, and
# parts of a waiting item left bare. The plan is read as a stack of
# list items; a part's mark is the first thing after its <li>.
bare_parts=`awk '
    { text = text " " $0 }
    END {
        n = split(text, pieces, "<")
        depth = 0
        pending = 0
        bad = 0
        for (i = 2; i <= n; i++) {
            piece = pieces[i]
            if (piece ~ /^li[ >]/) {
                depth++
                rest = piece
                sub(/^[^>]*>/, "", rest)
                pending = 1
                mark[depth] = "none"
                if (rest ~ /[^ \t]/) {
                    pending = 0
                }
            } else if (piece ~ /^\/li>/) {
                if (depth > 1 && mark[depth - 1] ~ /review|check/ && \
                    mark[depth] !~ /review|check/) {
                    bad++
                }
                depth--
                pending = 0
            } else if (pending) {
                if (piece ~ /^span class="(review|check)"/) {
                    mark[depth] = piece
                    sub(/^span class="/, "", mark[depth])
                    sub(/".*/, "", mark[depth])
                }
                pending = 0
            }
        }
        print bad
    }
' "$plan"`
if [ "$bare_parts" -gt 0 ]; then
    echo "check_devlog: FAIL $bare_parts part(s) of an implemented or done"
    echo "  item carry no implemented or done mark of their own."
    fail=1
fi
if ! check_top_level "$plan"; then
    fail=1
fi
# The plan rule says no sentence in an entry runs past about twenty
# words, and that rule was the one with no check behind it: a round
# wrote an entry of nine long sentences and nothing caught it. The plan
# is append-only and holds older entries written before this check, so
# only the sentences this patch adds are read, found against the copies
# gold holds. Any added sentence past the limit is named.
# A title in <strong> is dropped first, since a full-sentence title does
# not count toward the sentence rule, and a few words of slack are
# allowed over twenty, since the rule says "about".
WORD_LIMIT=${DEVLOG_WORD_LIMIT:-24}
# Sentences are compared rather than lines, so a list item moved under
# the item it fits, within a plan or from another, or given a marker,
# is not read again as though written this round. Each plan's prose is
# joined, titles and tags dropped, and split on periods; a sentence of
# this plan that no plan in gold holds is new.
sentences()
{
    awk '
        { text = text " " $0 }
        END {
            gsub(/<strong>[^<]*<\/strong>/, "", text)
            gsub(/<[^>]*>/, " ", text)
            gsub(/&#[0-9]+;/, "", text)
            gsub(/&[a-z]+;/, "", text)
            n = split(text, parts, /[.!?]/)
            for (i = 1; i <= n; i++) {
                sentence = parts[i]
                gsub(/[ \t]+/, " ", sentence)
                sub(/^ /, "", sentence)
                sub(/ $/, "", sentence)
                if (sentence != "") {
                    print sentence
                }
            }
        }
    '
}
gold_sentences=`mktemp`
git -C "$GOLD" ls-tree -r --name-only confirmed-head devlog 2>/dev/null | \
    grep '/plan.html$' | while IFS= read -r gold_plan; do
    git -C "$GOLD" show "confirmed-head:$gold_plan" 2>/dev/null | sentences
done > "$gold_sentences"
long_sentences=`sentences < "$plan" | grep -vxF -f "$gold_sentences" | \
    awk -v limit="$WORD_LIMIT" '{ if (NF > limit) { print NF } }'`
rm -f "$gold_sentences"
if [ -n "$long_sentences" ]; then
    worst=`echo "$long_sentences" | sort -rn | head -1`
    many=`echo "$long_sentences" | wc -l | tr -d ' '`
    echo "check_devlog: FAIL $many added sentence(s) past $WORD_LIMIT words,"
    echo "  the longest $worst. Each entry sentence stays near twenty"
    echo "  words; split the long ones or move detail to a nested part."
    fail=1
fi
if [ "$fail" = "0" ]; then
    echo "check_devlog: PASS $plan"
fi
exit $fail