diff --git a/README.md b/README.md index f6ceb55..3e5d8f0 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ are regenerated on each release — patches cannot be merged directly. Report problems (or send patches) to the author; accepted changes are applied to the development tree and appear in a following snapshot. -This snapshot was assembled from development commit `685ce778cf34`. +This snapshot was assembled from development commit `3321e7c9e36e`. ## License diff --git a/sks/document/document_class.cpp b/sks/document/document_class.cpp index f7302eb..52d77ba 100644 --- a/sks/document/document_class.cpp +++ b/sks/document/document_class.cpp @@ -1,4 +1,6 @@ #include "util.h" +#include +#include #include "machine.h" #include "error.h" #include "kutil.h" @@ -133,6 +135,30 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine) } +// The cache directory of ONE document: the target's cache directory +// (_latex_files, _html_pages) with a subdirectory per document, named by +// the document's stem and a hash of its absolute path. The cache used to +// be one directory per target with an entry per chapter BASENAME, shared by +// every document on the machine, and freshness is only "the source is +// older than the entry": document A's kt/alpha.kt was served document B's +// cached alpha whenever B had rendered later -- silently when the chapter +// assembled, with B's error message when it did not (TODO 79, 2026-10-19). +// The hash, not the path itself: a path can be long and hold any +// character; the stem is for the eye. A document given as a string +// (ktext -s) has no file and is keyed by the working directory, where its +// _text.kt is rewritten on every run. +std::string Document_class::document_cache_directory(const std::string& target_cache) const +{ + const fs::path document = m_kt_root_filename.empty() + ? fs::current_path() / "_string_input" + : fs::absolute(fs::path(m_kt_root_filename)).lexically_normal(); + std::ostringstream key; + key << document.stem().string() << "-" << std::hex + << std::hash{}(document.generic_string()); + return cache_directory(target_cache + "/" + key.str()); +} + + void Document_class::save_string_input_as_file() { if (!m_text.empty()) { diff --git a/sks/document/document_class.h b/sks/document/document_class.h index 1748266..6c9db5b 100644 --- a/sks/document/document_class.h +++ b/sks/document/document_class.h @@ -193,7 +193,10 @@ public: std::vector> m_string_components {}; std::vector> m_toc_components {}; + // The page cache of THIS document under a target's cache directory + // (document_class.cpp); set by each target's create_*_output_directories(). std::string m_cache_dir {}; + std::string document_cache_directory(const std::string& target_cache) const; }; fs::path parse_input_filename(const std::string& s, const std::string& input_dir); diff --git a/sks/document/document_html.cpp b/sks/document/document_html.cpp index c0b78b6..ce19d1e 100644 --- a/sks/document/document_html.cpp +++ b/sks/document/document_html.cpp @@ -190,7 +190,7 @@ std::string Document_class::create_html_output_directories() install_resolved_font(m_resolved_sans, output_directory); install_resolved_font(m_resolved_mono, output_directory); } - m_cache_dir = cache_directory("_html_pages"); + m_cache_dir = document_cache_directory("_html_pages"); if (m_no_cache && fs::exists(m_cache_dir)) { for (auto& entry : std::filesystem::directory_iterator(m_cache_dir)) { // What ":cache false" actually removed: a derived consequence of diff --git a/sks/document/document_latex.cpp b/sks/document/document_latex.cpp index feb3968..8ec9fc7 100644 --- a/sks/document/document_latex.cpp +++ b/sks/document/document_latex.cpp @@ -12,7 +12,7 @@ std::string Document_class::create_tex_output_directories() (void)K::log(3); std::string output_directory = absolute_pathname(get("K_output_dir")); // + "/" + get("K_output_basename"); - m_cache_dir = cache_directory("_latex_files"); + m_cache_dir = document_cache_directory("_latex_files"); if (!m_toc_only) { if (!file_exists(output_directory, false, true)) { fs::create_directory(output_directory); diff --git a/tst/editor/vim_feature_test.vim b/tst/editor/vim_feature_test.vim index 5489370..7aeacbd 100644 --- a/tst/editor/vim_feature_test.vim +++ b/tst/editor/vim_feature_test.vim @@ -20,6 +20,11 @@ " " Writes PASS/FAIL lines to $KT_OUT; exits nonzero if anything failed. +" The buffer text below is UTF-8 (the umlauts of the multibyte case). Vim +" takes 'encoding' from the locale, and under a C locale that is latin1, +" where each umlaut is two characters and the jump lands two columns short +" -- a failure of this file, not of the plugin (TODO 78, 2026-10-19). +set encoding=utf-8 let s:results = [] function! s:Check(name, ok, ...) abort let detail = (a:ok || !a:0) ? '' : ' -- ' . string(a:1) diff --git a/tst/kdesc_test.sh b/tst/kdesc_test.sh index c36ea88..22d6e66 100755 --- a/tst/kdesc_test.sh +++ b/tst/kdesc_test.sh @@ -49,6 +49,9 @@ else fi # shows NAME PATTERN CMD... — exits 0 and the output matches PATTERN. +# The matching grep alone runs under a UTF-8 locale (TODO 78): under C its +# "." counts bytes, so the width test 10a fails and 10b can falsely pass; the +# command itself keeps the caller's locale. shows() { local name="$1" pattern="$2"; shift 2 local out status @@ -56,7 +59,7 @@ shows() { if [ $status -gt 128 ]; then echo "${red}FAIL${reset} $name — died by signal $((status-128))"; FAIL=$((FAIL+1)); return fi - if printf '%s' "$out" | grep -Eq -- "$pattern"; then + if printf '%s' "$out" | LC_ALL=C.UTF-8 grep -Eq -- "$pattern"; then echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} $name — no line matching [$pattern]" @@ -67,7 +70,7 @@ shows() { # absent NAME PATTERN CMD... — the output does NOT match PATTERN. absent() { local name="$1" pattern="$2"; shift 2 - if limited "$@" 2>&1 | plain | grep -Eq -- "$pattern"; then + if limited "$@" 2>&1 | plain | LC_ALL=C.UTF-8 grep -Eq -- "$pattern"; then echo "${red}FAIL${reset} $name — unexpected match [$pattern]"; FAIL=$((FAIL+1)) else echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) diff --git a/tst/kdiag_test.sh b/tst/kdiag_test.sh index 0e6d754..43b1b7b 100755 --- a/tst/kdiag_test.sh +++ b/tst/kdiag_test.sh @@ -80,6 +80,11 @@ pass() { echo "${green}PASS${reset} $1"; PASS=$((PASS + 1)); } fail() { echo "${red}FAIL${reset} $1"; [ -n "$2" ] && echo " $2"; FAIL=$((FAIL + 1)); } # shows NAME PATTERN KDIAG_ARGS... — exits 0 and the output matches PATTERN. +# The MATCHING grep runs under a UTF-8 locale whatever the caller's: under C +# its "." matches a byte, and a pattern like "⟨@i.⟩" over a three-byte +# subscript fails as if kdiag were wrong (cases 28 and 30; TODO 78). Only +# the grep: kdiag itself runs under the caller's locale, so a locale +# dependency in the command would still show. shows() { local name="$1" pattern="$2"; shift 2 run "$KDIAG" "$@" @@ -89,7 +94,7 @@ shows() { if [ "$STATUS" -ne 0 ]; then fail "$name" "exit $STATUS: $(printf '%s' "$OUT" | head -2)"; return fi - if printf '%s' "$OUT" | grep -Eq -- "$pattern"; then + if printf '%s' "$OUT" | LC_ALL=C.UTF-8 grep -Eq -- "$pattern"; then pass "$name" else fail "$name" "no match for [$pattern]; got: $(printf '%s' "$OUT" | head -2)" @@ -106,7 +111,7 @@ absent() { if [ "$STATUS" -ne 0 ]; then fail "$name" "exit $STATUS: $(printf '%s' "$OUT" | head -2)"; return fi - if printf '%s' "$OUT" | grep -Eq -- "$pattern"; then + if printf '%s' "$OUT" | LC_ALL=C.UTF-8 grep -Eq -- "$pattern"; then fail "$name" "unexpected match [$pattern]" else pass "$name" @@ -123,7 +128,7 @@ reports() { if [ "$STATUS" -eq 0 ]; then fail "$name" "expected a nonzero exit, got 0"; return fi - if printf '%s' "$OUT" | grep -Eq -- "$pattern"; then + if printf '%s' "$OUT" | LC_ALL=C.UTF-8 grep -Eq -- "$pattern"; then pass "$name" else fail "$name" "no match for [$pattern]; got: $(printf '%s' "$OUT" | head -2)" @@ -245,7 +250,7 @@ reports "24a. the error advice names a way kdiag has" '@read sks/sks\.k @' \ absent_in_error() { local name="$1" pattern="$2"; shift 2 run "$KDIAG" "$@" - if printf '%s' "$OUT" | grep -Eq -- "$pattern"; then + if printf '%s' "$OUT" | LC_ALL=C.UTF-8 grep -Eq -- "$pattern"; then fail "$name" "unexpected match [$pattern]" else pass "$name"