diff --git a/README.md b/README.md
index fdd6817..4880a59 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,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 `daad6273483a`.
+This snapshot was assembled from development commit `f344a37d772c`.
## License
diff --git a/com/Makefile b/com/Makefile
index 5680833..7c6cf42 100644
--- a/com/Makefile
+++ b/com/Makefile
@@ -28,6 +28,20 @@ LIBRARY := $(LIBDIR)/libklammertext.so
# Linker flags for commands
COM_LDFLAGS := $(EXPORT_DYNAMIC) -Wl,-rpath,'$(ORIGIN)/../lib' -L$(LIBDIR)
+# Windows: the application manifest (UTF-8 process code page) is linked into
+# each command as a resource object; elsewhere there is nothing to add.
+# (Only variables here: a rule before "all" would become the default goal.
+# The Windows rules themselves follow the commands target below.)
+ifneq (,$(filter MINGW% MSYS%,$(UNAME_S)))
+MANIFEST_OBJ := manifest.o
+# The DLLs beside the commands (makefile.env explains); with them, bin/ on
+# PATH is all a Windows user needs besides Python.
+RUNTIME_DLLS := $(addprefix $(BINDIR)/,libklammertext.so $(GCC_RUNTIME_DLLS))
+else
+MANIFEST_OBJ :=
+RUNTIME_DLLS :=
+endif
+
# Dependency files for command sources
DEPFILES := $(addsuffix .d,$(COMMANDS))
@@ -46,7 +60,16 @@ all : | mac sks
# Separate target to build commands (called after dependencies are ready)
.PHONY: commands
-commands : $(BINCOMMANDS)
+commands : $(BINCOMMANDS) $(RUNTIME_DLLS)
+
+ifneq (,$(filter MINGW% MSYS%,$(UNAME_S)))
+manifest.o : klammertext.rc klammertext.manifest
+ windres $< $@
+$(BINDIR)/libklammertext.so : $(LIBRARY) | $(BINDIR)
+ cp $< $@
+$(BINDIR)/%.dll : $(GCC_BIN)%.dll | $(BINDIR)
+ cp $< $@
+endif
# Ensure the output directory exists before linking into it.
$(BINDIR) :
@@ -63,14 +86,14 @@ sks : | mac
$(MAKE) -C $(KS)/document
# Explicit rules for each command - link against shared library, output to bin/
-$(BINDIR)/kdiag : kdiag.cpp $(LIBRARY) | $(BINDIR)
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS)
+$(BINDIR)/kdiag : kdiag.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR)
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS)
-$(BINDIR)/kdesc : kdesc.cpp $(LIBRARY) | $(BINDIR)
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS)
+$(BINDIR)/kdesc : kdesc.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR)
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS)
-$(BINDIR)/ktext : ktext.cpp $(LIBRARY) | $(BINDIR)
- $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< -o $@ -lklammertext $(LDLIBS)
+$(BINDIR)/ktext : ktext.cpp $(LIBRARY) $(MANIFEST_OBJ) | $(BINDIR)
+ $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $(COM_LDFLAGS) $(LDFLAGS) $< $(MANIFEST_OBJ) -o $@ $(KLAMMERTEXT_LIB) $(LDLIBS)
# Out-of-tree system install (copies; the build tree stays intact). The
# installed commands still need KLAMMERTEXT_HOME pointing at a Klammertext tree
diff --git a/com/kdesc.cpp b/com/kdesc.cpp
index 4286498..bba00f6 100644
--- a/com/kdesc.cpp
+++ b/com/kdesc.cpp
@@ -309,7 +309,7 @@ int main(int argc, char* argv[])
M.m_state.set(
"K_input_dir", absolute_pathname(file_directory(input_filenames[0])));
} else {
- M.m_state.set("K_input_dir", fs::current_path().string());
+ M.m_state.set("K_input_dir", fs::current_path().generic_string());
}
// --klammersets both lists and loads: with no words it enumerates the
diff --git a/com/kdiag.cpp b/com/kdiag.cpp
index 4a1d8e4..a1d81fc 100644
--- a/com/kdiag.cpp
+++ b/com/kdiag.cpp
@@ -74,7 +74,7 @@ int main(int argc, char* argv[])
}
if (p("rewrite")) {
- show_rewrite_rules = true;
+ set_show_rewrite_rules(true);
}
// kdiag deliberately has no --klammersets: a klammerset is reached here
@@ -94,7 +94,7 @@ int main(int argc, char* argv[])
machine.m_state.set("K_target", Target_registry::general_name);
machine.m_state.set("K_input_dir", fs::current_path().string());
std::string input = args.as_string("input");
- std::string command = construct_command_pathname(argv[0]);
+ std::string command = construct_command_pathname(argv[0]).generic_string();
// ONE authoritative katom list. process() returns the katoms but
// registers nothing -- read() is what appends to m_katoms and extracts
// the definitions -- so kdiag does that itself, in the order read()
diff --git a/com/klammertext.manifest b/com/klammertext.manifest
new file mode 100644
index 0000000..d8e39fc
--- /dev/null
+++ b/com/klammertext.manifest
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ UTF-8
+
+
+
diff --git a/com/klammertext.rc b/com/klammertext.rc
new file mode 100644
index 0000000..5c89f7e
--- /dev/null
+++ b/com/klammertext.rc
@@ -0,0 +1,4 @@
+// Resource script for the Windows builds of the commands: embeds
+// klammertext.manifest as the application manifest (resource type 24 is
+// RT_MANIFEST, id 1 the process manifest). Compiled by windres; see com/Makefile.
+1 24 "klammertext.manifest"
diff --git a/doc/install/windows_source_install.md b/doc/install/windows_source_install.md
new file mode 100644
index 0000000..4756236
--- /dev/null
+++ b/doc/install/windows_source_install.md
@@ -0,0 +1,305 @@
+# Klammertext source installation on Windows
+
+This document describes how to build and install Klammertext from source on
+Windows 10 or 11, so that the three commands `ktext`, `kdesc` and `kdiag`
+run as native Windows programs. Klammertext is developed on Linux and
+macOS; Windows is a supported place to *use* it, and installing it there
+takes more steps than on those systems, because Windows has no package
+manager that supplies a C++ compiler and `make`. The steps below were
+followed on a fresh Windows 11 machine and are the ones that worked.
+
+The installation uses three things a Windows user installs once:
+**MSYS2**, which provides the compiler, `make` and a Unix-style shell for
+building; **Python** from python.org, which Klammertext embeds; and **TeX
+Live**, for PDF output. Everyday use needs only PowerShell.
+
+
+## Prerequisites
+
+### Git and Python
+
+In PowerShell (search for "PowerShell" in the Start menu):
+
+```powershell
+winget install --id Git.Git -e
+winget install --id Python.Python.3.12 -e --override "/quiet InstallAllUsers=1 PrependPath=1"
+```
+
+The second command installs Python for all users into `C:\Program
+Files\Python312` and puts it on the `Path`. Open a new PowerShell window
+(the `Path` of an open one does not change) and install the image library
+the `@image` klammer uses:
+
+```powershell
+python -m pip install OpenImageIO
+python -c "import OpenImageIO"
+```
+
+Note that the interpreter is called `python` on Windows. There is no
+`python3`; the `python3` that Windows itself offers is a placeholder that
+opens the Microsoft Store, and Klammertext does not use it.
+
+### MSYS2 and the compiler
+
+Install MSYS2 from https://www.msys2.org/ (or `winget install --id
+MSYS2.MSYS2 -e`). It installs into `C:\msys64`. Open the **"MSYS2 UCRT64"**
+shell from the Start menu — that one, not "MSYS2 MSYS" or "MINGW64": the
+UCRT64 environment shares its C runtime with python.org's Python, which is
+what lets the two be linked together. Update it and install the toolchain:
+
+```bash
+pacman -Syu # if it closes the window, reopen the UCRT64 shell and run it again
+pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-make make \
+ mingw-w64-ucrt-x86_64-pkgconf mingw-w64-ucrt-x86_64-dlfcn diffutils
+```
+
+`diffutils` is only needed to run Klammertext's test suites.
+
+By default the MSYS2 shell does not see the programs Windows has on its
+`Path`, which means it cannot see `git` or `python`. Tell it to, once, in
+PowerShell, then reopen the UCRT64 shell:
+
+```powershell
+setx MSYS2_PATH_TYPE inherit
+```
+
+Verify, in the UCRT64 shell:
+
+```bash
+g++ --version # g++.exe (Rev4, Built by MSYS2 project) 16.x
+python --version # Python 3.12.x (python.org's, from the Windows Path)
+git --version
+```
+
+
+## Clone the repository
+
+In the UCRT64 shell. Windows paths are written with forward slashes there,
+`/c/Users/` for `C:\Users\`:
+
+```bash
+cd /c/Users/
+git clone https://git.andykopra.com/ack/klammertext.git
+cd klammertext
+```
+
+Git for Windows must not translate line endings: Klammertext's `.kt`
+files, `.k` files and shell scripts are LF files. The clone above respects
+that as long as `core.autocrlf` is not `true`; check with `git config
+--get core.autocrlf` and, if it prints `true`, set it to `false` and clone
+again.
+
+
+## Environment variables
+
+Klammertext's runtime environment is one self-configuring file, the same
+one Linux and macOS use. Source it from the UCRT64 shell's profile:
+
+```bash
+echo 'source /c/Users//klammertext/env/runtime.env' >> ~/.bashrc
+source ~/.bashrc
+```
+
+On Windows it sets `KLAMMERTEXT_HOME` and `TMPDIR` in the form Windows
+programs understand (`C:/Users//klammertext`), adds `bin/` to `PATH`,
+finds a TeX Live installed under `C:\Users\\external\texlive\`
+(see below), and sets `PYTHON=python`. Nothing is edited by hand.
+
+
+## Build
+
+In the UCRT64 shell, in the repository:
+
+```bash
+make -C com -j
+```
+
+`-j` compiles in parallel; on a machine with 8 GB of memory or less the
+optimizing compiler may run out of memory and report `out of memory
+allocating`. If so, run `make -C com` without `-j`: slower, and it
+finishes.
+
+This builds the library, the Standard Klammer Set's components and the
+three commands into `bin\`, and copies the library and the compiler's
+runtime DLLs there too, so `bin\` is self-contained. The Python binding is
+built into `kpy\`. Verify:
+
+```bash
+ktext -s '@eval 1 + 1 @' -d # prints 2
+ktext -s '@eval :shell echo hello | tr a-z A-Z @' -d # prints HELLO
+```
+
+The second line shows that `@eval :shell` runs a POSIX shell on Windows as
+it does elsewhere: Klammertext ships one (`win\busybox\`), so a document's
+`:shell` command means the same thing on every system.
+
+For a first document, create `hello.kt`:
+
+```
+@document
+:structure article
+:title Hello
+:text
+@s1 Hello, Klammertext @
+
+This document was built from source on Windows.
+@
+```
+
+and render it:
+
+```bash
+ktext hello.kt -t html # writes hello\index.html
+```
+
+
+## TeX Live (for PDF output)
+
+The Standard Klammer Set renders PDF with **XeLaTeX**. Install TeX Live's
+`scheme-small` plus the packages the SKS needs into
+`C:\Users\\external\texlive\2026`, where `runtime.env` finds it. This
+is a download of about 600 MB and takes twenty to thirty minutes.
+
+1. Download `install-tl.zip` from a CTAN mirror, for example
+ `https://ctan.math.illinois.edu/systems/texlive/tlnet/install-tl.zip`,
+ and unzip it. It unpacks into a folder named `install-tl-`.
+
+2. In that folder, create a file `texlive.profile` with this content,
+ replacing `` with your Windows user name:
+
+ ```
+ selected_scheme scheme-small
+ TEXDIR C:/Users//external/texlive/2026
+ TEXMFLOCAL C:/Users//external/texlive/2026/texmf-local
+ TEXMFSYSCONFIG C:/Users//external/texlive/2026/texmf-config
+ TEXMFSYSVAR C:/Users//external/texlive/2026/texmf-var
+ TEXMFHOME C:/Users//texmf
+ TEXMFVAR C:/Users//.texlive2026/texmf-var
+ TEXMFCONFIG C:/Users//.texlive2026/texmf-config
+ tlpdbopt_install_docfiles 0
+ tlpdbopt_install_srcfiles 0
+ tlpdbopt_autobackup 0
+ tlpdbopt_desktop_integration 0
+ tlpdbopt_file_assocs 0
+ tlpdbopt_w32_multi_user 0
+ ```
+
+3. In PowerShell, in that folder, run the installer without its graphical
+ interface, naming the same mirror:
+
+ ```powershell
+ .\install-tl-windows.bat -no-gui -profile texlive.profile -repository https://ctan.math.illinois.edu/systems/texlive/tlnet
+ ```
+
+ Leave the window open until it reports that the installation is
+ complete. (A warning about `tlaunch.ini` is harmless.)
+
+4. Still in PowerShell, install the additional packages the SKS requires
+ beyond `scheme-small` — the same list `doc/install/texlive_additional_packages.sh`
+ installs on Linux and macOS — and rebuild the formats:
+
+ ```powershell
+ $tl = "$env:USERPROFILE\external\texlive\2026\bin\windows"
+ $env:Path = "$tl;$env:Path"
+ & "$tl\tlmgr.bat" option repository https://ctan.math.illinois.edu/systems/texlive/tlnet
+ & "$tl\tlmgr.bat" install adjustbox collectbox collection-fontsrecommended enumitem fontaxes footmisc inconsolata layouts mdframed multirow needspace opensans pict2e textpos titlesec upquote zref
+ & "$tl\fmtutil-sys.exe" --all
+ ```
+
+ The second line matters: `tlmgr` and `fmtutil` start helper programs
+ such as `kpsewhich` by name and find them only through the `Path`.
+
+Open a new UCRT64 shell (so `runtime.env` runs again and finds the tree)
+and verify:
+
+```bash
+xelatex --version
+ktext hello.kt -t pdf # writes hello.pdf
+```
+
+
+## Using the commands from PowerShell
+
+Building needs the UCRT64 shell; using Klammertext does not. For everyday
+use from PowerShell, put three directories on the `Path` and set
+`KLAMMERTEXT_HOME`, once, as user environment variables (Settings ▸
+System ▸ About ▸ Advanced system settings ▸ Environment Variables, or with
+`setx`):
+
+```powershell
+setx KLAMMERTEXT_HOME "C:\Users\\klammertext"
+setx Path "$env:Path;C:\Users\\klammertext\bin;C:\Users\\external\texlive\2026\bin\windows"
+```
+
+Python is already on the `Path` from its installer. Open a new PowerShell
+window and `ktext`, `kdesc` and `kdiag` work there directly. Paths
+Klammertext prints use forward slashes on every system; paths you type may
+use either.
+
+
+## Verifying the installation
+
+In the UCRT64 shell:
+
+```bash
+ktext -s '@eval 1 + 1 @' -d # Python evaluation
+ktext -s '@eval :shell date @' -d # shell evaluation (the bundled sh)
+kdesc -t # the targets
+kdesc --typeface # the fonts Klammertext ships
+ktext hello.kt -t html # html
+ktext hello.kt -t pdf # pdf, requires TeX Live
+
+make -C $KLAMMERTEXT_HOME/tst test # the engine's test suites
+make -C $KLAMMERTEXT_HOME/sks/tst test # the Standard Klammer Set's suites (needs TeX Live)
+```
+
+A few suite cases report `SKIP` on Windows for things the MSYS2 shell
+cannot provide, such as a real terminal for the colour tests or a
+symbolic link; that is expected.
+
+
+## Updating
+
+In the UCRT64 shell:
+
+```bash
+cd $KLAMMERTEXT_HOME
+git pull
+make -C com -j
+```
+
+
+## Troubleshooting
+
+**The command window closes at once, or "The code execution cannot proceed
+because python312.dll was not found".** Python's directory is not on the
+`Path` of that window. Reinstall Python with "Add to PATH" selected, or add
+`C:\Program Files\Python312` to the `Path` yourself, and open a new window.
+
+**`import OpenImageIO` fails with "DLL load failed while importing
+OpenImageIO".** The OpenImageIO package needs the Microsoft Visual C++
+runtime, which nearly every Windows machine already has but a freshly
+installed one may not. Install it from
+https://aka.ms/vs/17/release/vc_redist.x64.exe and try the import again.
+
+**"libklammertext.so was not found" or "libstdc++-6.dll was not found".**
+`bin\` is not on the `Path`, or the build did not finish: `make -C com -j`
+places the library and the runtime DLLs in `bin\` beside the commands.
+
+**"The shell Klammertext runs sh commands with on Windows is missing".**
+`win\busybox\busybox64u.exe` is part of the repository; the clone is
+incomplete or `KLAMMERTEXT_HOME` points elsewhere.
+
+**"The 'xelatex' command required to make PDF files is not installed".**
+TeX Live's `bin\windows` directory is not on the `Path` of the shell you
+are using. In the UCRT64 shell `runtime.env` adds it when the tree is under
+`C:\Users\\external\texlive\`; in PowerShell add it as shown
+above.
+
+**`git` or `python` are "command not found" in the UCRT64 shell.**
+`MSYS2_PATH_TYPE` is not set to `inherit`, or the shell was opened before
+you set it. Set it and open a new UCRT64 shell.
+
+**The build is slow.** Windows Defender scans every file the compiler
+writes. Excluding the repository folder from real-time scanning speeds the
+build up considerably; it is not required.
diff --git a/env/makefile.env b/env/makefile.env
index bac46e9..cea6efd 100644
--- a/env/makefile.env
+++ b/env/makefile.env
@@ -27,9 +27,20 @@ else
COMPILER ?= gcc # gcc | clang
endif
-# Python: version- and platform-agnostic, no hardcoded paths.
+# Python: version- and platform-agnostic, no hardcoded paths. On Windows the
+# interpreter is python.org's (the one PyPI wheels -- OpenImageIO -- are built
+# for); it ships no python3-config, so its prefix is asked of the interpreter
+# itself and its import library named by version (libs/python312.lib).
+ifneq (,$(filter MINGW% MSYS%,$(UNAME_S)))
+PYTHON := python
+PYTHON_PREFIX := $(shell cygpath -m "$$($(PYTHON) -c 'import sys; print(sys.base_prefix)')")
+PYTHON_INC := -I"$(PYTHON_PREFIX)/Include"
+PYTHON_LIB := -L"$(PYTHON_PREFIX)/libs" -l$(shell $(PYTHON) -c 'import sys; print("python%d%d" % sys.version_info[:2])')
+else
+PYTHON := python3
PYTHON_INC := $(shell python3-config --includes)
PYTHON_LIB := $(shell python3-config --ldflags --embed)
+endif
# Flags common to all platforms.
CPPFLAGS = -I$(KLAMMERTEXT_HOME)/mac $(PYTHON_INC)
@@ -52,6 +63,29 @@ SHARED = -dynamiclib
SONAME = -install_name @rpath/$(notdir $@)
ORIGIN := @loader_path
EXPORT_DYNAMIC = -Wl,-export_dynamic
+else ifneq (,$(filter MINGW% MSYS%,$(UNAME_S)))
+# ---------- Windows (MSYS2, UCRT64 environment; native PE binaries) ----------
+# uname says MINGW64_NT-... under UCRT64 too. The compilers come from the
+# environment's PATH (/ucrt64/bin). libdl.a is the mingw-w64-dlfcn shim over
+# LoadLibrary, linked statically so no libdl.dll travels with the products.
+# PE has no soname and no rpath; a DLL is found through PATH or
+# the executable's own directory, and GNU ld's -l search list on PE does not
+# include ".so", hence the exact-name form below.
+GCC := g++
+CLANG := clang++
+LDFLAGS =
+LDLIBS = -l:libdl.a $(if $(NOPYTHON),,$(PYTHON_LIB))
+SHARED = -shared
+SONAME =
+ORIGIN := $$ORIGIN
+EXPORT_DYNAMIC = -Wl,--export-all-symbols
+KLAMMERTEXT_LIB = -l:libklammertext.so
+# A Windows program finds a DLL through PATH or its own directory, never an
+# rpath. So every directory that holds a program or a Python extension also
+# holds the DLLs it needs: libklammertext.so and the three gcc runtime DLLs
+# the toolchain links (from the compiler's own bin/). com/ and kpy/ copy them.
+GCC_BIN := $(dir $(shell cygpath -m "$$(command -v g++)"))
+GCC_RUNTIME_DLLS := libstdc++-6.dll libgcc_s_seh-1.dll libwinpthread-1.dll
else
# ---------- Linux ----------
GCC := /usr/bin/g++
@@ -63,6 +97,8 @@ SONAME = -Wl,-soname,$(notdir $@)
ORIGIN := $$ORIGIN
EXPORT_DYNAMIC = -rdynamic
endif
+# How a link line names libklammertext (set above for Windows).
+KLAMMERTEXT_LIB ?= -lklammertext
# Resolve the compiler choice (gcc by default; clang with COMPILER=clang).
CXX := $(if $(filter clang,$(COMPILER)),$(CLANG),$(GCC))
diff --git a/env/runtime.env b/env/runtime.env
index f94ca74..3752a22 100644
--- a/env/runtime.env
+++ b/env/runtime.env
@@ -20,6 +20,19 @@ export KLAMMERTEXT_HOME="$(cd "$(dirname "$_kt_self")/.." && pwd)"
unset _kt_self
_kt_uname="$(uname -s)"
+# Windows under MSYS2: the commands are native programs and never understand
+# MSYS's /c/... spelling, so KLAMMERTEXT_HOME and TMPDIR reach them in Windows
+# form (C:/..., cygpath -m). bash's own PATH keeps the POSIX form -- a colon
+# inside a PATH entry splits it. Python (python.org's) is the user's to put on
+# PATH; an MSYS2 login shell does not inherit the Windows PATH by default.
+_kt_home_posix="$KLAMMERTEXT_HOME"
+case "$_kt_uname" in
+ MINGW*|MSYS*)
+ export KLAMMERTEXT_HOME="$(cygpath -m "$KLAMMERTEXT_HOME")"
+ export TMPDIR="$(cygpath -m "${TMPDIR:-/tmp}")"
+ export PYTHON="${PYTHON:-python}" # python.org installs no python3
+ ;;
+esac
# --- KLAMMERTEXT_TEXLIVE_BIN: newest installed TeX Live under ~/external -------
# Klammertext's TeX Live installs are named by 4-digit year (2024, 2025, 2026).
@@ -27,20 +40,27 @@ _kt_uname="$(uname -s)"
# are never selected; sort -V then picks the newest year.
if [ "$_kt_uname" = "Darwin" ]; then
_kt_arch="universal-darwin"
+elif case "$_kt_uname" in MINGW*|MSYS*) true;; *) false;; esac; then
+ _kt_arch="windows" # TeX Live's bin/windows
else
_kt_arch="$(uname -m)-linux" # e.g. x86_64-linux
fi
# Use `find` with a -path pattern, NOT a shell glob: on macOS/zsh an unmatched
# glob (no texlive installed) is a hard "no matches found" error, whereas find
# just returns nothing. The -path pattern keeps the 4-digit-year restriction.
-_kt_tl="$(find "$HOME/external/texlive" -maxdepth 3 -type d \
+# Windows: a user installs under the Windows home, %USERPROFILE%\external
+# (MSYS2's $HOME is C:\msys64\home\, which no Windows guide would name).
+_kt_tlroot="$HOME/external/texlive"
+case "$_kt_uname" in MINGW*|MSYS*) _kt_tlroot="$(cygpath -u "$USERPROFILE")/external/texlive";; esac
+_kt_tl="$(find "$_kt_tlroot" -maxdepth 3 -type d \
-path "*/[0-9][0-9][0-9][0-9]/bin/$_kt_arch" 2>/dev/null \
| sort -V | tail -1)"
[ -n "$_kt_tl" ] && export KLAMMERTEXT_TEXLIVE_BIN="$_kt_tl"
-unset _kt_tl _kt_arch
+unset _kt_tl _kt_arch _kt_tlroot
# --- PATH (TeX Live appended only if one was found) ---------------------------
-export PATH="$KLAMMERTEXT_HOME/bin:$KLAMMERTEXT_HOME/tst${KLAMMERTEXT_TEXLIVE_BIN:+:$KLAMMERTEXT_TEXLIVE_BIN}:$PATH"
+export PATH="$_kt_home_posix/bin:$_kt_home_posix/tst${KLAMMERTEXT_TEXLIVE_BIN:+:$KLAMMERTEXT_TEXLIVE_BIN}:$PATH"
+unset _kt_home_posix
# --- The Python binding (kpy/) -----------------------------------------------
# "import klammertext" with no setup, as the container image already arranges
diff --git a/kpy/Makefile b/kpy/Makefile
index fb93ec4..e26353b 100644
--- a/kpy/Makefile
+++ b/kpy/Makefile
@@ -21,7 +21,7 @@ include $(K)/env/makefile.env
LIBDIR := $(K)/lib
LIBRARY := $(LIBDIR)/libklammertext.so
-EXT_SUFFIX := $(shell python3-config --extension-suffix)
+EXT_SUFFIX := $(shell $(PYTHON) -c 'import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX"))')
MODULE := klammertext$(EXT_SUFFIX)
# "../lib": the module sits in $(K)/kpy, one level below $(K).
@@ -34,7 +34,16 @@ DEPFLAGS = -MMD -MP -MF $(@:.o=.d)
.PHONY: all clean
-all : $(MODULE)
+# Windows: the module's dependent DLLs sit beside it (Python loads an
+# extension's dependencies from the extension's own directory, never from
+# PATH or an rpath -- there is none on PE); see makefile.env.
+ifneq (,$(filter MINGW% MSYS%,$(UNAME_S)))
+BESIDE_DLLS := libklammertext.so $(GCC_RUNTIME_DLLS)
+else
+BESIDE_DLLS :=
+endif
+
+all : $(MODULE) $(BESIDE_DLLS)
%.o : %.cpp
@echo Compiling $<
@@ -42,10 +51,17 @@ all : $(MODULE)
$(MODULE) : $(OBJECTS) $(LIBRARY)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(KPY_LDFLAGS) $(LDFLAGS) \
- -o $@ $(OBJECTS) -lklammertext $(LDLIBS)
+ -o $@ $(OBJECTS) $(KLAMMERTEXT_LIB) $(LDLIBS)
+
+ifneq (,$(filter MINGW% MSYS%,$(UNAME_S)))
+libklammertext.so : $(LIBRARY)
+ cp $< $@
+%.dll : $(GCC_BIN)%.dll
+ cp $< $@
+endif
clean :
- rm -f $(OBJECTS) $(DEPFILES) klammertext.*.so *~
+ rm -f $(OBJECTS) $(DEPFILES) klammertext.*.so klammertext.*.pyd libklammertext.so *.dll *~
rm -rf __pycache__
-include $(DEPFILES)
diff --git a/kpy/klammertext.cpp b/kpy/klammertext.cpp
index c1b521b..a4f4a3b 100644
--- a/kpy/klammertext.cpp
+++ b/kpy/klammertext.cpp
@@ -215,9 +215,12 @@ int instances = 0;
// module, so a relative @read in read() text resolves against the directory
// the script runs in -- the same rule ktext applies to -s input, and set at
// each read() because a script may chdir between two of them.
+// The command's identity, through the library (set_command_identity): on
+// Windows an assignment to error.h's inline variables from this module
+// would land in this module's own copies, not the library's.
void set_string_source()
{
- command_pathname = (fs::current_path() / "klammertext").string();
+ set_command_identity("klammertext", (fs::current_path() / "klammertext").generic_string());
}
PyObject* Machine_new(PyTypeObject* type, PyObject*, PyObject*)
@@ -607,7 +610,6 @@ PyMODINIT_FUNC PyInit_klammertext(void)
return nullptr;
}
// What an error report names as the command.
- command_name = "klammertext";
set_string_source();
const char* home = std::getenv(klammertext_home_var.c_str());
PyObject* home_obj = home ? PyUnicode_FromString(home) : Py_None;
diff --git a/mac/argv.h b/mac/argv.h
index 3338bf7..6433fb2 100644
--- a/mac/argv.h
+++ b/mac/argv.h
@@ -134,5 +134,5 @@ public:
// these, so a shell-quoted filename containing spaces stays one
// element; the space-joined m_value remains only for get()/describe().
std::map> m_vectors {};
- long unsigned int m_syntax_size = 0;
+ std::size_t m_syntax_size = 0;
};
diff --git a/mac/command.cpp b/mac/command.cpp
index 87442ce..238b691 100644
--- a/mac/command.cpp
+++ b/mac/command.cpp
@@ -1,3 +1,7 @@
+#ifdef _WIN32
+#include
+#include
+#endif
#include
#include "argv.h"
@@ -21,11 +25,24 @@ fs::path construct_command_pathname(char* command)
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 = std::string(argv[0]);
- command_pathname = construct_command_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 {};
@@ -140,7 +157,7 @@ void set_command_state(
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().string());
+ S.set("K_input_dir", fs::current_path().generic_string());
}
}
@@ -178,7 +195,7 @@ void load_klammersets(Machine& machine, const strings_t& symbols)
continue;
}
std::string klammerset_filename = resolve_klammerset_symbol(
- symbol, machine.m_state.value("K_input_dir"), Locator::none()).string();
+ 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".
diff --git a/mac/command.h b/mac/command.h
index 35fb21d..9299e44 100644
--- a/mac/command.h
+++ b/mac/command.h
@@ -10,6 +10,13 @@
class Machine;
void set_verbose_level(int argc, char* argv[]);
+// What an error report and the -s pseudo source name as the command, set
+// from OUTSIDE the library (the Python binding). It is a function, not an
+// assignment to the header's inline variables, because on Windows every
+// module -- the DLL, an extension module -- has its OWN copy of an inline
+// variable: an assignment made in the binding never reached the copy the
+// library's error reports read. On Linux the two are one variable.
+void set_command_identity(const std::string& name, const std::string& pathname);
bool show_usage(int argc, char* argv[]);
fs::path construct_command_pathname(char* command);
diff --git a/mac/coverage.cpp b/mac/coverage.cpp
index a480cd1..3b29ae1 100644
--- a/mac/coverage.cpp
+++ b/mac/coverage.cpp
@@ -139,8 +139,10 @@ std::string short_path(const std::string& path)
std::error_code ec {};
fs::path home_real = fs::weakly_canonical(fs::path(home), ec);
fs::path path_real = fs::weakly_canonical(fs::path(path), ec);
- std::string prefix = (ec ? std::string(home) : home_real.string()) + "/";
- const std::string& subject = ec ? path : path_real.string();
+ // Both in the generic spelling ('/'): on Windows weakly_canonical
+ // answers with backslashes while the report's paths use '/'.
+ std::string prefix = (ec ? fs::path(home).generic_string() : home_real.generic_string()) + "/";
+ const std::string subject = ec ? fs::path(path).generic_string() : path_real.generic_string();
if (subject.size() > prefix.size()
&& subject.compare(0, prefix.size(), prefix) == 0) {
return subject.substr(prefix.size());
diff --git a/mac/error.cpp b/mac/error.cpp
index e97c192..f666b16 100644
--- a/mac/error.cpp
+++ b/mac/error.cpp
@@ -11,7 +11,7 @@ bool display_source(const std::string& filename_arg)
return false;
} else {
fs::path filename = fs::path(filename_arg).filename();
- return sks_commands.count(filename) == 0;
+ return sks_commands.count(filename.string()) == 0;
}
}
diff --git a/mac/eval.cpp b/mac/eval.cpp
index 3b0a0b4..4aceb51 100644
--- a/mac/eval.cpp
+++ b/mac/eval.cpp
@@ -10,7 +10,16 @@
#include
#include
#include
+#ifdef _WIN32
+// mingw-w64 has no : there pclose() returns the child's exit code
+// itself, not a wait status, and no signal can have killed it.
+#define WIFEXITED(s) ((s) != -1)
+#define WEXITSTATUS(s) (s)
+#define WIFSIGNALED(s) 0
+#define WTERMSIG(s) 0
+#else
#include
+#endif
// Run a shell command and return its STANDARD OUTPUT, which becomes document
// text. Two things the first version did not do, both of them the same defect
@@ -51,7 +60,8 @@ std::string shell(State state, std::string command, Locator loc)
// The braces keep the redirection outside the writer's command, so a
// command containing its own pipeline or redirection still works.
std::string wrapped = "{ " + command + " ; } 2>" + q_(err_path);
- FILE* pipe = popen(wrapped.c_str(), "r");
+ Shell_command sh_cmd(wrapped, loc);
+ FILE* pipe = popen(sh_cmd.line().c_str(), "r");
if (!pipe) {
fs::remove(err_path);
throw Environment_error(
@@ -129,7 +139,9 @@ std::string run_haskell(const std::string& hsfile, Locator loc)
err_path = err_template.data();
std::string command = "runghc " + q_(hsfile) + " 2>" + q_(err_path);
- FILE* pipe = popen(command.c_str(), "r");
+ // Through the same sh as :shell: the quoting is sh quoting.
+ Shell_command sh_cmd(command, loc);
+ FILE* pipe = popen(sh_cmd.line().c_str(), "r");
if (!pipe) {
fs::remove(err_path);
throw Environment_error("Could not run runghc.", loc);
diff --git a/mac/file.cpp b/mac/file.cpp
index 8a16bc5..4d11ff8 100644
--- a/mac/file.cpp
+++ b/mac/file.cpp
@@ -33,7 +33,7 @@ std::string absolute_pathname(const std::string& filename, const std::string& ba
{
fs::path p(filename);
if (filename.empty()) {
- return fs::current_path().string();
+ return fs::current_path().generic_string();
}
if (!base.empty()) {
// Resolve filename relative to base's directory
@@ -41,7 +41,7 @@ std::string absolute_pathname(const std::string& filename, const std::string& ba
fs::path base_dir = fs::is_directory(b) ? b : b.parent_path();
p = base_dir / p;
}
- return fs::absolute(p).string();
+ return fs::absolute(p).generic_string();
}
std::string relative_pathname(const std::string& filename)
@@ -70,6 +70,66 @@ size_t count_substrings(const std::string& text, const std::string& substring) {
return count;
}
+std::string home_directory()
+{
+ if (const char* home = std::getenv("HOME"); home && *home) {
+ return home;
+ }
+#ifdef _WIN32
+ if (const char* profile = std::getenv("USERPROFILE"); profile && *profile) {
+ return profile;
+ }
+#endif
+ return {};
+}
+
+std::string find_in_path(const std::string& program)
+{
+ const char* path = std::getenv("PATH");
+ if (path == nullptr) {
+ return {};
+ }
+ std::stringstream ss(path);
+ std::string dir;
+ while (std::getline(ss, dir, path_list_separator)) {
+ if (dir.empty()) {
+ continue;
+ }
+ fs::path candidate = fs::path(dir) / program;
+ std::error_code ec {};
+ if (fs::is_regular_file(candidate, ec)) {
+ return candidate.generic_string();
+ }
+#ifdef _WIN32
+ fs::path exe = candidate;
+ exe += ".exe";
+ if (fs::is_regular_file(exe, ec)) {
+ return exe.generic_string();
+ }
+#endif
+ }
+ return {};
+}
+
+std::vector directory_list(const char* variable, const std::string& default_subdir)
+{
+ std::string paths {};
+ if (const char* env = std::getenv(variable); env && *env) {
+ paths = env;
+ } else if (std::string home = home_directory(); !home.empty()) {
+ paths = home + "/" + default_subdir;
+ }
+ std::vector result {};
+ std::stringstream ss(paths);
+ std::string dir;
+ while (std::getline(ss, dir, path_list_separator)) {
+ if (!dir.empty()) {
+ result.push_back(dir);
+ }
+ }
+ return result;
+}
+
fs::path relative_to_cwd(const fs::path& input)
{
const auto base = fs::current_path();
@@ -289,8 +349,10 @@ std::string string_from_file(const std::string& pathname, bool strip_surrounding
void string_to_file(const std::string& pathname, std::string contents)
{
- fs::create_directories(file_directory(fs::absolute(pathname)));
- std::ofstream out(pathname);
+ fs::create_directories(file_directory(fs::absolute(pathname).string()));
+ // Binary: the text Klammertext writes ends its lines with LF on every
+ // system; Windows' text mode would turn each into CRLF.
+ std::ofstream out(pathname, std::ios::binary);
if (!out) {
throw File_error("Could not write file " + pathname);
}
@@ -319,7 +381,7 @@ strings_t get_files_in_directory(const std::string& dir)
for (const auto& entry : fs::directory_iterator(dir)) {
if (entry.is_regular_file()) {
// std::cout << entry.path().filename() << std::endl;
- result.push_back(entry.path().filename());
+ result.push_back(entry.path().filename().string());
}
}
} catch (const fs::filesystem_error& ex) {
@@ -456,8 +518,11 @@ strings_t get_sks_directories(const std::string& s, bool include_argument)
std::string symbolic_klammertext_filename(std::string filename)
{
+ // Both in the generic spelling: a Windows KLAMMERTEXT_HOME may be written
+ // with either separator, and a path Klammertext prints uses '/'.
return string_replace(
- filename, get_env_var("KLAMMERTEXT_HOME"), "$KLAMMERTEXT_HOME");
+ fs::path(filename).generic_string(),
+ fs::path(get_env_var("KLAMMERTEXT_HOME")).generic_string(), "$KLAMMERTEXT_HOME");
}
diff --git a/mac/file.h b/mac/file.h
index d7211d7..522aba2 100644
--- a/mac/file.h
+++ b/mac/file.h
@@ -16,6 +16,28 @@ std::string relative_pathname(const std::string& filename, const std::string& ba
fs::path relative_to_cwd(const fs::path& input);
+// The separator of a directory list in an environment variable
+// (KLAMMERTEXT_FONTS, KLAMMERTEXT_KLAMMERSETS): ';' on Windows, where a drive
+// letter puts a colon inside every absolute path, ':' everywhere else -- the
+// convention of PATH on each system.
+#ifdef _WIN32
+constexpr char path_list_separator = ';';
+#else
+constexpr char path_list_separator = ':';
+#endif
+// The user's home directory as the system names it: HOME, and on Windows
+// USERPROFILE when HOME is unset (a plain PowerShell or cmd sets no HOME;
+// an MSYS2 shell does). Empty when neither is set.
+std::string home_directory();
+// The first PROGRAM on PATH (split on path_list_separator; on Windows the
+// name also tried with ".exe"), or "" when none: what "which" answers, without
+// spawning a shell -- on Windows popen()'s cmd.exe has no "which".
+std::string find_in_path(const std::string& program);
+// The directories listed in an environment variable, split on
+// path_list_separator, empty entries dropped; DEFAULT_SUBDIR under the home
+// directory when the variable is unset or empty.
+std::vector directory_list(const char* variable, const std::string& default_subdir);
+
bool file_exists(const std::string& pathname, bool error_if_not=false, bool is_directory=false);
// Text to its lines, and a file to (its whole text, its lines). These
diff --git a/mac/font_store.cpp b/mac/font_store.cpp
index 1d9cdb3..9f32580 100644
--- a/mac/font_store.cpp
+++ b/mac/font_store.cpp
@@ -23,12 +23,20 @@
namespace fs = std::filesystem;
+// The interpreter spawned for the font-metric script: python.org's Windows
+// build installs "python" only; Linux and macOS name it "python3".
+#ifdef _WIN32
+static const char* const python_interpreter = "python";
+#else
+static const char* const python_interpreter = "python3";
+#endif
+
// A filesystem extension lowercased for case-insensitive matching, so a
// font named Foo.TTF or Foo.OTF is recognized like Foo.ttf / Foo.otf.
static std::string lower_extension(const fs::path& path)
{
- std::string ext = path.extension();
+ std::string ext = path.extension().string();
for (char& c : ext) {
c = std::tolower(static_cast(c));
}
@@ -49,29 +57,14 @@ std::string name_to_dirname(const std::string& name)
}
-// The directories searched for installed fonts: the colon-separated
-// KLAMMERTEXT_FONTS environment variable, or ~/.klammertext/fonts when it
-// is not set. Directories are searched in listed order, and the default
-// fonts in $KLAMMERTEXT_HOME/fnt are searched last, so an installed font
-// can deliberately shadow a default one.
+// The directories searched for installed fonts: the KLAMMERTEXT_FONTS
+// environment variable (a list in the system's PATH convention: ':' or, on
+// Windows, ';'), or ~/.klammertext/fonts when it is not set. Directories are
+// searched in listed order, and the default fonts in $KLAMMERTEXT_HOME/fnt are
+// searched last, so an installed font can deliberately shadow a default one.
strings_t installed_font_dirs()
{
- strings_t result {};
- std::string paths {};
- const char* env = std::getenv("KLAMMERTEXT_FONTS");
- if (env && *env) {
- paths = env;
- } else if (const char* home = std::getenv("HOME"); home && *home) {
- paths = std::string(home) + "/.klammertext/fonts";
- }
- std::stringstream ss(paths);
- std::string dir;
- while (std::getline(ss, dir, ':')) {
- if (!dir.empty()) {
- result.push_back(dir);
- }
- }
- return result;
+ return directory_list("KLAMMERTEXT_FONTS", ".klammertext/fonts");
}
std::string default_font_dir()
@@ -151,7 +144,7 @@ std::vector fonts_in_directory(const std::string& base)
for (auto& entry : fs::directory_iterator(base)) {
if (entry.path().extension() == ".css" &&
fs::is_directory(base + "/" + entry.path().stem().string())) {
- names.push_back(entry.path().stem());
+ names.push_back(entry.path().stem().string());
}
}
std::sort(names.begin(), names.end());
@@ -199,10 +192,12 @@ static void extract_font_metrics(Resolved_font& font)
if (!fs::exists(ttf_path))
return;
// Extract both x-height and cap-height ratios from OS/2 table
+ // The path goes inside a Python literal: forward slashes, which Python
+ // opens on every system, where a Windows backslash would be an escape.
std::string script =
- "python3 -c \""
+ std::string(python_interpreter) + " -c \""
"import struct; "
- "f = open('" + ttf_path + "', 'rb'); "
+ "f = open('" + fs::path(ttf_path).generic_string() + "', 'rb'); "
"_, n = struct.unpack('>IH', f.read(6)); "
"f.read(6); "
"t = {};\n"
@@ -219,6 +214,9 @@ static void extract_font_metrics(Resolved_font& font)
"print(f'{xh/upm:.4f} {ch/upm:.4f}') if ver >= 2 else None; "
"f.close()\"";
std::string result = trim(exec(script.c_str()));
+ // A DERIVED value a designer cannot read off the font: the two ratios
+ // (or nothing, when the script failed) decide the html size normalization.
+ (void)K::log(3, "Font metrics of " + ttf_path + ": " + (result.empty() ? std::string("(none)") : result));
if (!result.empty()) {
try {
auto pos = result.find(' ');
@@ -418,7 +416,7 @@ std::vector classify_font_files(const std::string& directory)
for (auto& entry : fs::recursive_directory_iterator(directory)) {
std::string ext = lower_extension(entry.path());
if (entry.is_regular_file() && (ext == ".ttf" || ext == ".otf")) {
- result.push_back(classify_font_file(entry.path()));
+ result.push_back(classify_font_file(entry.path().string()));
}
}
return result;
@@ -521,7 +519,7 @@ std::string write_font_samples(const std::string& output_dir, const std::string&
install_fonts(source_dir, output_dir + "/fonts");
for (auto& entry : fs::directory_iterator(output_dir + "/fonts")) {
if (entry.path().extension() == ".css") {
- std::string css = string_from_file(entry.path());
+ std::string css = string_from_file(entry.path().string());
std::smatch match {};
if (std::regex_search(css, match,
std::regex(R"(font-family:\s*'([^']+)')"))) {
diff --git a/mac/katomize.cpp b/mac/katomize.cpp
index 5374473..558af9f 100644
--- a/mac/katomize.cpp
+++ b/mac/katomize.cpp
@@ -160,3 +160,8 @@ void warn_unparsed_katoms(katom_list& katoms, bool warn)
}
}
}
+
+void set_show_rewrite_rules(bool on)
+{
+ show_rewrite_rules = on;
+}
diff --git a/mac/katomize.h b/mac/katomize.h
index 85bf4c5..314639a 100644
--- a/mac/katomize.h
+++ b/mac/katomize.h
@@ -82,6 +82,10 @@ katom_list katomize_lines(const strings_t& lines, const std::string& source,
// expand_compound_katom() and attributed to whichever rule the loop
// happened to be holding, so it always read as "bar precedence".
inline bool show_rewrite_rules = false;
+// Set it through this, never by assignment from a command: on Windows the
+// command's copy of an inline variable is not the library's (see
+// set_command_identity in command.h).
+void set_show_rewrite_rules(bool on);
// Warn about text that matched no katom type and that no rule could divide
// -- but only where it survives processing: text removed by #, ##, or
diff --git a/mac/klammerset_registry.cpp b/mac/klammerset_registry.cpp
index a07f570..a65ac88 100644
--- a/mac/klammerset_registry.cpp
+++ b/mac/klammerset_registry.cpp
@@ -151,7 +151,7 @@ std::vector klammerset_search_dirs(const std::string& local_dir)
// A repeated directory (e.g. the local stage already IS
// $KLAMMERTEXT_HOME) adds nothing and clutters error messages.
auto push_unique = [&result](const std::string& dir) {
- std::string canonical = fs::weakly_canonical(dir).string();
+ std::string canonical = fs::weakly_canonical(dir).generic_string();
if (!is_in(canonical, result)) {
result.push_back(canonical);
}
@@ -171,22 +171,10 @@ std::vector klammerset_search_dirs(const std::string& local_dir)
// a document could extend its own search path between one declaration and
// the next. Nothing intended that -- it fell out of a fresh getenv and an
// in-process interpreter.
- static const std::string paths = [] {
- const char* env = std::getenv("KLAMMERTEXT_KLAMMERSETS");
- if (env && *env) {
- return std::string(env);
- }
- if (const char* home = std::getenv("HOME"); home && *home) {
- return std::string(home) + "/.klammertext/klammersets";
- }
- return std::string();
- }();
- std::stringstream ss(paths);
- std::string dir;
- while (std::getline(ss, dir, ':')) {
- if (!dir.empty()) {
- push_unique(dir);
- }
+ static const strings_t installed =
+ directory_list("KLAMMERTEXT_KLAMMERSETS", ".klammertext/klammersets");
+ for (const std::string& dir : installed) {
+ push_unique(dir);
}
if (const char* kthome = std::getenv(klammertext_home_var.c_str()); kthome && *kthome) {
push_unique(kthome);
@@ -280,11 +268,11 @@ std::string describe_klammerset_search(const std::string& local_dir, int margin)
if (!is_klammerset_symbol(symbol) || !fs::is_regular_file(declaration)) {
continue;
}
- std::string line = tab + symbol + sp_arrow + declaration.string();
+ std::string line = tab + symbol + sp_arrow + declaration.generic_string();
if (first_hit.count(symbol)) {
line += " (shadowed by " + first_hit[symbol] + ")";
} else {
- first_hit[symbol] = declaration.string();
+ first_hit[symbol] = declaration.generic_string();
}
// What a set IS, under where it lives: the symbol and the path
// say which file would load, and nothing at all about whether
diff --git a/mac/locator.cpp b/mac/locator.cpp
index beb5e1b..c009d44 100644
--- a/mac/locator.cpp
+++ b/mac/locator.cpp
@@ -12,14 +12,14 @@ std::string abbreviate_location(
{
std::string result = location;
fs::path basename = fs::path(location).filename();
- if (sks_commands.contains(basename)) {
- result = basename;
+ if (sks_commands.contains(basename.string())) {
+ result = basename.string();
} else {
if (make_map) {
if (relpath.count(location)) {
result = relpath[location];
} else {
- result = fs::relative(location, fs::current_path());
+ result = fs::relative(location, fs::current_path()).generic_string();
relpath[location] = result;
}
}
@@ -29,7 +29,7 @@ std::string abbreviate_location(
Locator::Locator(const fs::path& filename, int line, int chr)
- : m_filename(filename)
+ : m_filename(filename.generic_string())
, m_line(line)
, m_chr(chr)
{
@@ -49,7 +49,7 @@ std::string Locator::str(bool relative) const
// relative_pathname(m_filename, fs::current_path().string()) << ":"
std::string filename = m_filename;
if (relative) {
- filename = relative_to_cwd(m_filename);
+ filename = relative_to_cwd(m_filename).generic_string();
}
std::stringstream ss {};
ss << "[" << filename << ":"
@@ -63,7 +63,7 @@ std::string Locator::desc(bool relative) const
// relative_pathname(m_filename, fs::current_path().string()) << ":"
std::string filename = m_filename;
if (relative) {
- filename = relative_to_cwd(m_filename);
+ filename = relative_to_cwd(m_filename).generic_string();
}
std::stringstream ss {};
ss << filename << ", line " << m_line << ", character " << m_chr + 1;
diff --git a/mac/locator.h b/mac/locator.h
index c64866e..b8222b1 100644
--- a/mac/locator.h
+++ b/mac/locator.h
@@ -19,7 +19,7 @@ public:
// toolchains (e.g. Homebrew GCC on macOS); guard against
// fs::path(nullptr) -> strlen(NULL).
: m_filename(location.file_name()
- ? fs::absolute(location.file_name()) : fs::path{})
+ ? fs::absolute(location.file_name()).generic_string() : std::string{})
, m_line(int(location.line()))
, m_chr(int(location.column()))
{};
diff --git a/mac/machine.cpp b/mac/machine.cpp
index 53fefe6..a139c44 100644
--- a/mac/machine.cpp
+++ b/mac/machine.cpp
@@ -491,7 +491,7 @@ void Machine::read(const fs::path& pathname)
// :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());
- input(string_from_file(pathname), pathname.string());
+ input(string_from_file(pathname.string()), pathname.generic_string());
m_sources.push_back(pathname);
}
@@ -519,7 +519,7 @@ void Machine::expand_read_katoms(
bool klammers, bool eval, bool cond, bool read)
{
(void)K::log(3);
- current_filename = resolve_relative_to(current_filename);
+ current_filename = resolve_relative_to(current_filename).generic_string();
// msg() << "current_filename: " << current_filename << "\n";
if (std::find_if(katoms.begin(), katoms.end(), begin_read) != katoms.end()) {
(void)K::log(3);
@@ -553,11 +553,11 @@ void Machine::expand_read_katoms(
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_lines(line_split(text), input_filename);
+ katom_list ks = katomize_lines(line_split(text), input_filename.generic_string());
// ks =
process_katoms(
// ks, command_pathname,
- ks, input_filename,
+ ks, input_filename.generic_string(),
nonascii, literal, ignore, whitespace, klammers, eval, cond, read);
katoms.insert(end, ks.begin(), ks.end());
}
@@ -640,7 +640,7 @@ void Machine::load_klammerset_files(const Klammerset& klammerset, katom_iter ins
std::vector filenames {};
for (const auto& required : klammerset.m_requires) {
std::string path = is_klammerset_symbol(required)
- ? resolve_klammerset_symbol(required, base_dir, klammerset.m_loc).string()
+ ? resolve_klammerset_symbol(required, base_dir, klammerset.m_loc).generic_string()
: required;
// ALREADY LOADED? Decided BEFORE the file is opened. The guard used
// to sit in Klammerset_registry::add, which only runs once the file has
@@ -682,11 +682,11 @@ void Machine::load_klammerset_files(const Klammerset& klammerset, katom_iter ins
klammerset.m_loc);
}
pathname = fs::canonical(pathname);
- loaded_files.push_back(pathname.string());
+ loaded_files.push_back(pathname.generic_string());
m_state.add_search_dir(pathname.parent_path().string());
std::string text = trim_right(string_from_file(pathname.string()));
- katom_list ks = katomize_lines(line_split(text), pathname);
- process_katoms(ks, pathname);
+ katom_list ks = katomize_lines(line_split(text), pathname.generic_string());
+ process_katoms(ks, pathname.generic_string());
loaded.insert(loaded.end(), ks.begin(), ks.end());
}
m_katoms.insert(insert_at, loaded.begin(), loaded.end());
diff --git a/mac/state.cpp b/mac/state.cpp
index 9932b27..ab29f83 100644
--- a/mac/state.cpp
+++ b/mac/state.cpp
@@ -357,7 +357,9 @@ std::string State::python_code()
m_search_dirs.begin(), m_search_dirs.end());
for (const auto& d : python_dirs) {
if (directory_has_python_file(d)) {
- ss << "sys.path.append('" << d << "')\n";
+ // A Python string literal, not the raw text: a Windows directory
+ // holds backslashes, and C:\Users reads as a \U unicode escape.
+ ss << "sys.path.append(" << pyformat_string({d}) << ")\n";
}
}
if (!m_frames.empty()) {
diff --git a/mac/util.cpp b/mac/util.cpp
index 5b47c85..ce894e0 100644
--- a/mac/util.cpp
+++ b/mac/util.cpp
@@ -1,3 +1,8 @@
+#include
+#include
+#include "error.h"
+#include "file.h"
+#include "locator.h"
#include
#include
#include
@@ -621,11 +626,51 @@ std::string freplace(const std::string& src, const std::regex& pattern,
return result;
}
+// Shell_command -- see util.h. On Windows the script goes to a file under
+// the temp directory (mkstemp, so concurrent runs do not collide), and the
+// line is """ sh "