Filenames with spaces; output beside the input file; warning fixes (from dev c08eb1bbfa4c)
- Filenames may contain spaces: quote on the command line; filename lists use a standalone "/" separator; unseparated names that do not exist are rejoined into names that do (announced); leading ~ expands. - Without -o, output is written to the input file's directory; -o fully specifies the output directory and basename. - "Word not parsed" warnings now appear without -v, only for text that survives removal, with correct line numbers. - New tst/filename_test.sh regression suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,12 @@ std::string tex_to_pdf(Machine& machine)
|
||||
for (auto ext : {"aux", "log", "out", "toc"}) {
|
||||
fs::remove(outbase + "." + ext);
|
||||
}
|
||||
std::string command = "xelatex -interaction=batchmode -halt-on-error " + tex_filename;
|
||||
// xelatex writes .pdf/.log/.aux/.toc into its cwd unless told otherwise;
|
||||
// K_output_dir need not be cwd (it follows the input file, or -o).
|
||||
// The embedded paths are quoted defensively: they derive from user
|
||||
// filenames, which may contain spaces.
|
||||
std::string command = "xelatex -interaction=batchmode -halt-on-error -output-directory=\""
|
||||
+ machine.m_state.value("K_output_dir") + "\" \"" + tex_filename + "\"";
|
||||
string_to_file(tex_filename, machine.m_result);
|
||||
std::string xelatex_output = exec(command.c_str());
|
||||
std::string xelatex_log = string_from_file(outbase + ".log");
|
||||
|
||||
@@ -47,10 +47,14 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine)
|
||||
m_logo = get("logo");
|
||||
|
||||
m_text = get("text");
|
||||
m_files = word_split(get("files"));
|
||||
// Filename lists: standalone-"/" separation, existence rescue for
|
||||
// spaces, ~ expansion (resolve_filename_list in mac/file.cpp); the
|
||||
// existence checks resolve relative names against the input directory,
|
||||
// as parse_input_filename() will.
|
||||
m_files = resolve_filename_list(get("files"), get("K_input_dir"));
|
||||
|
||||
m_css_text = get("css_text");
|
||||
m_css_filenames = word_split(get("css_files"));
|
||||
m_css_filenames = resolve_filename_list(get("css_files"), get("K_input_dir"));
|
||||
m_include_sks_css = strbool(get("include_sks_css"), loc);
|
||||
frame_background_color = get("frame_background_color");
|
||||
frame_text_color = get("frame_text_color");
|
||||
@@ -58,7 +62,7 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine)
|
||||
nav_text_color = get("nav_text_color");
|
||||
|
||||
js_text = get("js_text");
|
||||
m_js_filenames = word_split(get("js_files"));
|
||||
m_js_filenames = resolve_filename_list(get("js_files"), get("K_input_dir"));
|
||||
m_include_sks_js = strbool(get("include_sks_js"), loc);
|
||||
|
||||
// font_dirs = word_split(get("font_dirs"));
|
||||
@@ -99,7 +103,7 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine)
|
||||
//use_pages_dir = strbool(get("use_pages_dir"), loc);
|
||||
|
||||
// m_toc_only = strbool(get("K_toc_only"), loc);
|
||||
m_kt_root_filename = get("K_input_filenames");
|
||||
m_kt_root_filename = get("K_input_filename");
|
||||
|
||||
m_no_cache = !strbool(get("cache"), loc);
|
||||
|
||||
@@ -138,8 +142,20 @@ fs::path parse_input_filename(std::string s, std::string input_dir)
|
||||
if (p.extension() != ".kt") {
|
||||
p += ".kt";
|
||||
}
|
||||
if (!file_exists(p)) {
|
||||
p = input_dir + "/kt/" + p.string();
|
||||
if (p.is_absolute()) {
|
||||
return p;
|
||||
}
|
||||
// A relative :files name resolves against the input file's directory
|
||||
// (K_input_dir), so a document renders identically wherever ktext is
|
||||
// run from; then the legacy kt/ subdirectory; a name found in neither
|
||||
// is returned as given (cwd-relative) and errors downstream.
|
||||
fs::path in_input_dir = fs::path(input_dir) / p;
|
||||
if (file_exists(in_input_dir.string())) {
|
||||
return in_input_dir;
|
||||
}
|
||||
fs::path in_kt_dir = fs::path(input_dir) / "kt" / p;
|
||||
if (file_exists(in_kt_dir.string())) {
|
||||
return in_kt_dir;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class Image(klammer_base.Klammer_base):
|
||||
self.basename = klammer_base.unescape_ktesc(self.basename)
|
||||
self.source, self.pwidth, self.pheight, self.file_error = self.cache.get(self.K_target, self.basename)
|
||||
if self.file_error:
|
||||
self.file_error_message = f'\nERROR: File "{self.K_input_filenames}" not found'
|
||||
self.file_error_message = f'\nERROR: File "{self.K_input_filename}" not found'
|
||||
self.as_string = as_string
|
||||
if width:
|
||||
self.width = width
|
||||
@@ -42,7 +42,7 @@ class Image(klammer_base.Klammer_base):
|
||||
self.rel_fraction = self.pwidth / self.rel_pwidth
|
||||
|
||||
if self.file_error:
|
||||
self.file_error_message = f'\nERROR: File "{self.K_input_filenames}" not found'
|
||||
self.file_error_message = f'\nERROR: File "{self.K_input_filename}" not found'
|
||||
|
||||
def html(self):
|
||||
img_dir = f"{self.K_output_dir}/{self.K_output_basename}/{self.Image_output_dir}"
|
||||
|
||||
@@ -22,7 +22,7 @@ class Kargs:
|
||||
self.number = K.cell_number
|
||||
self.hpos = "none"
|
||||
self.K_target = target
|
||||
self.K_input_filenames = K.K_input_filenames
|
||||
self.K_input_dir = K.K_input_dir
|
||||
self.K_output_dir = K.K_output_dir
|
||||
self.K_output_basename = K.K_output_basename
|
||||
self.Image_output_dir = K.Image_output_dir
|
||||
@@ -57,7 +57,7 @@ class Image_grid(klammer_base.Klammer_base):
|
||||
self.basenames.append([e[0] for e in self.images[-1]])
|
||||
self.captions.append([e[1] for e in self.images[-1]])
|
||||
self.cache = image_cache.Image_cache(
|
||||
os.path.dirname(os.path.abspath(self.K_input_filenames)),
|
||||
self.K_input_dir,
|
||||
self.Image_search_path,
|
||||
verbose=False)
|
||||
|
||||
|
||||
@@ -69,3 +69,14 @@ An <id> is the value of the ^:id argument for an image.
|
||||
|
||||
^:pattern before(?^:\s+\d+)?^|after(?^:\s+\d+)?^|[-\w]+
|
||||
@@@
|
||||
|
||||
@@@argtype filename_list |
|
||||
one or more filenames. Filenames may contain spaces: a list is separated
|
||||
by a standalone "/" (whitespace on both sides), e.g.
|
||||
"chapter 1.kt / chapter 2.kt". Without the separator, the names are
|
||||
separated by whitespace, and names that do not exist are rejoined with
|
||||
their neighbors into names that do. A leading ~ expands to the home
|
||||
directory.
|
||||
:pattern [\s\S]*
|
||||
:python_cast (lambda s: __import__("kutil").filename_list(s))
|
||||
@@@
|
||||
|
||||
@@ -219,3 +219,37 @@ def format_for_paragraphs(s):
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def filename_list(text):
|
||||
"""Split a filename list; the Python twin of resolve_filename_list()
|
||||
in mac/file.cpp (keep the two in sync). A standalone "/" (whitespace
|
||||
on both sides) separates names, whose inner spacing is preserved;
|
||||
without a separator, whitespace-separated tokens that do not name
|
||||
existing files are rejoined with their neighbors into names that do.
|
||||
A leading ~ expands to the home directory."""
|
||||
text = text.strip()
|
||||
if not text:
|
||||
return []
|
||||
parts = re.split(r'(?:^|(?<=\s))/(?:\s|$)', text)
|
||||
if len(parts) > 1:
|
||||
return [os.path.expanduser(p.strip()) for p in parts if p.strip()]
|
||||
tokens = text.split()
|
||||
result, i = [], 0
|
||||
while i < len(tokens):
|
||||
if os.path.isfile(os.path.expanduser(tokens[i])):
|
||||
result.append(tokens[i])
|
||||
i += 1
|
||||
continue
|
||||
acc, j, found = tokens[i], i + 1, False
|
||||
while j < len(tokens):
|
||||
acc += " " + tokens[j]
|
||||
j += 1
|
||||
if os.path.isfile(os.path.expanduser(acc)):
|
||||
result.append(acc)
|
||||
i = j
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
result.append(tokens[i])
|
||||
i += 1
|
||||
return [os.path.expanduser(p) for p in result]
|
||||
|
||||
Reference in New Issue
Block a user