onpath 0.2.0

Get your tools on the PATH — cross-shell, cross-platform, zero fuss
Documentation

onpath

Get your tools on the PATH — cross-shell, cross-platform, zero fuss.

Built for CLI tool installers that need to persistently add a directory to the user's PATH. Detects installed shells, writes the right config for each, and removes everything cleanly on uninstall. Linux, macOS, Windows.

Quick start

// Add ~/.myapp/bin to PATH for all detected shells
let report = onpath::add("/home/user/.myapp/bin", "myapp")?;
println!("{report}");
// [sh]   wrote env script: /home/user/.myapp/env
// [sh]   added source line to /home/user/.profile
// [Bash] wrote env script: /home/user/.myapp/env
// [Bash] added source line to /home/user/.bashrc
// [Zsh]  wrote env script: /home/user/.myapp/env
// [Zsh]  added source line to /home/user/.zshenv

To undo everything during uninstall:

let report = onpath::remove("/home/user/.myapp/bin", "myapp")?;

Env scripts land in the parent of the directory you pass in (~/.myapp/ for ~/.myapp/bin). Override with .env_dir() on the builder.

Shells: Bash, Zsh, Fish, Nushell, PowerShell, Tcsh, Xonsh, POSIX sh. Windows: Modifies HKCU\Environment\PATH in the registry directly.

How it works

On Unix, onpath uses the same two-layer approach as rustup:

1. Writes a self-guarding env script that only adds the directory if it's not already in PATH:

#!/bin/sh
# Generated by onpath. Do not edit.
case ":${PATH}:" in
    *:"/home/user/.myapp/bin":*)
        ;;
    *)
        export PATH="/home/user/.myapp/bin:$PATH"
        ;;
esac

Fish, Nushell, PowerShell, Tcsh, and Xonsh each get a script in their native syntax (.fish, .nu, .ps1, etc.). Fish scripts go directly into ~/.config/fish/conf.d/ (auto-loaded, no source line needed).

2. Adds a source line to the shell's RC file, wrapped in markers for clean removal:

# >>> onpath:myapp >>>
. "/home/user/.myapp/env"
# <<< onpath:myapp <<<

Multiple tools can coexist — each gets its own marker block. Adding the same directory twice is a no-op.

On Windows, onpath reads the raw PATH from HKCU\Environment, prepends or appends the directory, writes it back, and broadcasts WM_SETTINGCHANGE so running programs pick up the change. It preserves unexpanded %VARIABLES% like %USERPROFILE% and the original registry value type (REG_SZ vs REG_EXPAND_SZ).

Builder API

For more control, use PathManager:

use onpath::{PathManager, Position};

let report = PathManager::new("/home/user/.myapp/bin", "myapp")
    .env_dir("/home/user/.myapp")  // where to write env scripts (default: dir.parent())
    .position(Position::Append)    // default is Prepend
    .backup(true)                  // back up RC files before modifying (default)
    .dry_run(true)                 // preview without writing anything
    .allow_relative(false)         // reject relative paths (default)
    .add()?;

for action in &report.actions {
    println!("{action}");
}

Inspecting results

The returned Report contains a list of Action values describing what happened. Use report.has() to check for specific outcomes:

use onpath::ActionKind;

if report.has(ActionKind::SourceLineAdded) {
    println!("New shells configured — restart your shell to pick up changes.");
}

Input validation

onpath validates all inputs before writing anything:

  • Tool names must match [a-zA-Z0-9_.-]+ (non-empty).
  • Paths must be absolute (unless .allow_relative(true) is set), valid UTF-8, and must not contain shell-dangerous characters (", `, $, \) on Unix.

Invalid inputs return descriptive errors rather than producing broken shell scripts.

Concurrency safety

RC file modifications are serialized with advisory file locking (flock(2) on Unix, a named mutex on Windows) to prevent corruption when multiple installers run concurrently.

Shell detection

Shells are auto-detected by checking for existing RC files and the $SHELL environment variable. POSIX sh (.profile) is always included on Unix. No configuration needed.

Features

Feature Description
tracing Enables structured logging via the tracing crate. Disabled by default.
onpath = { version = "0.1", features = ["tracing"] }

Install

[dependencies]
onpath = "0.2"

MSRV: 1.71 (edition 2021)

License

MIT OR Apache-2.0