Target coverage: a klammer states the targets it serves

kdesc gains --coverage, which reports for every klammer the set of targets it
can render to, and — the point of it — which klammers' coverage cannot be
derived and must therefore be declared.  Three rules: coverage is DERIVED
where the definitions determine it (a general body of klammer calls covers
the intersection of what those klammers cover, by a greatest fixpoint after
loading), DECLARED where the engine cannot interpret what decides it (an
@eval body, whose targets are undecidable), and UNKNOWN where nothing is
written — which never means "deliberately unavailable".

Two new spellings in a definition's name.  A comma-separated target list,
"@@table.html,tex :: ...", gives one body several targets; it is surface
syntax, expanded at registration, and each member goes through the
redefinition rules on its own.  And "@@date.* :: ..." writes the general
target out, asserting that the klammer works for EVERY target including ones
not yet defined — a stronger claim than a list of the targets defined today,
and the one target declaration that could be mechanically falsified.

The Standard Klammer Set was swept accordingly: it now has no general
definitions at all, every klammer names the targets it serves, six use ".*",
and tex and pdf are at zero undecided.

kdesc's flags are reorganised on two rules: a flag reached for often gets a
single letter (-k klammers, -t targets, -c characters, -i input), a more
specialised topic a multi-letter name (--argtypes, --katoms, --rewrite,
--optionsets, --coverage, --klammerset, --font); and -v says how much to show
about PROCESSING, never what the RESULT contains — so the katom regex column
is "--katoms full" and the coverage detail "--coverage all".  NOTE: "-k" now
lists klammers (optionally filtered by a name/description search); the katom
table moved to "--katoms".

Fixes carried along: an option written with no value crashed the command with
SIGSEGV instead of reporting the mistake; two required positional arguments
never parsed; kdesc and kdiag printed an error and exited 0; and definition
diagnostics counted registrations rather than what was written, so one line
could be reported as two definitions and then printed twice.

Four new test suites: target_list, coverage, command_option, kdesc.

(from dev 46f54080bd9a)
This commit is contained in:
2026-08-12 17:20:23 +02:00
parent 61987c8b1f
commit 59c1599bc9
49 changed files with 1960 additions and 431 deletions

View File

@@ -1,5 +1,6 @@
#include "argv.h"
#include "command.h"
#include "coverage.h"
#include "error.h"
#include "file.h"
#include "font_store.h"
@@ -33,6 +34,50 @@ static void font_usage()
"and then in the default font set.\n";
}
static void coverage_usage()
{
std::cout <<
"Coverage commands:\n"
" --coverage Report the targets each klammer covers\n"
" --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 help This description\n"
"\n"
"Coverage is a fact about a klammer: the targets it can render to. It is\n"
"DERIVED where the definitions determine it (a general body of klammer\n"
"calls covers the intersection of what those klammers cover), must be\n"
"DECLARED where they cannot (an @eval body, whose targets are named in a\n"
"comma-separated list: @@name.html,tex :: ... @@), and is UNKNOWN where\n"
"nothing is written -- which never means \"deliberately unavailable\".\n"
"\n"
"Without \"all\", an AUTHOR sees what is available and only the problems\n"
"that exist. With it, a DESIGNER building a klammer set is reminded of\n"
"the whole set of categories.\n";
}
static void katom_usage()
{
std::cout <<
"Katom commands:\n"
" --katoms List the katom types\n"
" --katoms full The same, with the regex each type is\n"
" matched by\n"
" --katoms help This description\n";
}
// The words of a variadic option, with the empty ones dropped.
static strings_t option_words(Argv& args, const std::string& name)
{
strings_t words {};
for (const std::string& w : args.as_vector(name)) {
if (!w.empty()) {
words.push_back(w);
}
}
return words;
}
static void klammerset_usage()
{
std::cout <<
@@ -94,15 +139,23 @@ int main(int argc, char* argv[])
set_verbose_level(argc, argv);
Argv args {};
args.flag("c", "Special characters");
args.flag("a", "Argument types");
args.flag("k", "Katom types");
args.flag("r", "Katom rewrite patterns");
args.opt("input", "Input filename", "filename", "", "'text'");
args.flag("targets", "Show targets defined by the input file");
args.flag("klammers", "Show klammers defined by the input file");
args.flag("optionsets", "Show option sets declared by the input file");
// Declaration order is display order in the usage text, so these are
// ordered by how likely a user is to want them. A single letter is a
// flag one reaches for often; a multi-letter name is a more specialised
// topic. -v stays last: it says how much to show about the command's
// PROCESSING, never what its RESULT contains -- which is why the katom
// regexes and the coverage file column are words of their own options
// rather than verbosity levels.
args.var("k", "Show klammers. With <text>, only those whose name or description contains <text>.", "text");
args.flag("t", "Show the targets for Klammertext output");
args.flag("c", "Show the codes for accented vowels and other special characters");
args.opt("i", "Input filename. If given, it is used instead of the SKS.", "filename", "", "'text'");
args.var("font", "List installed fonts. Enter \"--font help\" for font maintenance commands.");
args.flag("argtypes", "Show the klammer argument types");
args.var("katoms", "Show the katom types. Enter \"--katoms help\" for details.");
args.flag("rewrite", "Show the katom rewrite patterns");
args.flag("optionsets", "Show the option sets declared by the input");
args.var("coverage", "Show the targets each klammer covers. Enter \"--coverage help\" for details.");
args.var("klammerset", "List the klammersets on the search path. Enter \"--klammerset help\" for details.");
args.opt("v", "'verbosity'", "n", "0", "'verbosity'");
@@ -123,50 +176,72 @@ int main(int argc, char* argv[])
std::cout << "\n";
}
if (p("a")) {
if (p("argtypes")) {
Argtype_registry argtypes;
std::cout << boldblack << "\nStandard klammer argument types\n" << black;
std::cout << argtypes.describe() << "\n";
}
if (p("k")) {
describe_katoms(verbose_level > 2);
if (args.given("katoms")) {
strings_t words = option_words(args, "katoms");
std::string verb = words.empty() ? "" : words[0];
if (verb == "help") {
katom_usage();
return 0;
}
if (!verb.empty() && verb != "full") {
std::cout << "Unrecognized katom command: --katoms " << join(words, " ") << "\n\n";
katom_usage();
return 1;
}
describe_katoms(verb == "full");
}
if (p("r")) {
if (p("rewrite")) {
describe_rewrite_patterns();
}
// The --font subcommands operate on the Klammertext font store
// (infrastructure) and load no klammer set.
if (args.given("font")) {
strings_t words {};
for (const std::string& w : args.as_vector("font")) {
if (!w.empty()) {
words.push_back(w);
}
}
font_command(words);
font_command(option_words(args, "font"));
return 0;
}
// The --klammerset subcommands operate on the search path
// (filesystem enumeration) and load no klammer set.
if (args.given("klammerset")) {
strings_t words {};
for (const std::string& w : args.as_vector("klammerset")) {
if (!w.empty()) {
words.push_back(w);
}
}
klammerset_command(words);
klammerset_command(option_words(args, "klammerset"));
return 0;
}
// --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.
bool coverage_all = false;
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;
}
}
Machine M;
strings_t input_filenames = resolve_filename_list(args.get("input"));
std::cout << "input_filenames: " << input_filenames << "\n";
strings_t input_filenames = resolve_filename_list(args.get("i"));
if (verbose_level > 0) {
std::cout << "input_filenames: " << input_filenames << "\n";
}
if (input_filenames.empty()) {
M.read(fs::path(M.m_state.value("KLAMMERTEXT_HOME") + "/sks/sks.k"));
} else {
@@ -178,25 +253,48 @@ int main(int argc, char* argv[])
fname = resolve_klammerset_symbol(
fname, fs::current_path().string(), Locator()).string();
}
std::cout << "Read " << fname << "\n";
if (verbose_level > 0) {
std::cout << "Read " << fname << "\n";
}
M.read(fs::path(absolute_pathname(fname)));
}
}
if (p("targets")) {
if (p("t")) {
std::cout << boldblack << "Targets\n" << black << M.m_targets.describe(2, true);
}
if (p("klammers")) {
std::cout << boldblack << "Klammers\n" << black << M.m_klammers.describe(2);
// "-k <text>" searches names AND descriptions, case-insensitively,
// with whitespace collapsed on both sides. An empty listing for a
// search that was actually made is reported: silence would read as a
// broken command. It is not an error -- finding nothing is a result.
if (args.given("k")) {
std::string search = join(option_words(args, "k"), " ");
std::string listing = M.m_klammers.describe(2, search);
if (listing.empty() && !search.empty()) {
std::cout << "No klammer names or descriptions contained "
<< q_(collapse_whitespace(search)) << ".\n";
} else {
std::cout << boldblack << "Klammers\n" << black << listing;
}
}
if (p("optionsets")) {
std::cout << boldblack << "Option sets\n" << black << M.m_option_sets.describe(2);
}
// 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), 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.
e.print_message();
return 1;
}
return 0;
}

View File

@@ -111,6 +111,10 @@ int main(int argc, char* argv[])
if (e.m_type == "target")
advice = "To include the Standard Klammer Set, add flag \"--sks\".";
e.print_message(advice);
// Nonzero, as ktext does: a command that prints an error and exits 0
// reports success, and a script cannot tell the difference.
std::cout << black;
return 1;
}
std::cout << black;
return 0;