Files
klammertext/mac/command.cpp

158 lines
5.7 KiB
C++
Raw Permalink Normal View History

#include <tuple>
#include "argv.h"
#include "file.h"
#include "log.h"
An output policy for the three commands, and @cond as a true special form A snapshot of the development tree. The substantial changes since the last one: COMMAND OUTPUT POLICY. The three commands display text in exactly three cases, and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging used to share the stream and land inside the document. A bare command prints its usage and succeeds rather than failing. Colour is emitted only to a terminal, per stream, and NO_COLOR is honoured. "-v 1" reports every decision whose outcome you could not have read off your own input: the klammerset that was loaded and from which file, a font's directory, a ":files" name's file, how "-o" was expanded. Higher levels are the trace. The commands no longer warn and continue: an anomaly is an error, described with its location. Two exceptions remain, each for a stated reason -- a condition that is expected and temporary by design, and a judgment that is a heuristic rather than exact. @cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when the file is read. Two consequences for a writer: * a state variable reaches the predicate. "@@@state Flag :value true @@@ @cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and silently take the false branch. The document now behaves like a klammer body, whose arguments are bound before its conditionals are decided. * nothing in a discarded branch happens -- it is not read, not evaluated, not expanded. An @eval in the branch not taken used to run anyway. Its predicate relation is total and strict: true, True, 1; false, False, 0, and empty; anything else is an error at the @cond rather than silently false. @eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard error out of the document (it appears under "-v 1") and treat a nonzero exit as an error naming what the command reported. A command that exits nonzero on purpose -- "grep" finding no match -- says so with "|| true". KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the order given, sharing one namespace, with the definition modes deciding collisions. "none" means none and may not be combined with other symbols. A klammerset with symbol X is declared in a file X/X.k, which is what lets two sets require the same third set without loading it twice. TESTS. Four new suites: the kdiag command's interface, the @eval primitive's contract with the outside world, and verbosity at both tiers. Three suites that could not run on macOS at all now do. Assembled from dev commit 6c8ee6c22fca.
2026-08-16 01:37:59 +02:00
#include "machine.h"
using namespace std::string_literals;
fs::path construct_command_pathname(char* command)
{
return fs::path(fs::current_path().string() + "/" + std::string(command));
}
void set_verbose_level(int argc, char* argv[])
{
//command_name = absolute_pathname(argv[0]);
command_name = std::string(argv[0]);
command_pathname = construct_command_pathname(argv[0]);
//std::cout << "set_verbose_level: " << command_pathname << "\n";
Argv args {};
args.opt("v", "'verbosity'", "level", "0", "'verbosity'");
args.parse(argc, argv, false);
verbose_level = args.as_verbosity("v");
}
bool show_usage(int argc, char* argv[])
{
return argc == 1 || (argc == 3 && std::string(argv[1]) == "-v"s);
}
std::string construct_output_filename(
const std::string& output_dir, const std::string& output_basename, const std::string& target)
{
std::string result {};
if (target == "html") {
result = output_dir + "/" + output_basename + "/index.html";
} else {
result = output_dir + "/" + output_basename + "." + target;
}
return result;
}
bool only_definitions(std::vector<std::string> filenames)
{
if (filenames.empty()) return false;
bool result = true;
for (auto f : filenames) {
if (fs::path(f).extension() != ".k") {
result = false;
break;
}
}
return result;
}
std::tuple<std::string, std::string, std::string, std::string, bool, bool>
parse_args(
const std::vector<std::string>& input_filenames, std::string target, std::string output_basename, bool display_only)
{
// output_dir
std::string output_target = target;
std::string output_dir = "";
std::string ext = extension(output_basename);
// std::string output_filename = "";
bool write_files = true;
if (target.empty() && output_basename.empty()) {
output_target = "any";
}
if (output_basename == "-") {
write_files = false;
} else if (!output_basename.empty()) {
output_dir = file_directory(output_basename);
output_basename = file_basename(output_basename);
} else if (!input_filenames.empty()) {
// No -o: output goes next to the input file ("" -- an input with no
// directory component -- resolves to cwd below).
output_dir = file_directory(input_filenames[0]);
output_basename = file_basename(input_filenames[0]);
}
output_dir = absolute_pathname(output_dir);
if (output_target.empty()) {
target = ext;
}
std::string output_filename =
construct_output_filename(output_dir, output_basename, target);
if (output_basename.empty() && input_filenames.empty()) {
display_only = true;
} else if (only_definitions(input_filenames)) {
display_only = true;
}
std::vector<std::pair<std::string,std::string>> vars = {
{"Output target", output_target},
{"Output directory", output_dir},
{"Output basename", output_basename},
{"Output filename", output_filename},
{"Write file", write_files ? "true" : "false"}};
int verbose = 1;
for (auto [label, value] : vars) {
if (label == "Output filename") {
//verbose = 2;
}
(void)K::log(int(verbose), label + ":", value);
}
return {output_target, output_dir, output_basename, output_filename, write_files, display_only};
}
An output policy for the three commands, and @cond as a true special form A snapshot of the development tree. The substantial changes since the last one: COMMAND OUTPUT POLICY. The three commands display text in exactly three cases, and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging used to share the stream and land inside the document. A bare command prints its usage and succeeds rather than failing. Colour is emitted only to a terminal, per stream, and NO_COLOR is honoured. "-v 1" reports every decision whose outcome you could not have read off your own input: the klammerset that was loaded and from which file, a font's directory, a ":files" name's file, how "-o" was expanded. Higher levels are the trace. The commands no longer warn and continue: an anomaly is an error, described with its location. Two exceptions remain, each for a stated reason -- a condition that is expected and temporary by design, and a judgment that is a heuristic rather than exact. @cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when the file is read. Two consequences for a writer: * a state variable reaches the predicate. "@@@state Flag :value true @@@ @cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and silently take the false branch. The document now behaves like a klammer body, whose arguments are bound before its conditionals are decided. * nothing in a discarded branch happens -- it is not read, not evaluated, not expanded. An @eval in the branch not taken used to run anyway. Its predicate relation is total and strict: true, True, 1; false, False, 0, and empty; anything else is an error at the @cond rather than silently false. @eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard error out of the document (it appears under "-v 1") and treat a nonzero exit as an error naming what the command reported. A command that exits nonzero on purpose -- "grep" finding no match -- says so with "|| true". KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the order given, sharing one namespace, with the definition modes deciding collisions. "none" means none and may not be combined with other symbols. A klammerset with symbol X is declared in a file X/X.k, which is what lets two sets require the same third set without loading it twice. TESTS. Four new suites: the kdiag command's interface, the @eval primitive's contract with the outside world, and verbosity at both tiers. Three suites that could not run on macOS at all now do. Assembled from dev commit 6c8ee6c22fca.
2026-08-16 01:37:59 +02:00
void load_klammersets(Machine& machine, const strings_t& symbols)
{
// "none" is the absence of a klammerset, not the name of one, so it says
// nothing about what should be loaded ALONGSIDE it: written first it would
// silently discard the rest of the list, written last it would be looked up
// as a symbol and reported missing. Both readings are wrong, so neither is
// guessed at -- "none" is accepted only alone.
if (is_in("none"s, symbols) && symbols.size() > 1) {
throw Klammerset_error(
"The klammerset \"none\" cannot be combined with other klammersets: "
"it means that none is loaded. Give \"none\" alone, or name only the "
"klammersets to load.",
Locator());
}
if (symbols.empty()) {
// Which klammerset was loaded, and from where, is a DERIVED value: the
// user wrote no --klammersets at all and got the SKS from
// $KLAMMERTEXT_HOME. Only the symbol branch below reported its
// resolution, so the commonest case -- the default -- said nothing.
std::string sks = machine.m_state.value("KLAMMERTEXT_HOME") + "/sks/sks.k";
(void)K::log(1, "Klammerset (default): " + sks);
machine.read(fs::path(sks));
} else if (symbols[0] == "none") {
(void)K::log(1, "Klammersets: none");
} else {
// The whitespace-delimited words in symbols must be klammerset symbols:
for (const auto& symbol : symbols) {
// Same guard as ":requires" (Machine::load_klammerset_files): a set
// named twice on the command line is loaded once.
if (machine.m_klammersets.has(symbol)) {
(void)K::log(1, "Klammerset \"" + symbol + "\": already loaded, skipped");
continue;
}
std::string klammerset_filename = resolve_klammerset_symbol(
symbol, machine.m_state.value("K_input_dir"), Locator()).string();
// Symbol -> file is the search path's answer, and the search
// path has three stages with shadowing: the file it landed on is
// exactly what a user cannot read off "--klammersets x".
(void)K::log(1, "Klammerset \"" + symbol + "\": " + klammerset_filename);
machine.read(fs::path(klammerset_filename));
}
}
}