Document language for dates; @eval pathname resolution and :cwd (from dev bc7cd62b6f68)
@date/@datetime gain :lang (German: "16. Juni 1910") over a document-wide Language state variable, and :number for the numeric form (en 6/16/1910, de 16.06.1910 per DIN 5008). @eval finds Python modules next to the file that names them regardless of the cwd, and the new :cwd option runs an eval in a chosen working directory (@source_file uses it to resolve against the document). Two new test suites ship in tst/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
65
mac/eval.cpp
65
mac/eval.cpp
@@ -7,6 +7,7 @@
|
||||
#include "katom.h"
|
||||
#include "file.h"
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <unistd.h>
|
||||
|
||||
std::string shell(State state, std::string command, Locator loc)
|
||||
@@ -114,12 +115,64 @@ void check_cpp_arguments(katom_list args, Locator loc)
|
||||
}
|
||||
|
||||
|
||||
// Save the process working directory, change to DIR, and restore on
|
||||
// destruction (exception-safe), so an @eval's :cwd cannot leak into the
|
||||
// rest of the run. NOTE: the cwd is process-global state; if input files
|
||||
// are ever processed in parallel, this needs rethinking.
|
||||
class Cwd_guard
|
||||
{
|
||||
public:
|
||||
explicit Cwd_guard(const std::string& dir)
|
||||
: m_saved(fs::current_path())
|
||||
{
|
||||
fs::current_path(dir);
|
||||
}
|
||||
~Cwd_guard()
|
||||
{
|
||||
std::error_code ec;
|
||||
fs::current_path(m_saved, ec); // never throw from a destructor
|
||||
}
|
||||
Cwd_guard(const Cwd_guard&) = delete;
|
||||
Cwd_guard& operator=(const Cwd_guard&) = delete;
|
||||
private:
|
||||
fs::path m_saved;
|
||||
};
|
||||
|
||||
|
||||
std::string Eval::eval_command(katom_iter begin, katom_iter end)
|
||||
{
|
||||
(void)K::log(3, *begin, *(end - 1));
|
||||
//msg() << "in Eval::eval:\n" << ktype << kall << kindex << std::pair(begin, end) << "\n";
|
||||
katom_iter first = after_whitespace(begin + 1);
|
||||
std::string eval_result = "[unevaluated]";
|
||||
|
||||
// :cwd DIR — run the eval (any mode) with DIR as the working directory,
|
||||
// restoring the process cwd afterwards. The default is the directory
|
||||
// ktext was started in (unchanged behavior). DIR may hold state
|
||||
// substitutions (:cwd *K_input_dir* is the document's directory) and,
|
||||
// per the filenames-with-spaces convention, whitespace-separated tokens
|
||||
// are joined until they name an existing directory.
|
||||
std::optional<Cwd_guard> cwd_guard {};
|
||||
if (first->m_text == ":cwd") {
|
||||
katom_iter tok = after_whitespace(first + 1);
|
||||
std::string dir {};
|
||||
katom_iter cursor = tok;
|
||||
katom_iter resume = tok;
|
||||
while (cursor < end - 1 && !cursor->m_text.starts_with(":")) {
|
||||
dir = m_machine.m_state.subst(as_string(tok, cursor + 1, true));
|
||||
resume = after_whitespace(cursor + 1);
|
||||
if (fs::is_directory(dir)) break;
|
||||
cursor = resume;
|
||||
}
|
||||
if (dir.empty() || !fs::is_directory(dir)) {
|
||||
throw Argument_error(
|
||||
"The :cwd directory does not exist: \"" + dir + "\"",
|
||||
begin->m_loc, false);
|
||||
}
|
||||
cwd_guard.emplace(dir);
|
||||
first = resume;
|
||||
}
|
||||
|
||||
std::string first_word = first->m_text;
|
||||
int offset = first_word[0] == ':' ? 1 : 0;
|
||||
std::string command = as_string(first + offset, end - 1, true);
|
||||
@@ -145,6 +198,18 @@ std::string Eval::eval_command(katom_iter begin, katom_iter end)
|
||||
lib_text = string_replace(lib_text, "*KLAMMERTEXT_HOME*", khome);
|
||||
}
|
||||
fs::path libpath(lib_text + ".so");
|
||||
// A relative library name not found from the cwd is searched in the
|
||||
// directories of the files the Machine has read (same rule as the
|
||||
// Python module path: the library lives next to the file using it).
|
||||
if (libpath.is_relative() && !fs::exists(fs::absolute(libpath))) {
|
||||
for (const auto& dir : m_machine.m_state.m_search_dirs) {
|
||||
fs::path candidate = fs::path(dir) / libpath;
|
||||
if (fs::exists(candidate)) {
|
||||
libpath = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
libpath = fs::absolute(libpath);
|
||||
std::string funcname = args.size() == 4 ? libpath.stem().string() : args[3].m_text;
|
||||
Eval_cpp E_cpp(m_machine, begin->m_loc);
|
||||
|
||||
@@ -269,6 +269,9 @@ katom_list Machine::process(
|
||||
void Machine::read(const fs::path& pathname)
|
||||
{
|
||||
(void)K::log(3, pathname.string());
|
||||
// A file's directory joins the @eval search path (Python modules and
|
||||
// :cpp libraries live next to the file that uses them).
|
||||
m_state.add_search_dir(fs::absolute(pathname).parent_path().string());
|
||||
m_state.open_frame("Machine state: " + pathname.string());
|
||||
std::string text = m_state.subst(trim_right(string_from_file(pathname)));
|
||||
katom_list katoms = process(text, pathname);
|
||||
@@ -328,6 +331,7 @@ void Machine::expand_read_katoms(
|
||||
std::for_each(begin, end, mark_as_replaced);
|
||||
|
||||
input_filename = fs::canonical(input_filename);
|
||||
m_state.add_search_dir(input_filename.parent_path().string());
|
||||
std::string text = trim_right(string_from_file(input_filename.string()));
|
||||
katom_list ks = katomize(line_split(text), input_filename);
|
||||
// ks =
|
||||
|
||||
@@ -259,6 +259,16 @@ std::vector<std::string> State::all_names()
|
||||
}
|
||||
|
||||
|
||||
void State::add_search_dir(const std::string& dir)
|
||||
{
|
||||
if (!dir.empty()
|
||||
&& std::find(m_search_dirs.begin(), m_search_dirs.end(), dir)
|
||||
== m_search_dirs.end()) {
|
||||
m_search_dirs.push_back(dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string State::python_code()
|
||||
{
|
||||
(void)K::log(3);
|
||||
@@ -267,7 +277,12 @@ std::string State::python_code()
|
||||
std::stringstream ss {};
|
||||
|
||||
ss << "import sys\n";
|
||||
for (auto d : sks_dirs()) {
|
||||
strings_t python_dirs = sks_dirs();
|
||||
// The directories of the files this Machine has read: a module next to
|
||||
// the file whose @eval names it is found regardless of the cwd.
|
||||
python_dirs.insert(python_dirs.end(),
|
||||
m_search_dirs.begin(), m_search_dirs.end());
|
||||
for (auto d : python_dirs) {
|
||||
auto python_files = pathnames_with_extension(d, "py");
|
||||
if (!python_files.empty()) {
|
||||
ss << "sys.path.append('" << d << "')\n";
|
||||
|
||||
@@ -77,10 +77,16 @@ public:
|
||||
|
||||
void parse_state_katoms(std::vector<Katom>::iterator begin, std::vector<Katom>::iterator end, katom_list katoms);
|
||||
std::vector<std::string> all_names();
|
||||
void add_search_dir(const std::string& dir);
|
||||
std::string python_code();
|
||||
std::string describe(bool show_environment=false, int margin_size=2) const;
|
||||
|
||||
std::vector<Frame> m_frames {};
|
||||
// Directories of the files the Machine has read (input files, klammer
|
||||
// sets, @read targets), in reading order: @eval finds Python modules
|
||||
// and :cpp libraries next to the file that uses them (see python_code()
|
||||
// and Eval::eval_command).
|
||||
std::vector<std::string> m_search_dirs {};
|
||||
// @@@state Image_search_path :set :append :replace :argtype :desc
|
||||
// Parameter_set m_parameters = Parameter_set("name :set :append :replace :argtype :delim :desc");
|
||||
Parameter_set m_parameters = Parameter_set("name :value :append :replace :delim :desc");
|
||||
|
||||
Reference in New Issue
Block a user