Files
klammertext/mac/command.cpp
T
ack fe475e774a Klammertext runs natively on Windows; a Windows source-install guide; the bundled sh for @eval :shell
Assembled from klammertext-dev f344a37d0f6e. The commands, the library,
the Standard Klammer Set, the reflection klammerset and the Python
binding build and run natively on Windows (MSYS2 UCRT64 gcc, python.org
Python), and both test tiers pass there. doc/install/windows_source_install.md
is the fifth install guide: a source install parallel to the Linux and
macOS ones, verified by following it on a pristine Windows 11.

Behaviour on every system: paths Klammertext prints use forward slashes;
output is LF; the command line is UTF-8 on Windows; @eval :shell is a
POSIX shell command everywhere -- on Windows it runs the sh Klammertext
ships, BusyBox for Windows (win/busybox/, GPLv2, with its licence and the
source pointer). KLAMMERTEXT_FONTS and KLAMMERTEXT_KLAMMERSETS are lists in
the system's PATH convention (';' on Windows). On Linux and macOS nothing
observable changes.
2026-09-25 00:32:55 +02:00

207 lines
7.9 KiB
C++

#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include <tuple>
#include "argv.h"
#include "file.h"
#include "log.h"
#include "machine.h"
using namespace std::string_literals;
fs::path construct_command_pathname(char* command)
{
// The pseudo source file for command-line string input (-s, kdiag's
// input): a file IN THE CWD named after the command, so that a relative
// @read in string input resolves against the directory the user ran the
// command from -- the string-input analog of "a document's relative
// names resolve against the document". The basename matters: the
// former "<cwd>/<argv[0]>" string concatenation anchored resolution at
// the BINARY's directory whenever argv[0] was absolute
// (/usr/local/bin/ktext, a wrapper script), so every relative @read in
// -s input searched bin/ instead of the cwd.
return fs::current_path() / fs::path(command).filename();
}
void set_command_identity(const std::string& name, const std::string& pathname)
{
command_name = name;
command_pathname = pathname;
}
void set_verbose_level(int argc, char* argv[])
{
#ifdef _WIN32
// Klammertext writes LF on every system: a document a Windows user pipes
// or redirects must not gain CR, and the suites read the streams byte for
// byte. The C runtime's text mode would append it to every line.
_setmode(_fileno(stdout), _O_BINARY);
_setmode(_fileno(stderr), _O_BINARY);
#endif
//command_name = absolute_pathname(argv[0]);
command_name = fs::path(argv[0]).generic_string();
command_pathname = construct_command_pathname(argv[0]).generic_string();
//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};
}
void set_command_state(
Machine& machine, const std::string& frame_name, const std::string& target,
const std::vector<std::string>& input_filenames, const std::string& output_dir,
const std::string& output_basename, bool display_only)
{
State& S = machine.m_state;
S.open_frame(frame_name);
S.set("K_target", target);
S.set("K_output_dir", output_dir);
S.set("K_output_basename", output_basename);
S.set("K_stdout_only", display_only ? "true" : "false");
// The list form uses the standalone-"/" separator (filenames may
// contain spaces); K_input_filename is the single root input file.
S.set("K_input_filenames", join(input_filenames, " / "));
S.set("K_input_filename", input_filenames.empty() ? "" : input_filenames[0]);
S.set("K_verbose_level", std::to_string(verbose_level));
if (!input_filenames.empty()) {
S.set("K_input_dir", absolute_pathname(file_directory(input_filenames[0])));
} else {
S.set("K_input_dir", fs::current_path().generic_string());
}
}
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::none());
}
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::none()).generic_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));
}
}
}