From d14da90bbf0c3d5c51b703aaee828b9f47b09481 Mon Sep 17 00:00:00 2001 From: Andy Kopra Date: Sat, 26 Sep 2026 23:00:57 +0200 Subject: [PATCH] kdesc --coverage : one target's view (from dev 931ea5490a5d) For the designer building a target: the klammers that cover it, those that do so with an empty definition (a decision that the klammer has no meaning there), and the undecided ones (no definition). One klammer per line, alphabetical; "all" adds the explanations; an unknown word names the missing target. Co-Authored-By: Claude Fable 5.1 --- README.md | 2 +- com/kdesc.cpp | 46 ++++++++++++++++------- mac/coverage.cpp | 75 ++++++++++++++++++++++++++++++++++++++ mac/coverage.h | 6 +++ tst/coverage/target_view.k | 17 +++++++++ tst/kdesc_test.sh | 21 ++++++++++- 6 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 tst/coverage/target_view.k diff --git a/README.md b/README.md index 471bdea..1224e29 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ are regenerated on each release — patches cannot be merged directly. Report problems (or send patches) to the author; accepted changes are applied to the development tree and appear in a following snapshot. -This snapshot was assembled from development commit `c59774b8252d`. +This snapshot was assembled from development commit `931ea5490a5d`. ## License diff --git a/com/kdesc.cpp b/com/kdesc.cpp index bba00f6..639cb24 100644 --- a/com/kdesc.cpp +++ b/com/kdesc.cpp @@ -43,6 +43,12 @@ static void coverage_usage() " --coverage all Also name the file each klammer is written\n" " in, and show every \"needs attention\"\n" " category, including the empty ones\n" + " --coverage \n" + " One target's view: the klammers that cover it,\n" + " those that do so with an EMPTY definition (a\n" + " decision: no meaning there), and the undecided\n" + " ones (no definition: nothing decided). \"all\"\n" + " may be added for the explanations.\n" " --coverage help This description\n" "\n" "Coverage is a fact about a klammer: the targets it can render to. It is\n" @@ -336,21 +342,26 @@ int main(int argc, char* argv[]) // --coverage is validated here, before a klammer set is read, so a // mistyped word or "help" is answered at once. The report itself // needs the loaded registry, so it runs at the end. + // The words are "help", "all", and at most one target name, which + // can only be checked once the klammersets are loaded (below). bool coverage_all = false; + std::string coverage_target {}; if (args.given("coverage")) { strings_t words = option_words(args, "coverage"); - std::string verb = words.empty() ? "" : words[0]; - if (verb == "help") { - coverage_usage(); - return 0; - } - if (verb == "all") { - coverage_all = true; - } else if (!verb.empty()) { - std::cout << "Unrecognized coverage command: --coverage " - << join(words, " ") << "\n\n"; - coverage_usage(); - return 1; + for (const std::string& word : words) { + if (word == "help") { + coverage_usage(); + return 0; + } else if (word == "all") { + coverage_all = true; + } else if (coverage_target.empty()) { + coverage_target = word; + } else { + std::cout << "Unrecognized coverage command: --coverage " + << join(words, " ") << "\n\n"; + coverage_usage(); + return 1; + } } } @@ -400,7 +411,16 @@ int main(int argc, char* argv[]) // Analysis only: klammer_coverage() reads the registry and modifies // nothing, so what a document renders to is unaffected by asking. if (args.given("coverage")) { - report_coverage(M, klammer_coverage(M, own_klammers_only), coverage_all, std::cout); + if (!coverage_target.empty()) { + if (!is_in(coverage_target, M.m_targets.user_defined())) { + std::cout << "No target \"" << coverage_target << "\" is defined. Enter \"kdesc -t\" to see the\n" + << "targets the loaded klammersets define, or \"--coverage help\".\n"; + return 1; + } + report_target_coverage(klammer_coverage(M, own_klammers_only), coverage_target, coverage_all, std::cout); + } else { + report_coverage(M, klammer_coverage(M, own_klammers_only), coverage_all, std::cout); + } } } catch (Error& e) { diff --git a/mac/coverage.cpp b/mac/coverage.cpp index 3b29ae1..f282a19 100644 --- a/mac/coverage.cpp +++ b/mac/coverage.cpp @@ -233,6 +233,23 @@ std::vector klammer_coverage(const Machine& machine, if (!is_in(file, kc.m_files)) { kc.m_files.push_back(file); } + // An empty body (nothing, or whitespace only) is a definition + // that yields no text: the designer's statement that the + // klammer has no meaning in that target. Recorded so a + // target's report can tell it from a gap. + if (def.target != Target_registry::declare_name && + def.target != Target_registry::general_name && + def.target != Target_registry::optionset_name) { + auto body = klammer.m_body.find(def.target); + bool empty = body == klammer.m_body.end(); + if (!empty) { + empty = std::all_of(body->second.begin(), body->second.end(), + [](const Katom& k) { return k.is_whitespace(); }); + } + if (empty && !is_in(def.target, kc.m_empty)) { + kc.m_empty.push_back(def.target); + } + } } for (const auto& [target, loc] : klammer.m_defloc) { if (target != Target_registry::declare_name && @@ -509,3 +526,61 @@ void report_coverage(const Machine& machine, os << "\n"; } } + +// --------------------------------------------------------------------------- +// One target's view (kdesc --coverage ), 2026-09-26. Three lists, and +// the middle one is the point: an EMPTY definition is a decision, not a gap. +// One name per line, alphabetical, as the general report's rows are (Andy: +// consistency, and no better order exists). + +namespace { + +void names_listed(std::ostream& os, std::vector names) +{ + std::sort(names.begin(), names.end()); + for (const auto& n : names) { + os << " @" << n << "\n"; + } +} + +} // namespace + +void report_target_coverage(const std::vector& coverage, + const std::string& target, bool full, std::ostream& os) +{ + std::vector covered {}, empty {}, undecided {}; + for (const auto& kc : coverage) { + if (is_in(target, kc.m_empty)) { + empty.push_back(kc.m_name); + } else if (is_in(target, kc.m_targets)) { + covered.push_back(kc.m_name); + } else { + undecided.push_back(kc.m_name); + } + } + os << "Klammer coverage of target \"" << target << "\"\n" + << std::string(29 + target.size(), '=') << "\n\n" + << coverage.size() << " " << plural("klammer", static_cast(coverage.size())) + << ": " << covered.size() + empty.size() << " covered"; + if (!empty.empty()) { + os << " (" << empty.size() << " by an empty definition)"; + } + os << ", " << undecided.size() << " undecided\n"; + + section(os, "Covered", covered.size(), + full ? " A definition for this target, or a general one that reaches it.\n" : ""); + names_listed(os, covered); + if (!empty.empty() || full) { + section(os, "Covered by an empty definition", empty.size(), + full ? " Defined for this target with a body of nothing, so applying the\n" + " klammer yields no text: the designer's decision that it has no\n" + " meaning here (a presentation hint with no analogue, say). A\n" + " decision, not a gap.\n" : ""); + names_listed(os, empty); + } + section(os, "Undecided", undecided.size(), + full ? " No definition for this target. Nothing has been decided either\n" + " way: this is what a designer of the target has not written yet,\n" + " not what the target excludes.\n" : ""); + names_listed(os, undecided); +} diff --git a/mac/coverage.h b/mac/coverage.h index d4decf2..ca13222 100644 --- a/mac/coverage.h +++ b/mac/coverage.h @@ -89,6 +89,7 @@ struct Klammer_coverage // definitions are spread over several files lists them all. Shown by // "--coverage -v", and only on a row that names ONE klammer. std::vector m_files {}; + std::vector m_empty {}; // targets whose written definition has an empty body }; // Compute the coverage of every klammer the machine has loaded. Analysis @@ -116,3 +117,8 @@ std::vector klammer_coverage(const Machine& machine, void report_coverage(const Machine& machine, const std::vector& coverage, bool full, std::ostream& os); +// One target's view: which klammers cover it, which do so with an empty +// definition (a decision that the klammer has no meaning there), and which +// are undecided (no definition -- nothing decided either way). +void report_target_coverage(const std::vector& coverage, + const std::string& target, bool full, std::ostream& os); diff --git a/tst/coverage/target_view.k b/tst/coverage/target_view.k new file mode 100644 index 0000000..5606829 --- /dev/null +++ b/tst/coverage/target_view.k @@ -0,0 +1,17 @@ +# Fixture for kdesc_test.sh: one target's view (--coverage ). +# Engine tier: it declares its own targets, so the suite runs with no +# klammer set. +@@@target ta | Target A @@@ +@@@target tb | Target B @@@ + +# Covers ta with a body. +@@full.k s : defined for ta @@ +@@full.ta :: A*s* @@ + +# Covers ta with an EMPTY body: a decision that it has no meaning in ta. +@@nothing.k s : defined for ta by an empty body @@ +@@nothing.ta :: @@ + +# Defined for tb only: undecided in ta. +@@elsewhere.k s : defined for tb @@ +@@elsewhere.tb :: B*s* @@ diff --git a/tst/kdesc_test.sh b/tst/kdesc_test.sh index 0662c6a..c36ea88 100755 --- a/tst/kdesc_test.sh +++ b/tst/kdesc_test.sh @@ -163,7 +163,26 @@ echo echo "-- the subcommand words --" shows "24. --coverage help explains" 'Coverage commands' "$KDESC" --coverage help shows "25. --katoms help explains" 'Katom commands' "$KDESC" --katoms help -shows "26. an unknown coverage word" 'Unrecognized coverage command' "$KDESC" --coverage nonsense +# Since 2026-09-26 a word after --coverage is a TARGET name, checked once the +# klammersets are loaded, so an unknown word is answered as a missing target. +shows "26. an unknown coverage word" 'No target "nonsense" is defined' "$KDESC" --coverage nonsense + +# --coverage (2026-09-26): one target's view -- covered, covered by +# an EMPTY definition (a decision), undecided (no definition, nothing decided). +shows "26a. --coverage is that target's view" \ + 'Klammer coverage of target "ta"' "$KDESC" --klammersets none -i "$DIR/target_view.k" --coverage ta +shows "26b. ... counting the empty definition apart" \ + '3 klammers: 2 covered \(1 by an empty definition\), 1 undecided' "$KDESC" --klammersets none -i "$DIR/target_view.k" --coverage ta +shows "26c. ... the empty one under its own heading" \ + '^ @nothing$' "$KDESC" --klammersets none -i "$DIR/target_view.k" --coverage ta +shows "26d. ... the undefined one as undecided" \ + '^ @elsewhere$' "$KDESC" --klammersets none -i "$DIR/target_view.k" --coverage ta +shows "26e. ... \"all\" adds the explanation" \ + 'decision, not a gap' "$KDESC" --klammersets none -i "$DIR/target_view.k" --coverage ta all +shows "26f. a target that is not defined is named" \ + 'No target "tz" is defined' "$KDESC" --klammersets none -i "$DIR/target_view.k" --coverage tz +shows "26g. two target words are one too many" \ + 'Unrecognized coverage command' "$KDESC" --klammersets none -i "$DIR/target_view.k" --coverage ta tb shows "27. an unknown katom word" 'Unrecognized katom command' "$KDESC" --katoms nonsense # The help text and the error must name the flag by its CURRENT spelling: # both still said --font for three weeks after the 2026-08-25 rename, and # retired-ok