Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fbe8859
Mac bare metal install: psycopg2-binary, async cli_entry wrapper, con…
tarahmarie Feb 19, 2026
a241a5a
Update install.sh for Mac bare metal installation
tarahmarie Feb 19, 2026
29768d3
looks liek this works for more corpora
tarahmarie Feb 20, 2026
e588ba6
Add banality_finder.py macOS wc whitespace patch to install script
tarahmarie Feb 20, 2026
3b9ee45
Add .gitignore, remove corpus texts and temp files from tracking
tarahmarie Feb 21, 2026
42a5c9e
Add .gitignore: exclude corpus data, macOS artifacts, backup files
tarahmarie Feb 21, 2026
71cbf53
Merge pull request #1 from tarahmarie/mac-bare-metal
tarahmarie Feb 21, 2026
1cce403
describe mac bare metal process and tag for thesis usage
tarahmarie Apr 29, 2026
d5565e0
Merge pull request #2 from tarahmarie/mac-bare-metal
tarahmarie Apr 29, 2026
af2ce5b
Installtion and configuration updates. Install script installs needed…
karmichand Jul 13, 2026
7357d6f
Merge pull request #3 from tarahmarie/install-fix
tarahmarie Jul 13, 2026
6ef0557
Install.sh script rewrite for a bare metal mac
karmichand Jul 13, 2026
0a4e57a
Merge pull request #4 from tarahmarie/install-fix
tarahmarie Jul 13, 2026
c423f08
Install.sh scaffold and config setup
karmichand Jul 13, 2026
0b93f90
Merge branch 'master' into local-install-fix2
karmichand Jul 13, 2026
d43e76a
Merge pull request #5 from tarahmarie/local-install-fix2
karmichand Jul 13, 2026
870c8f3
multithread update
karmichand Jul 13, 2026
4d2238c
Merge pull request #6 from tarahmarie/multithread
karmichand Jul 13, 2026
82a30e0
text_setup.sh
karmichand Jul 13, 2026
973d62a
Merge pull request #7 from tarahmarie/multithread
karmichand Jul 13, 2026
f2de8d1
Make ngram generation safe for paths containing spaces
tarahmarie Aug 28, 2026
d43ee19
Drop stale field names from the alignment config echo
tarahmarie Aug 28, 2026
c34627c
Remove machine-local paths from my_config.ini and text_setup.sh
tarahmarie Aug 28, 2026
db58e82
Stop tracking the compiled compareNgrams binary
tarahmarie Aug 28, 2026
b28028b
Merge pull request #8 from tarahmarie/upstream-fixes
tarahmarie Aug 28, 2026
7db6514
Extend path-space safety to the core alignment pipeline
tarahmarie Aug 28, 2026
aea09ee
Untrack fork-local run config and setup script; align egg-info with u…
tarahmarie Aug 28, 2026
0a1cc41
Merge pull request #9 from tarahmarie/pr-cleanup
tarahmarie Aug 28, 2026
7e48340
Merge pull request #10 from tarahmarie/space-safety-core
tarahmarie Aug 28, 2026
e84c596
Merge upstream/master: adopt upstream config split, uv install, and r…
tarahmarie Aug 28, 2026
1f16474
Merge pull request #11 from tarahmarie/sync-upstream
tarahmarie Aug 28, 2026
2267461
Update macOS docs and installer for the post-merge layout
tarahmarie Aug 28, 2026
44045c0
Merge pull request #12 from tarahmarie/readme-macos
tarahmarie Aug 28, 2026
ad697c8
Update README.md
tarahmarie Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10734,3 +10734,13 @@ web-app/appConfig.json
web-app/stats.json
appConfig.json
banality_finder.py.lprof

# macOS
.DS_Store

# Backup files
*.bak
in-and-out/
lib/core/src/compareNgrams/compareNgrams
my_config.ini
text_setup.sh
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11.15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why hardcode to 3.11? The original install.sh lets you choose your Python executable which gives you more flexibility. I'm also planning on actually having uv install python directly (Python 3.12 most likely) to avoid any issues with Python versions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its an issue with the way python is upgrading on different systems, Im game to try some alts.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, the hardcode will break stuff

32 changes: 31 additions & 1 deletion README.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if compareNgrams should just be compiled by default. Adding Go as a dependency is not a huge ask anyway.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember, i have to get back in and check.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because I made the edit.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"Nous ne faisons que nous entregloser" Montaigne wrote famously in his <i>Essais</i>... Since all we do is glose over what's already been written, we may as well build a tool to detect these intertextual relationships...
"Nous ne faisons que nous entregloser" Montaigne wrote famously in his <i>Essais</i>... Since all we do is gloss over what's already been written, we may as well build a tool to detect these intertextual relationships...


# TextPAIR (Pairwise Alignment for Intertextual Relations)

Expand Down Expand Up @@ -30,6 +31,35 @@ The recommended install is to build your own Docker image and run TextPAIR insid
If you do run into the issue where the web server does not respond, restart the web server with the following command:
`/var/lib/text-pair/api_server/web_server.sh &`

### macOS bare-metal installation (experimental)

TextPAIR officially supports 64-bit Linux; the Docker method above is the recommended path for production use. For local development and research runs on a Mac (Apple Silicon or Intel), an opt-in installer sets up the full sequence-alignment pipeline natively, without Docker:

```console
./install_bare_metal_mac.sh
```

**Prerequisite:** [Homebrew](https://brew.sh). The script stops with instructions if it is missing. Everything else is handled automatically:

- installs `pyenv`, Go, and `lz4` via Homebrew if absent, then installs Python 3.11 via pyenv and creates the environment TextPAIR runs in
- installs the TextPAIR Python package into that environment
- patches the installed PhiloLogic dependency's `line_count.py` (its non-lz4 code path is broken, and BSD `wc` output differs from GNU)
- builds a native `compareNgrams` binary from source with Go — the prebuilt binaries ship as Linux ELF executables and cannot run on macOS — and installs it to `/usr/local/bin` (this step asks for your password)
- seeds `~/.text-pair/global_settings.ini` and a starter `my_config.ini` (copied from `config/sa_config.ini`) if you do not already have them

Then edit `my_config.ini` (at minimum, set `source_file_path` to your corpus directory) and run:

```console
textpair --config=my_config.ini --skip_web_app --output_path=/tmp/textpair-out --workers=8 my_run_name
```

Known limitations of the macOS path:

- Avoid corpus and output paths containing spaces (a PhiloLogic limitation; note that iCloud-synced folders live under a path with spaces — copy corpora to `/tmp` or similar first).
- The PostgreSQL credentials in `~/.text-pair/global_settings.ini` are only needed if you drop `--skip_web_app` to build the web application.

This mode was developed for the corpus-scale alignment runs in Tarah Wheeler's (2026) DPhil thesis at the University of Oxford.

### Manual installation

If you wish to install TextPAIR on a host machine, note that TextPair will only run on 64 bit Linux, see below.
Expand Down
8 changes: 7 additions & 1 deletion config/global_settings.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
## DATABASE SETTINGS ##
[WEB_APP]
## Directory where generated web apps are written; irrelevant if you always pass --skip_web_app
web_app_path = /var/www/html/text-pair
## Base URL where the TextPAIR API is served
api_server = http://localhost/text-pair-api

[DATABASE]
database_name = textpair
database_user = textpair
database_password =
2 changes: 1 addition & 1 deletion docs/ubuntu_installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ sudo vim /etc/postgresql/10/pg_hba.conf

Note that the path the pg_hba.conf may vary based on your postgres version.

Fill in the database info in text-pair config: `sudo vim /etc/text-pair/config/global_settings.ini`
Fill in the database info in text-pair config: `sudo vim /etc/text-pair/global_settings.ini`

### Create webspace with proper permissions

Expand Down
317 changes: 317 additions & 0 deletions install_bare_metal_mac.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
#!/bin/bash
# TextPAIR Mac Bare-Metal Install Script
# For use without Docker, without web app
# Forked from ARTFL-Project/text-pair
#
# Usage: ./install-mac.sh

set -e

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}TextPAIR Mac Bare-Metal Installer${NC}"
echo "=================================="
echo ""

# =============================================================================
# PATCH PHILOLOGIC WC -L (macOS compatibility)
# =============================================================================
patch_philologic_wc() {
echo "Patching PhiloLogic line_count.py for macOS..."

# Find the philologic installation
local philologic_path=$("$PYTHON_BIN" -c "import philologic; print(philologic.__path__[0])" 2>/dev/null)

if [ -z "$philologic_path" ]; then
echo -e "${YELLOW} PhiloLogic not yet installed, will patch after pip install${NC}"
return 0
fi

local line_count_file="${philologic_path}/utils/line_count.py"

if [ -f "$line_count_file" ]; then
# Check if already patched
if grep -q 'wc -l < {file_path}' "$line_count_file"; then
echo " Already patched"
else
# Rewrite the entire file - the upstream non-lz4 branch is broken
# (runs cut on empty stdin instead of wc -l on the file)
cat > /tmp/_line_count_patch.py << 'PATCH'
#!/usr/bin/env python3
"""Count number of lines in a file using subprocess module."""
import subprocess
def count_lines(file_path, lz4=False):
"""Count number of lines in a file."""
if lz4:
cmd = f"lz4 -dc {file_path} | wc -l"
else:
cmd = f"wc -l < {file_path}"
process = subprocess.run(cmd, shell=True, text=True, capture_output=True)
count = int(process.stdout.strip())
return count
PATCH
sudo cp /tmp/_line_count_patch.py "$line_count_file"
rm /tmp/_line_count_patch.py
echo " Patched: rewrote line_count.py (upstream non-lz4 branch was broken)"
fi
else
echo -e "${YELLOW} line_count.py not found at expected path${NC}"
fi

echo ""
}

# =============================================================================
# ARCHITECTURE CHECK
# =============================================================================
check_architecture() {
local arch=$(uname -m)
echo "Checking architecture..."
echo " Detected: $arch"

if [ "$arch" == "arm64" ]; then
BINARY_ARCH="aarch64"
echo " Binary: aarch64 (Apple Silicon)"
elif [ "$arch" == "x86_64" ]; then
BINARY_ARCH="x86_64"
echo " Binary: x86_64 (Intel)"
else
echo -e "${RED}Unsupported architecture: $arch${NC}"
exit 1
fi
echo ""
}

# =============================================================================
# DEPENDENCY CHECK
# =============================================================================
check_dependencies() {
echo "Checking dependencies..."

# Homebrew (required to auto-install pyenv/Go below)
if command -v brew &> /dev/null; then
echo " Homebrew: found"
else
echo -e "${RED} ERROR: Homebrew not found. TextPAIR needs it to install pyenv and the Go toolchain.${NC}"
echo -e "${YELLOW} Install Homebrew first, then re-run this script:${NC}"
echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
exit 1
fi

# pyenv (guarantees a consistent, correct Python 3.11 regardless of whatever
# python3 happens to be on the ambient PATH - relying on system/Homebrew python3
# directly is what let a Python 3.9 slip past the old version check below)
if command -v pyenv &> /dev/null; then
echo " pyenv: found"
else
echo " pyenv: not found, installing with Homebrew..."
brew install pyenv
echo " pyenv installed"
fi

# Resolve the latest available Python 3.11.x via pyenv, install it if missing, and
# pin this directory to it (writes .python-version) so python3/pip here always
# resolve to 3.11, regardless of the system's default python3.
TARGET_PY_VERSION=$(pyenv install --list | grep -E '^\s*3\.11\.[0-9]+$' | tail -1 | xargs)
if [ -z "$TARGET_PY_VERSION" ]; then
echo -e "${RED} ERROR: could not find a Python 3.11.x version via pyenv${NC}"
exit 1
fi
if ! pyenv versions --bare | grep -qx "$TARGET_PY_VERSION"; then
echo " Installing Python $TARGET_PY_VERSION via pyenv (this can take a few minutes)..."
pyenv install "$TARGET_PY_VERSION"
fi
pyenv local "$TARGET_PY_VERSION"
PYTHON_BIN="$(pyenv root)/versions/$TARGET_PY_VERSION/bin/python3"
echo " Python: $("$PYTHON_BIN" --version) (pyenv, pinned to this directory via .python-version)"

# Confirm pyenv's shims are wired into the user's shell so `textpair`/`python3` keep
# resolving to this pinned version in future terminal sessions, not just this script run.
local shell_rc=""
case "$SHELL" in
*/zsh) shell_rc="$HOME/.zshrc" ;;
*/bash) shell_rc="$HOME/.bash_profile" ;;
*) shell_rc="$HOME/.profile" ;;
esac
if [ -f "$shell_rc" ] && grep -q 'pyenv init' "$shell_rc"; then
echo " pyenv shell integration: found in $shell_rc"
else
echo -e "${YELLOW} pyenv shell integration not found in $shell_rc${NC}"
echo -e "${YELLOW} Add this line to $shell_rc, then restart your terminal:${NC}"
echo ' eval "$(pyenv init -)"'
fi

# ripgrep (optional but recommended)
if command -v rg &> /dev/null; then
echo " ripgrep: $(rg --version | head -1)"
else
echo -e "${YELLOW} ripgrep: not found (optional, install with: brew install ripgrep)${NC}"
fi

# Homebrew (required to auto-install Go below)
if command -v brew &> /dev/null; then
echo " Homebrew: found"
else
echo -e "${RED} ERROR: Homebrew not found. TextPAIR needs it to install the Go toolchain (used to build compareNgrams).${NC}"
echo -e "${YELLOW} Install Homebrew first, then re-run this script:${NC}"
echo ' /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
exit 1
fi

# Go (required to build compareNgrams; the bundled binaries are Linux-only)
if command -v go &> /dev/null; then
echo " Go: $(go version)"
else
echo " Go: not found, installing with Homebrew..."
brew install go
echo " Go installed"
fi

# lz4 CLI (used to merge alignment result batches)
if command -v lz4 &> /dev/null; then
echo " lz4: found"
else
echo " lz4: not found, installing with Homebrew..."
brew install lz4
fi

echo ""
}

# =============================================================================
# INSTALL
# =============================================================================
install_textpair() {
echo "Installing TextPAIR..."

# Install textpair_llm first (local dependency)
if [ -d "lib/textpair_llm" ]; then
echo " Installing textpair_llm..."
"$PYTHON_BIN" -m pip install -e lib/textpair_llm/. --break-system-packages --quiet
fi

# Install main package
echo " Installing textpair..."
"$PYTHON_BIN" -m pip install -e lib/. --break-system-packages

echo ""
}

# =============================================================================
# SEED GLOBAL SETTINGS (user-level path, no sudo/root needed on macOS)
# =============================================================================
setup_global_settings() {
local target="$HOME/.text-pair/global_settings.ini"
echo "Setting up $target..."

if [ -f "$target" ] || [ -f /etc/text-pair/global_settings.ini ]; then
echo " Already exists, leaving as-is"
else
mkdir -p "$HOME/.text-pair"
cp config/global_settings.ini "$target"
echo " Seeded from config/global_settings.ini"
echo -e "${YELLOW} Edit $target with your actual PostgreSQL credentials (only needed if you drop --skip_web_app)${NC}"
fi

echo ""
}

# =============================================================================
# SCAFFOLD STARTER CORPUS CONFIG
# =============================================================================
scaffold_config() {
local target="my_config.ini"
echo "Setting up $target..."

if [ -f "$target" ]; then
echo " Already exists, leaving as-is ($target)"
else
cp config/sa_config.ini "$target"
echo " Seeded from config/sa_config.ini"
echo -e "${YELLOW} Edit $target and set source_file_path to your corpus directory before running textpair${NC}"
fi

echo ""
}

# =============================================================================
# INSTALL BINARY
# =============================================================================
install_binary() {
echo "Installing compareNgrams binary..."

# The prebuilt binaries under lib/core/binary are Linux ELF executables (upstream only
# targets Linux) and cannot run on macOS at all, even when the CPU architecture matches.
# Build a native Mach-O binary from source instead. Go is guaranteed present at this point
# (installed by check_dependencies if it was missing).
echo " Building compareNgrams from source with Go..."
(cd lib/core/src/compareNgrams && go build -o /tmp/compareNgrams_build .)
sudo cp /tmp/compareNgrams_build /usr/local/bin/compareNgrams
rm -f /tmp/compareNgrams_build
sudo chmod +x /usr/local/bin/compareNgrams
echo " Built and installed to /usr/local/bin/compareNgrams"

echo ""
}

# =============================================================================
# VERIFY
# =============================================================================
verify_install() {
echo "Verifying installation..."

# Check the pyenv-pinned interpreter directly, since `command -v textpair` only
# works if pyenv's shims are already wired into this shell's PATH (see the
# shell-integration note printed by check_dependencies).
local textpair_bin="$(pyenv root)/versions/$TARGET_PY_VERSION/bin/textpair"
if [ -x "$textpair_bin" ]; then
echo -e "${GREEN} textpair command found ($textpair_bin)${NC}"
else
echo -e "${RED} ERROR: textpair command not found at $textpair_bin${NC}"
exit 1
fi
if ! command -v textpair &> /dev/null; then
echo -e "${YELLOW} Note: textpair isn't on PATH in this shell yet - see the pyenv shell integration note above${NC}"
fi

if command -v compareNgrams &> /dev/null; then
echo -e "${GREEN} compareNgrams binary found${NC}"
else
echo -e "${RED} ERROR: compareNgrams binary not found${NC}"
exit 1
fi

echo ""
echo -e "${GREEN}Installation complete!${NC}"
echo ""
echo "Usage:"
echo " textpair --config=my_config.ini --skip_web_app --output_path=/tmp/textpair-out --workers=4 alignment_name"
echo ""
echo "Notes:"
echo " - Edit my_config.ini first and set source_file_path to your corpus directory"
echo " - ulimit is automatically increased on macOS (no manual fix needed)"
echo " - Use absolute paths in my_config.ini for source_file_path"
echo " - Avoid paths with spaces (copy corpus to /tmp if on iCloud)"
echo " - Input files should be TEI XML format"
echo " - Downloading a Spacy model (python -m spacy download <model>) is only needed if"
echo " you enable POS/entity filtering or spacy-based lemmatization in my_config.ini"
}

# =============================================================================
# MAIN
# =============================================================================
main() {
check_architecture
check_dependencies
install_textpair
patch_philologic_wc
setup_global_settings
scaffold_config
install_binary
verify_install
}

main "$@"
Loading