#include "argv.h" #include "command.h" #include "coverage.h" #include "error.h" #include "file.h" #include "font_store.h" #include "ktype.h" #include "log.h" #include "argtype_registry.h" #include "klammerset_registry.h" #include "character.h" #include "show.h" #include "util.h" static void font_usage() { std::cout << "Font maintenance commands:\n" " --font List the available fonts\n" " --font list The same\n" " --font samples Write a sample page of the available fonts\n" " to /index.html\n" " --font samples \n" " Write a sample page for the (not yet\n" " installed) font files under \n" " --font install Install the font files found under\n" " into the user font directory\n" " --font install \n" " Install into instead\n" " --font help This description\n" "\n" "Fonts are searched in the directories of the KLAMMERTEXT_FONTS\n" "environment variable (colon-separated; default $HOME/.klammertext/fonts)\n" "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 << "Klammerset commands:\n" " --klammerset List the klammersets on the search path\n" " --klammerset list The same\n" " --klammerset help This description\n" "\n" "A klammerset symbol x names the declaration file x/x.k, searched for in:\n" "the current directory (for ktext, the input document's directory), the\n" "KLAMMERTEXT_KLAMMERSETS directories (colon-separated; default\n" "$HOME/.klammertext/klammersets), and $KLAMMERTEXT_HOME. The first hit\n" "wins, so a document-local klammerset shadows an installed one, which\n" "shadows a distributed one.\n"; } static void klammerset_command(const strings_t& words) { std::string verb = words.empty() ? "list" : words[0]; if (verb == "list") { std::cout << boldblack << "Klammersets\n" << black << describe_klammerset_search(fs::current_path().string()); } else if (verb == "help") { klammerset_usage(); } else { std::cout << "Unrecognized klammerset command: --klammerset " << join(words, " ") << "\n\n"; klammerset_usage(); } } static void font_command(const strings_t& words) { std::string verb = words.empty() ? "list" : words[0]; size_t n = words.size() - (words.empty() ? 0 : 1); if (verb == "list" && n == 0) { std::cout << boldblack << "Fonts\n" << black << describe_fonts(); } else if (verb == "samples" && n == 1) { std::cout << "Font samples written to " << write_font_samples(words[1]) << "\n"; } else if (verb == "samples" && n == 2) { std::cout << "Font samples written to " << write_font_samples(words[2], words[1]) << "\n"; } else if (verb == "install" && (n == 1 || n == 2)) { std::cout << "Installing fonts from " << words[1] << ":\n" << install_fonts(words[1], n == 2 ? words[2] : ""); } else if (verb == "help") { font_usage(); } else { std::cout << "Unrecognized font command: --font " << join(words, " ") << "\n\n"; font_usage(); } } int main(int argc, char* argv[]) { try { set_verbose_level(argc, argv); Argv args {}; // 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 , only those whose name or description contains .", "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'"); if (show_usage(argc, argv)) { args.usage(file_basename(argv[0])); exit(1); } auto p = [&](const std::string& name) { return args.get(name) == "true"; }; args.parse(argc, argv); verbose_level = stoi(args.get("v")); if (verbose_level > 0) { args.describe(); } if (p("c")) { show_special_characters(); std::cout << "\n"; } if (p("argtypes")) { Argtype_registry argtypes; std::cout << boldblack << "\nStandard klammer argument types\n" << black; std::cout << argtypes.describe() << "\n"; } 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("rewrite")) { describe_rewrite_patterns(); } // The --font subcommands operate on the Klammertext font store // (infrastructure) and load no klammer set. if (args.given("font")) { 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")) { 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("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 { for (auto fname : input_filenames) { if (is_klammerset_symbol(fname)) { // A bare symbol resolves on the klammerset search // path; kdesc has no input document, so the local // stage is the cwd. fname = resolve_klammerset_symbol( fname, fs::current_path().string(), Locator()).string(); } if (verbose_level > 0) { std::cout << "Read " << fname << "\n"; } M.read(fs::path(absolute_pathname(fname))); } } if (p("t")) { std::cout << boldblack << "Targets\n" << black << M.m_targets.describe(2, true); } // "-k " 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; }