kdesc --coverage <target>: 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 <noreply@anthropic.com>
This commit is contained in:
ackandClaude Fable 5.1 committed 2026-09-26 23:00:57 +02:00
1 parent 39edac9c94
commit d14da90bbf
6 files changed
+144 -7

No files matched your search

+1 -1
View File
@@ -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
+25 -5
View File
@@ -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 <target>\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,23 +342,28 @@ 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") {
for (const std::string& word : words) {
if (word == "help") {
coverage_usage();
return 0;
}
if (verb == "all") {
} else if (word == "all") {
coverage_all = true;
} else if (!verb.empty()) {
} else if (coverage_target.empty()) {
coverage_target = word;
} else {
std::cout << "Unrecognized coverage command: --coverage "
<< join(words, " ") << "\n\n";
coverage_usage();
return 1;
}
}
}
strings_t own_klammers_only {};
if (!input_filenames.empty()) {
@@ -400,9 +411,18 @@ 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")) {
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) {
// Nonzero, as ktext does: a command that prints an error and exits 0
// reports success, and a script cannot tell the difference.
+75
View File
@@ -233,6 +233,23 @@ std::vector<Klammer_coverage> 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 <target>), 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<std::string> names)
{
std::sort(names.begin(), names.end());
for (const auto& n : names) {
os << " @" << n << "\n";
}
}
} // namespace
void report_target_coverage(const std::vector<Klammer_coverage>& coverage,
const std::string& target, bool full, std::ostream& os)
{
std::vector<std::string> 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<int>(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);
}
+6
View File
@@ -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<std::string> m_files {};
std::vector<std::string> 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> klammer_coverage(const Machine& machine,
void report_coverage(const Machine& machine,
const std::vector<Klammer_coverage>& 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<Klammer_coverage>& coverage,
const std::string& target, bool full, std::ostream& os);
+17
View File
@@ -0,0 +1,17 @@
# Fixture for kdesc_test.sh: one target's view (--coverage <target>).
# 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* @@
+20 -1
View File
@@ -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 <target> (2026-09-26): one target's view -- covered, covered by
# an EMPTY definition (a decision), undecided (no definition, nothing decided).
shows "26a. --coverage <target> 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