Klammertext runs natively on Windows; a Windows source-install guide; the bundled sh for @eval :shell
Assembled from klammertext-dev f344a37d772c. 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.
This commit is contained in:
1 parent
f9af1ff869
commit
830b19394d
55 files changed
+1181
-152
No files matched your search
@@ -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
|
||||
|
||||
|
||||
+30
-7
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!-- Embedded into ktext, kdesc and kdiag on Windows (com/klammertext.rc,
|
||||
via windres). The one setting: the process code page is UTF-8, so the
|
||||
command line arrives as UTF-8 (not the ANSI code page, which loses
|
||||
every character outside it) and narrow file names are UTF-8 too.
|
||||
Honoured since Windows 10 1903. -->
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" name="Klammertext.Command" version="1.0.0.0"/>
|
||||
<application>
|
||||
<windowsSettings>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -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"
|
||||
@@ -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/<you>` for `C:\Users\<you>`:
|
||||
|
||||
```bash
|
||||
cd /c/Users/<you>
|
||||
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/<you>/klammertext/env/runtime.env' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
On Windows it sets `KLAMMERTEXT_HOME` and `TMPDIR` in the form Windows
|
||||
programs understand (`C:/Users/<you>/klammertext`), adds `bin/` to `PATH`,
|
||||
finds a TeX Live installed under `C:\Users\<you>\external\texlive\<year>`
|
||||
(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\<you>\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-<date>`.
|
||||
|
||||
2. In that folder, create a file `texlive.profile` with this content,
|
||||
replacing `<you>` with your Windows user name:
|
||||
|
||||
```
|
||||
selected_scheme scheme-small
|
||||
TEXDIR C:/Users/<you>/external/texlive/2026
|
||||
TEXMFLOCAL C:/Users/<you>/external/texlive/2026/texmf-local
|
||||
TEXMFSYSCONFIG C:/Users/<you>/external/texlive/2026/texmf-config
|
||||
TEXMFSYSVAR C:/Users/<you>/external/texlive/2026/texmf-var
|
||||
TEXMFHOME C:/Users/<you>/texmf
|
||||
TEXMFVAR C:/Users/<you>/.texlive2026/texmf-var
|
||||
TEXMFCONFIG C:/Users/<you>/.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\<you>\klammertext"
|
||||
setx Path "$env:Path;C:\Users\<you>\klammertext\bin;C:\Users\<you>\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\<you>\external\texlive\<year>`; 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.
|
||||
Vendored
+37
-1
@@ -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))
|
||||
Vendored
+23
-3
@@ -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\<user>, 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
|
||||
|
||||
+20
-4
@@ -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)
|
||||
+4
-2
@@ -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;
|
||||
|
||||
+1
-1
@@ -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<std::string, std::vector<std::string>> m_vectors {};
|
||||
long unsigned int m_syntax_size = 0;
|
||||
std::size_t m_syntax_size = 0;
|
||||
};
|
||||
+21
-4
@@ -1,3 +1,7 @@
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#endif
|
||||
#include <tuple>
|
||||
|
||||
#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".
|
||||
|
||||
@@ -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);
|
||||
|
||||
+4
-2
@@ -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());
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-2
@@ -10,7 +10,16 @@
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <unistd.h>
|
||||
#ifdef _WIN32
|
||||
// mingw-w64 has no <sys/wait.h>: 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 <sys/wait.h>
|
||||
#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);
|
||||
|
||||
+71
-6
@@ -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<std::string> 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<std::string> 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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
+22
@@ -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<std::string> 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
|
||||
|
||||
+25
-27
@@ -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<unsigned char>(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<Resolved_font> 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<Font_file> 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*'([^']+)')"))) {
|
||||
|
||||
@@ -160,3 +160,8 @@ void warn_unparsed_katoms(katom_list& katoms, bool warn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_show_rewrite_rules(bool on)
|
||||
{
|
||||
show_rewrite_rules = on;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -151,7 +151,7 @@ std::vector<std::string> 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,23 +171,11 @@ std::vector<std::string> 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()) {
|
||||
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
|
||||
|
||||
+6
-6
@@ -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;
|
||||
|
||||
+1
-1
@@ -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()))
|
||||
{};
|
||||
|
||||
+8
-8
@@ -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<std::string> 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());
|
||||
|
||||
+3
-1
@@ -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()) {
|
||||
|
||||
+46
-1
@@ -1,3 +1,8 @@
|
||||
#include <unistd.h>
|
||||
#include <fstream>
|
||||
#include "error.h"
|
||||
#include "file.h"
|
||||
#include "locator.h"
|
||||
#include <stdlib.h>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
@@ -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 ""<busybox>" sh "<script>"" -- the outer pair of quotes because
|
||||
// cmd /c strips the FIRST and LAST quote of a line holding more than two.
|
||||
Shell_command::Shell_command(const std::string& sh_command, [[maybe_unused]] const Locator& loc)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
fs::path busybox = fs::path(get_env_var("KLAMMERTEXT_HOME")) / "win" / "busybox" / "busybox64u.exe";
|
||||
if (!fs::exists(busybox)) {
|
||||
throw Environment_error(
|
||||
"The shell Klammertext runs sh commands with on Windows is missing: "
|
||||
+ busybox.generic_string(), loc);
|
||||
}
|
||||
std::string path = (fs::temp_directory_path() / "ktext_sh_XXXXXX").string();
|
||||
std::vector<char> tmpl(path.begin(), path.end());
|
||||
tmpl.push_back('\0');
|
||||
int fd = mkstemp(tmpl.data());
|
||||
if (fd == -1) {
|
||||
throw Environment_error("Could not create a temporary file for a shell command", loc);
|
||||
}
|
||||
close(fd);
|
||||
m_script = tmpl.data();
|
||||
std::ofstream out(m_script, std::ios::binary);
|
||||
out << sh_command << "\n";
|
||||
out.close();
|
||||
m_line = "\"\"" + busybox.string() + "\" sh \"" + m_script + "\"\"";
|
||||
#else
|
||||
m_line = sh_command;
|
||||
#endif
|
||||
}
|
||||
|
||||
Shell_command::~Shell_command()
|
||||
{
|
||||
if (!m_script.empty()) {
|
||||
std::error_code ec {};
|
||||
fs::remove(m_script, ec);
|
||||
}
|
||||
}
|
||||
|
||||
std::string exec(const char* cmd)
|
||||
{
|
||||
std::array<char, 128> buffer;
|
||||
std::string result;
|
||||
FILE* pipe = popen(cmd, "r");
|
||||
Shell_command sh_cmd(cmd, Locator::none());
|
||||
FILE* pipe = popen(sh_cmd.line().c_str(), "r");
|
||||
if (!pipe) throw std::runtime_error("popen() failed");
|
||||
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
|
||||
result += buffer.data();
|
||||
|
||||
+22
@@ -7,6 +7,8 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
|
||||
class Locator; // locator.h; a full include would cycle through util.h
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -85,6 +87,26 @@ std::pair<std::string,std::string> extract_parameter_type(const std::string& par
|
||||
std::tuple<std::string, std::string, bool> regex_split_prefix(const std::regex& pattern, const std::string& text);
|
||||
std::vector<std::string> dlist_split(const std::string& s);
|
||||
std::string get_env_var(const std::string& var);
|
||||
// A POSIX sh command as popen()/system() must receive it. On Linux and
|
||||
// macOS line() is the text itself. On Windows popen() runs cmd.exe, which
|
||||
// cannot carry a quoted string with an embedded newline, so the command is
|
||||
// written to a script file and line() runs the sh Klammertext ships
|
||||
// (win/busybox/) on that file; the file lives as long as this object. exec()
|
||||
// uses it, so every command Klammertext composes runs under sh on every
|
||||
// system; code that calls popen()/system() itself (eval.cpp,
|
||||
// tst/klammer_test) uses it too.
|
||||
class Shell_command
|
||||
{
|
||||
public:
|
||||
Shell_command(const std::string& sh_command, const Locator& loc);
|
||||
~Shell_command();
|
||||
Shell_command(const Shell_command&) = delete;
|
||||
Shell_command& operator=(const Shell_command&) = delete;
|
||||
const std::string& line() const { return m_line; }
|
||||
private:
|
||||
std::string m_line {};
|
||||
std::string m_script {}; // the script file, Windows only
|
||||
};
|
||||
std::string freplace(const std::string& src, const std::regex& pattern,
|
||||
const std::function<std::string(std::smatch)>& func);
|
||||
std::string exec(const char* cmd);
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ all : reflect.so
|
||||
# gets reflect.d, so a header change rebuilds it.
|
||||
reflect.so : $(OBJECTS) $(LIBRARY)
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(REF_LDFLAGS) $(LDFLAGS) \
|
||||
-o $@ $(OBJECTS) -lklammertext $(LDLIBS)
|
||||
-o $@ $(OBJECTS) $(KLAMMERTEXT_LIB) $(LDLIBS)
|
||||
|
||||
clean :
|
||||
rm -f $(OBJECTS) $(DEPFILES) *.so *~
|
||||
|
||||
@@ -48,7 +48,7 @@ deps :
|
||||
# Shared object - link against libklammertext.so
|
||||
document.so : $(LOCAL_OBJECTS) $(SKS_OBJECTS) $(LIBRARY) document.cpp | deps
|
||||
$(CXX) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(CXXFLAGS) $(DOC_LDFLAGS) $(LDFLAGS) \
|
||||
document.cpp -o $@ $(ALL_OBJECTS) -lklammertext $(LDLIBS)
|
||||
document.cpp -o $@ $(ALL_OBJECTS) $(KLAMMERTEXT_LIB) $(LDLIBS)
|
||||
|
||||
clean :
|
||||
rm -f $(LOCAL_OBJECTS) $(LOCAL_DEPFILES) *.so *~
|
||||
|
||||
@@ -178,7 +178,7 @@ fs::path parse_input_filename(const std::string& s, const std::string& input_dir
|
||||
Locator::none());
|
||||
}
|
||||
// The resolved path itself shows kt/ -- no label needed.
|
||||
(void)K::log(1, "Input file \"" + s + "\": " + in_kt.string());
|
||||
(void)K::log(1, "Input file \"" + s + "\": " + in_kt.generic_string());
|
||||
return in_kt;
|
||||
}
|
||||
if (p.extension() != ".kt") {
|
||||
@@ -188,7 +188,7 @@ fs::path parse_input_filename(const std::string& s, const std::string& input_dir
|
||||
return p;
|
||||
}
|
||||
fs::path in_input_dir = fs::path(input_dir) / p;
|
||||
(void)K::log(1, "Input file \"" + s + "\": " + in_input_dir.string());
|
||||
(void)K::log(1, "Input file \"" + s + "\": " + in_input_dir.generic_string());
|
||||
return in_input_dir;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ std::string insert_ids(const std::string& html_text)
|
||||
std::string Document_class::make_help_page()
|
||||
{
|
||||
(void)K::log(3);
|
||||
std::string kt_filename = klammertext_filename("doc/html_help.kt");
|
||||
std::string kt_filename = klammertext_filename("doc/html_help.kt").string();
|
||||
std::string basename = "help";
|
||||
std::string help_page;
|
||||
//if (!in_modification_order(kt_filename, output_filename, no_cache)) {
|
||||
@@ -306,14 +306,14 @@ void Document_class::process_input_files()
|
||||
size_t basename_width = 0;
|
||||
for (auto input_filename : m_files) {
|
||||
fs::path inpath = input_filename;
|
||||
if (!file_exists(inpath)) {
|
||||
if (!file_exists(inpath.string())) {
|
||||
inpath = parse_input_filename(input_filename, m_input_dir);
|
||||
}
|
||||
std::string basename = file_basename(input_filename);
|
||||
basename_width = std::max(basename_width, basename.size());
|
||||
std::string html_text;
|
||||
//if (true || !in_modification_order(inpath, cached_filename, m_no_cache)) {
|
||||
if (m_no_cache || cache_requires_update(m_cache_dir, inpath, basename)) {
|
||||
if (m_no_cache || cache_requires_update(m_cache_dir, inpath.string(), basename)) {
|
||||
Machine M = m_machine;
|
||||
M.read(inpath);
|
||||
html_text = trim(M.apply("html"));
|
||||
|
||||
@@ -36,7 +36,7 @@ std::string Document_class::tex()
|
||||
std::string cached_filename = m_cache_dir + "/" + basename;
|
||||
fs::path inpath = parse_input_filename(input_filename, m_input_dir);
|
||||
// if (!in_modification_order(input_filename, cached_filename, m_no_cache)) {
|
||||
if (m_no_cache || cache_requires_update(m_cache_dir, inpath, basename)) {
|
||||
if (m_no_cache || cache_requires_update(m_cache_dir, inpath.string(), basename)) {
|
||||
Machine M = m_machine;
|
||||
M.m_state.set("document_landscape", landscape ? "true" : "false"); // , "Landscape mode");
|
||||
M.read(inpath);
|
||||
|
||||
@@ -163,7 +163,7 @@ make_navigation_table_of_contents(std::vector<Heading> headings)
|
||||
void show_table_of_contents(
|
||||
const std::string& kt_root_filename, const std::string& title, std::vector<Heading> headings)
|
||||
{
|
||||
long unsigned int width = kt_root_filename.size();
|
||||
std::size_t width = kt_root_filename.size();
|
||||
for (Heading h : headings) {
|
||||
width = std::max(width, h.m_filename.size());
|
||||
}
|
||||
@@ -189,7 +189,7 @@ void show_table_of_contents(
|
||||
std::string cache_table_of_contents(
|
||||
const std::string& kt_root_filename, const std::string& title, std::vector<Heading> headings)
|
||||
{
|
||||
long unsigned int width = kt_root_filename.size();
|
||||
std::size_t width = kt_root_filename.size();
|
||||
for (Heading h : headings) {
|
||||
width = std::max(width, h.m_filename.size());
|
||||
}
|
||||
|
||||
+3
-1
@@ -93,7 +93,9 @@ class Image(klammer_base.Klammer_base):
|
||||
|
||||
|
||||
def tex(self):
|
||||
source = os.path.abspath(self.source)
|
||||
# Forward slashes: this path is document text (\includegraphics, an
|
||||
# html src), where a Windows backslash is a TeX command or an escape.
|
||||
source = os.path.abspath(self.source).replace(os.sep, "/")
|
||||
width = kutil.parse_width("tex", self.width, self.rel_fraction)[0]
|
||||
width = re.sub("px", "pt", width)
|
||||
#result = f'\\includegraphics[width={width}]{{{source}}}'
|
||||
|
||||
+2
-2
@@ -65,7 +65,7 @@ std::vector<std::string> sks_files_of_type(const std::string& extension, const s
|
||||
std::string msg = "File " + ext_filename + " in " + list_filename + " does not exist";
|
||||
throw Internal_error(msg);
|
||||
}
|
||||
if (in_subset(fs::path(s).stem(), subset)) {
|
||||
if (in_subset(fs::path(s).stem().string(), subset)) {
|
||||
if (dbg) msg() << " Add: " << ext_filename << "\n";
|
||||
result.push_back(ext_filename);
|
||||
}
|
||||
@@ -73,7 +73,7 @@ std::vector<std::string> sks_files_of_type(const std::string& extension, const s
|
||||
} else {
|
||||
for (auto f : get_files_in_directory(ext_dir)) {
|
||||
if (f[f.size()-1] != '~') {
|
||||
if (in_subset(fs::path(f).stem(), subset)) {
|
||||
if (in_subset(fs::path(f).stem().string(), subset)) {
|
||||
result.push_back(ext_dir + "/" + f);
|
||||
if (dbg) msg() << " Add: " << f << "\n";
|
||||
}
|
||||
|
||||
+3
-1
@@ -40,7 +40,9 @@ def klammertext_dir():
|
||||
return kdir
|
||||
|
||||
def cache_directory(relative_pathname, basename):
|
||||
directory = relative_pathname
|
||||
# Forward slashes: the path ends up in document text (\includegraphics,
|
||||
# an html src), where a Windows backslash is a TeX command or an escape.
|
||||
directory = relative_pathname.replace(os.sep, "/")
|
||||
if not os.path.isdir(directory):
|
||||
os.makedirs(directory)
|
||||
result = directory + "/_klammertext_cache/" + basename
|
||||
|
||||
@@ -302,9 +302,10 @@ namespace latex {
|
||||
|
||||
void check_for_xelatex()
|
||||
{
|
||||
std::string command = "which xelatex";
|
||||
std::string xelatex_path = trim(exec(command.c_str()));
|
||||
if (trim(xelatex_path).empty()) {
|
||||
// A PATH search, not "which" through a shell: on Windows popen() runs
|
||||
// cmd.exe, which has no which, and the answer would always be empty.
|
||||
std::string xelatex_path = find_in_path("xelatex");
|
||||
if (xelatex_path.empty()) {
|
||||
throw Environment_error(
|
||||
"The \"xelatex\" command required to make PDF files is not installed",
|
||||
Locator());
|
||||
|
||||
@@ -136,7 +136,7 @@ echo "-- the output policy: three categories, two streams --"
|
||||
# For ktext, category 3 is a DOCUMENT that may be piped, so nothing else may
|
||||
# share the stream. It did: K::log and msg() both wrote to stdout, so
|
||||
# "ktext -d -v 1 > doc.txt" put the whole argument dump inside the document.
|
||||
OUTF=$(mktemp /tmp/kout.XXXXXX); ERRF=$(mktemp /tmp/kerr.XXXXXX)
|
||||
OUTF=$(mktemp "${TMPDIR:-/tmp}"/kout.XXXXXX); ERRF=$(mktemp "${TMPDIR:-/tmp}"/kerr.XXXXXX)
|
||||
trap 'rm -f "$OUTF" "$ERRF"' EXIT
|
||||
|
||||
ktext --klammersets none -s 'hello' -d -v 1 > "$OUTF" 2> "$ERRF"
|
||||
|
||||
+2
-2
@@ -118,7 +118,7 @@ check_error() {
|
||||
FRAC='@@frac a | b : *a*/*b* @@'
|
||||
|
||||
# A file for the @read non-strictness cases (21-21b).
|
||||
READ_FIXTURE=$(mktemp /tmp/cond_read.XXXXXX)
|
||||
READ_FIXTURE=$(mktemp "${TMPDIR:-/tmp}"/cond_read.XXXXXX)
|
||||
printf 'READ-FIXTURE\n' > "$READ_FIXTURE"
|
||||
trap 'rm -f "$READ_FIXTURE"' EXIT
|
||||
|
||||
@@ -248,7 +248,7 @@ check_eq "21b. ... so a missing file in a discarded branch is not an error" "ok"
|
||||
|
||||
# An @eval side effect is the observable test: the branch either touched the
|
||||
# file or it did not.
|
||||
SIDE=$(mktemp -u /tmp/cond_side.XXXXXX)
|
||||
SIDE=$(mktemp -u "${TMPDIR:-/tmp}"/cond_side.XXXXXX)
|
||||
"$KTEXT" --klammersets none -s "@cond false | @eval :shell touch $SIDE @ | ok @" -d >/dev/null 2>&1
|
||||
if [ -f "$SIDE" ]; then
|
||||
echo "${red}FAIL${reset} 22. an @eval in a discarded branch ran"; FAIL=$((FAIL+1)); rm -f "$SIDE"
|
||||
|
||||
@@ -59,7 +59,7 @@ fi
|
||||
# string subscript then evaluates to 0, so all five fixtures overwrote one
|
||||
# slot and the whole suite compared the wrong report against the wrong test.
|
||||
# Files have no such floor and read the same on both systems.
|
||||
REPORTS=$(mktemp -d /tmp/coverage_reports.XXXXXX)
|
||||
REPORTS=$(mktemp -d "${TMPDIR:-/tmp}"/coverage_reports.XXXXXX)
|
||||
trap 'rm -rf "$REPORTS"' EXIT
|
||||
|
||||
for f in basic undecidable cycle split clean; do
|
||||
@@ -296,6 +296,12 @@ echo "-- the file column survives a symlinked KLAMMERTEXT_HOME --"
|
||||
link=$(mktemp -u)/kt_home_link
|
||||
mkdir -p "$(dirname "$link")"
|
||||
ln -s "$K" "$link"
|
||||
if [ ! -L "$link" ]; then
|
||||
# MSYS2 on Windows makes ln -s a COPY unless winsymlinks is configured;
|
||||
# a copy has no second spelling to test, so the case does not apply.
|
||||
echo "SKIP 58. a symlinked KLAMMERTEXT_HOME still shortens the path (ln -s made a copy, not a link)"
|
||||
rm -rf "$link"
|
||||
else
|
||||
out=$(KLAMMERTEXT_HOME="$link" "$link/bin/kdesc" --klammersets none \
|
||||
-i "$link/tst/coverage/basic.k" --coverage all 2>&1 \
|
||||
| grep -E '^ +@ab ')
|
||||
@@ -308,6 +314,7 @@ case "$out" in
|
||||
echo " got: [$out]"
|
||||
FAIL=$((FAIL+1));;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "====================="
|
||||
|
||||
+9
-6
@@ -91,17 +91,20 @@ check() { # check NAME FILE_A FILE_B
|
||||
}
|
||||
|
||||
# --- the shared core: API and CLI (required) -------------------------------
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "editor_test.sh: python3 not found" >&2
|
||||
# The interpreter's name: python3 on Linux and macOS; on Windows python.org
|
||||
# installs "python" only, and env/runtime.env exports PYTHON there.
|
||||
PYTHON=${PYTHON:-python3}
|
||||
if ! command -v "$PYTHON" >/dev/null 2>&1; then
|
||||
echo "editor_test.sh: $PYTHON not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/editor_driver.py" "$SHARED_DIR" "${py_args[@]}"; then
|
||||
if ! PYTHONDONTWRITEBYTECODE=1 "$PYTHON" "$FIX/editor_driver.py" "$SHARED_DIR" "${py_args[@]}"; then
|
||||
echo "editor_test.sh: the Python driver failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- the Sublime adapters resolve to the shared core -----------------------
|
||||
if PYTHONDONTWRITEBYTECODE=1 python3 -c "
|
||||
if PYTHONDONTWRITEBYTECODE=1 "$PYTHON" -c "
|
||||
import sys
|
||||
sys.path.insert(0, '$SUBLIME_DIR')
|
||||
import Klammertext, Klammertext_indent, Klammertext_align
|
||||
@@ -116,7 +119,7 @@ else
|
||||
fi
|
||||
|
||||
# --- the language server protocol test -------------------------------------
|
||||
if PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/ls_test.py" "$SHARED_DIR" "$FIX" >"$OUT/ls.log" 2>&1; then
|
||||
if PYTHONDONTWRITEBYTECODE=1 "$PYTHON" "$FIX/ls_test.py" "$SHARED_DIR" "$FIX" >"$OUT/ls.log" 2>&1; then
|
||||
echo "${green}PASS${reset} language server protocol ($(grep -c '^PASS' "$OUT/ls.log") checks)"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
@@ -255,7 +258,7 @@ fi
|
||||
mkdir -p "$OUT/vsix"
|
||||
cp -R "$EDIT_DIR/vscode" "$EDIT_DIR/shared" "$OUT/vsix/"
|
||||
if ( cd "$OUT/vsix/vscode" && ./make_vsix.sh >/dev/null 2>&1 ) && \
|
||||
PYTHONDONTWRITEBYTECODE=1 python3 -c "
|
||||
PYTHONDONTWRITEBYTECODE=1 "$PYTHON" -c "
|
||||
import sys, zipfile
|
||||
names = set(zipfile.ZipFile('$OUT/vsix/vscode/klammertext.vsix').namelist())
|
||||
need = {'extension.vsixmanifest', '[Content_Types].xml',
|
||||
|
||||
+2
-2
@@ -170,7 +170,7 @@ echo "-- and the converse: a # INSIDE literal content is CONTENT --"
|
||||
# in the same string is not yet literal while that string is scanned. "-s" is
|
||||
# processed before the input files, so this ordering is what a real document
|
||||
# has -- the klammer set is loaded first.
|
||||
LITUSE=$(mktemp /tmp/escape_lit.XXXXXX).kt
|
||||
LITUSE=$(mktemp "${TMPDIR:-/tmp}"/escape_lit.XXXXXX).kt
|
||||
printf '@lit a # b lit@\n' > "$LITUSE"
|
||||
# "-d" is required here and nowhere else in this suite: with a FILE input ktext
|
||||
# writes a file instead of displaying, so stdout would be empty.
|
||||
@@ -191,7 +191,7 @@ echo "-- 25a-e: only a DECLARED option name opens an option region --"
|
||||
# never wrote, in content promised to be read as nothing but text.
|
||||
# OPT declares one option, :flag, so both directions can be asserted.
|
||||
OPT='@@opt.k :flag.bool | t.literal : A literal klammer with an option @@ @@opt.t :: [*flag*/*t*] @@'
|
||||
OPTUSE=$(mktemp /tmp/escape_opt.XXXXXX).kt
|
||||
OPTUSE=$(mktemp "${TMPDIR:-/tmp}"/escape_opt.XXXXXX).kt
|
||||
opt_case() { # opt_case NAME EXPECTED USE
|
||||
printf '%s\n' "$3" > "$OPTUSE"
|
||||
check_eq "$1" "$2" "$OPTUSE" --klammersets none -t t -d -s "$T $OPT"
|
||||
|
||||
+10
-3
@@ -38,8 +38,8 @@ green=$'\033[32m'
|
||||
bold=$'\033[1m'
|
||||
reset=$'\033[0m'
|
||||
|
||||
OUTF=$(mktemp /tmp/eval_out.XXXXXX)
|
||||
ERRF=$(mktemp /tmp/eval_err.XXXXXX)
|
||||
OUTF=$(mktemp "${TMPDIR:-/tmp}"/eval_out.XXXXXX)
|
||||
ERRF=$(mktemp "${TMPDIR:-/tmp}"/eval_err.XXXXXX)
|
||||
trap 'rm -f "$OUTF" "$ERRF"' EXIT
|
||||
|
||||
plain() { sed 's/\x1b\[[0-9;]*m//g'; }
|
||||
@@ -232,7 +232,14 @@ fi
|
||||
# :haskell without runghc: strip PATH so the case is deterministic whether or
|
||||
# not GHC is installed; ktext itself is invoked by absolute path (which works
|
||||
# since the 2026-08-21 construct_command_pathname fix).
|
||||
env PATH=/nonexistent "$K/bin/ktext" --klammersets none \
|
||||
# A PATH without runghc. On Windows a program finds its DLLs through PATH,
|
||||
# so there it keeps the directories of libklammertext.so and python312.dll
|
||||
# -- neither holds a runghc, which is all the case needs.
|
||||
NORUNGHC_PATH=/nonexistent
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
NORUNGHC_PATH="/nonexistent:$(cygpath -u "$K")/bin:$(dirname "$(command -v python)")"
|
||||
fi
|
||||
env PATH="$NORUNGHC_PATH" "$K/bin/ktext" --klammersets none \
|
||||
-s '@eval :haskell main = putStrLn "x" @' -d >"$OUTF" 2>"$ERRF"
|
||||
hstatus=$?
|
||||
if [ $hstatus -ne 0 ] && tr '\n' ' ' < "$ERRF" | grep -qF "requires runghc"; then
|
||||
|
||||
@@ -117,10 +117,19 @@ check "2b. ... and reported at -v 1" \
|
||||
"ALPHA" "interpreting \"my file.kt\" as one filename" \
|
||||
my file.kt -d --klammersets none -v 1
|
||||
|
||||
case "$(uname -s)" in
|
||||
MINGW*|MSYS*)
|
||||
# The MSYS shell rewrites a bare "/" argument into the MSYS root
|
||||
# (C:/msys64/) before a native program sees it; a Windows user in
|
||||
# PowerShell or cmd is not affected, so the feature is not what fails.
|
||||
echo "SKIP 3. standalone / separates a filename list (the MSYS shell rewrites a bare /)";;
|
||||
*)
|
||||
check "3. standalone / separates a filename list" \
|
||||
"ALPHA
|
||||
BETA" "" \
|
||||
my file.kt / b.kt -d --klammersets none
|
||||
;;
|
||||
esac
|
||||
|
||||
check "4. space in a directory component" \
|
||||
"GAMMA" "" \
|
||||
|
||||
+9
-6
@@ -94,7 +94,10 @@ shows " 4a. -u describes the Unicode syntax" '\^0418\^' "$KDESC" -u
|
||||
# The rendered glyph, as UTF-8 BYTE escapes: macOS ships bash 3.2, which
|
||||
# expands $'\xHH' but not $'\U...' (that difference failed this suite's
|
||||
# sibling on Olion, 2026-08-23).
|
||||
shows " 4b. ... and shows the characters" $'\xf0\x93\x8a\xaa' "$KDESC" -u
|
||||
# Byte semantics for this one grep: MSYS2's grep under its UTF-8 locale does
|
||||
# not match a four-byte character that the C locale matches. Only here --
|
||||
# under LC_ALL=C a ".{72,}" width test (10a) would count bytes, not characters.
|
||||
LC_ALL=C shows " 4b. ... and shows the characters" $'\xf0\x93\x8a\xaa' "$KDESC" -u
|
||||
|
||||
echo
|
||||
echo "-- the multi-letter topics --"
|
||||
@@ -174,7 +177,7 @@ shows "27b. an unknown typeface word names the flag" 'Unrecognized typeface comm
|
||||
# ~/.klammertext/fonts yet, so an error there would fail every new user's
|
||||
# first "kdesc --typeface". A typeface an earlier directory also holds is
|
||||
# shadowed, and the LATER row says by which directory.
|
||||
FONTS=$(mktemp -d /tmp/kdesc_fonts.XXXXXX)
|
||||
FONTS=$(mktemp -d "${TMPDIR:-/tmp}"/kdesc_fonts.XXXXXX)
|
||||
shows "27c. --typeface lists the default set" 'Klammertext default typefaces' "$KDESC" --typeface
|
||||
shows "27d. a missing search directory is a row" 'directory does not exist' \
|
||||
env KLAMMERTEXT_FONTS="$FONTS/none" "$KDESC" --typeface
|
||||
@@ -201,25 +204,25 @@ echo "-- provenance: -i shows what the INPUT defines --"
|
||||
# defines; a reader of an unfamiliar document wants the custom ones it
|
||||
# carries. So "-i" restricts every listing to definitions from outside the
|
||||
# loaded klammersets -- while the klammersets stay loaded.
|
||||
PROV=$(mktemp /tmp/kdesc_prov.XXXXXX.k)
|
||||
PROV=$(mktemp "${TMPDIR:-/tmp}"/kdesc_prov.XXXXXX.k)
|
||||
printf '@@shout.k s : Say it loudly @@\n@@shout.html :: <b>*s*</b> @@\n' > "$PROV"
|
||||
shows "32. -i lists the input's own klammer" '@shout' "$KDESC" -i "$PROV" -k
|
||||
absent "33. ... and not the klammerset's" '@table' "$KDESC" -i "$PROV" -k
|
||||
shows "34. without -i the klammerset is shown" '@table' "$KDESC" -k
|
||||
# The klammerset is still LOADED: a coverage derived from a klammer it
|
||||
# supplies proves the analysis saw it even though the report does not.
|
||||
DERIVE=$(mktemp /tmp/kdesc_derive.XXXXXX.k)
|
||||
DERIVE=$(mktemp "${TMPDIR:-/tmp}"/kdesc_derive.XXXXXX.k)
|
||||
printf '@@wave.k s : Wave @@\n@@wave :: @i *s* @ @@\n' > "$DERIVE"
|
||||
shows "35. coverage is derived through the unlisted klammerset" \
|
||||
'@wave +ansi html pdf tex txt +from @i' "$KDESC" -i "$DERIVE" --coverage
|
||||
absent "36. ... and the klammerset is not reported" '@table' "$KDESC" -i "$DERIVE" --coverage
|
||||
# A klammer the input REDEFINES came from the klammerset, but the input
|
||||
# changed it -- which is exactly what a reader needs to know.
|
||||
REDEF=$(mktemp /tmp/kdesc_redef.XXXXXX.k)
|
||||
REDEF=$(mktemp "${TMPDIR:-/tmp}"/kdesc_redef.XXXXXX.k)
|
||||
printf '@@i.html ::: <em class="mine">*text*</em> @@\n' > "$REDEF"
|
||||
shows "37. a redefined klammer is the input's too" '@i' "$KDESC" -i "$REDEF" -k
|
||||
# An input that declares its own klammerset is still the input.
|
||||
DECL=$(mktemp -d /tmp/kdesc_decl.XXXXXX)
|
||||
DECL=$(mktemp -d "${TMPDIR:-/tmp}"/kdesc_decl.XXXXXX)
|
||||
mkdir -p "$DECL/own"
|
||||
printf '@@@klammerset own | A designer set @@@\n@@yell.k s : Yell @@\n@@yell.html :: <b>*s*</b> @@\n' \
|
||||
> "$DECL/own/own.k"
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ GREET='@@greet name : Hello, *name*. @@ @greet World @'
|
||||
OWNTARGET='@@@target foo | Foo output @@@ @@bar.foo : B @@ text'
|
||||
|
||||
# A file for --read to pull in.
|
||||
READFIXTURE=$(mktemp /tmp/kdiag_read.XXXXXX)
|
||||
READFIXTURE=$(mktemp "${TMPDIR:-/tmp}"/kdiag_read.XXXXXX)
|
||||
printf 'READFILE\n' > "$READFIXTURE"
|
||||
trap 'rm -f "$READFIXTURE"' EXIT
|
||||
|
||||
|
||||
@@ -247,8 +247,8 @@ check_fails "11. invalid symbol rejected" \
|
||||
|
||||
# Runtime fixtures: a document directory holding a local klammerset, and a
|
||||
# separate directory serving as a KLAMMERTEXT_KLAMMERSETS stage.
|
||||
DOCDIR=$(mktemp -d /tmp/klammerset_doc.XXXXXX)
|
||||
ENVDIR=$(mktemp -d /tmp/klammerset_env.XXXXXX)
|
||||
DOCDIR=$(mktemp -d "${TMPDIR:-/tmp}"/klammerset_doc.XXXXXX)
|
||||
ENVDIR=$(mktemp -d "${TMPDIR:-/tmp}"/klammerset_env.XXXXXX)
|
||||
trap 'rm -rf "$DOCDIR" "$ENVDIR" /tmp/klammerset_test_err.$$' EXIT
|
||||
|
||||
mkdir -p "$DOCDIR/locset" "$ENVDIR/envset" "$ENVDIR/locset"
|
||||
@@ -416,7 +416,7 @@ check_eq "23c. ... as is naming the same set twice on the command line" \
|
||||
# "kdesc --klammersets" could not be a complete answer. It could:
|
||||
# the embedded Python shares the process, so an @eval doing os.environ
|
||||
# changed what a later getenv returned. The environment is now read once.
|
||||
LATE=$(mktemp -d /tmp/klammerset_late.XXXXXX)
|
||||
LATE=$(mktemp -d "${TMPDIR:-/tmp}"/klammerset_late.XXXXXX)
|
||||
mkdir -p "$LATE/late"
|
||||
printf '@@@klammerset late | Appeared mid-run @@@\n@@@target lt | T @@@\n@@l.lt : LATE @@\n' \
|
||||
> "$LATE/late/late.k"
|
||||
|
||||
+11
-8
@@ -39,12 +39,15 @@ green=$'\033[32m'
|
||||
bold=$'\033[1m'
|
||||
reset=$'\033[0m'
|
||||
|
||||
OUTF=$(mktemp /tmp/kpy_out.XXXXXX)
|
||||
ERRF=$(mktemp /tmp/kpy_err.XXXXXX)
|
||||
WORK=$(mktemp -d /tmp/kpy_work.XXXXXX)
|
||||
OUTF=$(mktemp "${TMPDIR:-/tmp}"/kpy_out.XXXXXX)
|
||||
ERRF=$(mktemp "${TMPDIR:-/tmp}"/kpy_err.XXXXXX)
|
||||
WORK=$(mktemp -d "${TMPDIR:-/tmp}"/kpy_work.XXXXXX)
|
||||
trap 'rm -rf "$OUTF" "$ERRF" "$WORK"' EXIT
|
||||
|
||||
export PYTHONPATH="$K/kpy${PYTHONPATH:+:$PYTHONPATH}"
|
||||
# Python splits PYTHONPATH on the system's list separator: ':' on Linux and
|
||||
# macOS, ';' on Windows (an MSYS2 shell runs a native python.org Python).
|
||||
if command -v cygpath >/dev/null 2>&1; then sep=';'; else sep=':'; fi
|
||||
export PYTHONPATH="$K/kpy${PYTHONPATH:+$sep$PYTHONPATH}"
|
||||
|
||||
# A DEBUG=1 build instruments the module with AddressSanitizer, and an
|
||||
# instrumented extension imported into an uninstrumented python needs the
|
||||
@@ -57,9 +60,9 @@ export PYTHONPATH="$K/kpy${PYTHONPATH:+:$PYTHONPATH}"
|
||||
# the engine throws dies with "CHECK failed: asan_interceptors.cpp ...
|
||||
# real___cxa_throw != 0" (every error case of this suite, 2026-09-08).
|
||||
# Linux only (LD_PRELOAD); macOS ASan runs are not supported here.
|
||||
MODULE=$(ls "$K"/kpy/klammertext.*.so 2>/dev/null | head -1)
|
||||
MODULE=$(ls "$K"/kpy/klammertext.*.so "$K"/kpy/klammertext.*.pyd 2>/dev/null | head -1)
|
||||
if [ -z "$MODULE" ]; then
|
||||
echo "${red}FAIL${reset} kpy/ is not built (no kpy/klammertext.*.so)"
|
||||
echo "${red}FAIL${reset} kpy/ is not built (no kpy/klammertext.*.so or .pyd)"
|
||||
exit 1
|
||||
fi
|
||||
if nm -D "$MODULE" 2>/dev/null | grep -q '__asan_init'; then
|
||||
@@ -200,7 +203,7 @@ import os, klammertext as kt
|
||||
M = kt.Machine(klammersets="none", target="txt", files=["doc.kt"])
|
||||
M.read_files()
|
||||
out = M.output_filename
|
||||
print("files", M.apply(), "|", os.path.basename(out), "|", os.path.dirname(out) == os.getcwd())
|
||||
print("files", M.apply(), "|", os.path.basename(out), "|", os.path.normpath(os.path.dirname(out)) == os.getcwd())
|
||||
EOF
|
||||
expect " 7. files= and read_files(); output_filename beside the input" 'files hello from a file \| doc.txt \| True'
|
||||
|
||||
@@ -219,7 +222,7 @@ import os, klammertext as kt
|
||||
M = kt.Machine(klammersets="none", target="txt", files=["doc.kt"], output="out/renamed")
|
||||
M.read_file("doc.kt")
|
||||
M.apply()
|
||||
print("output", os.path.relpath(M.write()), os.path.exists("out/renamed.txt"))
|
||||
print("output", os.path.relpath(M.write()).replace(os.sep, "/"), os.path.exists("out/renamed.txt"))
|
||||
EOF
|
||||
expect " 9. output= is -o: directory plus basename" 'output out/renamed.txt True'
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ reset=$'\033[0m'
|
||||
# same directory and fail on the Mac only. `pwd -P` is POSIX; macOS has no
|
||||
# GNU realpath by default.
|
||||
T="$(cd "$(mktemp -d)" && pwd -P)"
|
||||
# Under MSYS2 the physical path comes back in POSIX form (/tmp/...), which a
|
||||
# native ktext cannot open; the Windows spelling is what the fixtures and the
|
||||
# expectations must carry.
|
||||
command -v cygpath >/dev/null 2>&1 && T="$(cygpath -m "$T")"
|
||||
trap 'rm -rf "$T"' EXIT
|
||||
|
||||
mkdir -p "$T/seta" "$T/setb" "$T/elsewhere"
|
||||
@@ -51,6 +55,9 @@ check() { # check NAME EXPECTED ARGS...
|
||||
local name="$1" expected="$2"; shift 2
|
||||
local out
|
||||
out=$(ktext "$@" --klammersets none -d 2>/dev/null)
|
||||
# A working directory Python reports on Windows has backslashes; the
|
||||
# expectation, built from the shell's Windows-form path, has slashes.
|
||||
case "$(uname -s)" in MINGW*|MSYS*) out=${out//\\//};; esac
|
||||
if [ "$out" = "$expected" ]; then
|
||||
echo "${green}PASS${reset} $name"; PASS=$((PASS + 1))
|
||||
else
|
||||
|
||||
@@ -38,9 +38,9 @@ green=$'\033[32m'
|
||||
bold=$'\033[1m'
|
||||
reset=$'\033[0m'
|
||||
|
||||
OUTF=$(mktemp /tmp/verb_out.XXXXXX)
|
||||
ERRF=$(mktemp /tmp/verb_err.XXXXXX)
|
||||
TMPD=$(mktemp -d /tmp/verb_dir.XXXXXX)
|
||||
OUTF=$(mktemp "${TMPDIR:-/tmp}"/verb_out.XXXXXX)
|
||||
ERRF=$(mktemp "${TMPDIR:-/tmp}"/verb_err.XXXXXX)
|
||||
TMPD=$(mktemp -d "${TMPDIR:-/tmp}"/verb_dir.XXXXXX)
|
||||
trap 'rm -f "$OUTF" "$ERRF"; rm -rf "$TMPD"' EXIT
|
||||
|
||||
plain() { sed 's/\x1b\[[0-9;]*m//g'; }
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
--- A note on GPL versions
|
||||
|
||||
BusyBox is distributed under version 2 of the General Public License (included
|
||||
in its entirety, below). Version 2 is the only version of this license which
|
||||
this version of BusyBox (or modified versions derived from this one) may be
|
||||
distributed under.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,28 @@
|
||||
# BusyBox for Windows — the shell behind `@eval :shell` on Windows
|
||||
|
||||
`busybox64u.exe` is [BusyBox for Windows](https://frippery.org/busybox/)
|
||||
(busybox-w32) by Ron Yorston, release **FRP-6075-g169694ebd** of
|
||||
2026-05-07, the 64-bit Unicode build, downloaded from
|
||||
`https://frippery.org/files/busybox/busybox64u.exe` on 2026-09-24.
|
||||
|
||||
sha256 6e263d154d8548d1eb936f65d1d8312c80df31c45974e48d6335e4dcc0f4f34c
|
||||
size 675840 bytes
|
||||
|
||||
## Why it is here
|
||||
|
||||
On Linux and macOS `popen()` runs a `:shell` command with `/bin/sh`, so
|
||||
`@eval :shell …` has always meant a POSIX shell command. On Windows
|
||||
`popen()` runs `cmd.exe`, a different language, and the same document would
|
||||
mean something else. Klammertext therefore runs `:shell` commands on
|
||||
Windows with this `sh` (`mac/eval.cpp`, `shell_command_line()`), and a
|
||||
document's `:shell` command means the same thing on every system.
|
||||
|
||||
## Licence
|
||||
|
||||
BusyBox is distributed under the GNU General Public License version 2
|
||||
(`LICENSE`, the copy from the busybox-w32 repository). Klammertext invokes
|
||||
it as a separate program and does not link against it. The corresponding
|
||||
source is the busybox-w32 repository, `https://github.com/rmyorston/busybox-w32`,
|
||||
at the release above; anyone who receives this binary from a Klammertext
|
||||
distribution is entitled to that source under the GPL, and this file is the
|
||||
written offer's pointer.
|
||||
Binary file not shown.
Reference in new issue
Block a user