Add your description here
Project description
rplc
rplc (Replace Locally) is a CLI tool for managing file and directory mirroring in development workflows. It enables developers to swap between original and custom versions of files without polluting the main repository.
Perfect for:
- Managing personal configuration overrides during development
- Testing with different configuration files without git changes
- Maintaining custom versions of files across development sessions
- Creating isolated development environments
All operations are atomic and idempotent.
Features
- File/Directory Mirroring: Swap between original and mirror versions of files and directories
- Configuration-Driven: Define mirror mappings in Markdown configuration files
- Multi-Computer Awareness: Sentinel files track which computer performed the swap, preventing conflicts
- Environment Variable Support: Use environment variables in path configurations for flexible setups
- Environment Configuration: Configure CLI options via environment variables
- Environment Integration: Automatic
.envrcmanagement with swap state tracking - Atomic Operations: Safe file operations with backup and restore capabilities
- Selective Operations: Target specific files or operate on entire configurations
Installation
From PyPI
pip install rplc
From Source
git clone https://github.com/sysid/rplc.git
cd rplc
uv sync --dev # Install with development dependencies
uv tool install -e . # Install CLI globally
Quick Start
1. Create a Configuration File
Create a configuration file (e.g., rplc-config.md). The format is strict:
# Development
## rplc-config
main/resources/application.yml
main/src/class.java
scratchdir/
Required format: The exact heading structure
# Development→## rplc-configis mandatory. Only paths listed under this section are managed. No other content must be written under this section!
2. Configure Mirror Directory
The mirror directory stores your alternative file versions. Configure it:
# Option 1: Environment variable (recommended)
export RPLC_MIRROR_DIR="/path/to/mirror_proj"
# Option 2: CLI flag
rplc swapin --mirror-dir /path/to/mirror_proj
Default: ../mirror_proj (sibling directory to your project)
3. Set up Directory Structure
Create matching directory structures in both locations:
project/ mirror_proj/
├── main/resources/ ├── main/resources/
│ └── application.yml (original) │ └── application.yml (custom)
├── main/src/ ├── main/src/
│ └── class.java (original) │ └── class.java (custom)
└── scratchdir/ └── scratchdir/
└── file.txt (original) └── file.txt (custom)
4. Swap in Mirror Versions
# Swap all configured files
rplc swapin --config rplc-config.md
# Or swap specific files only
rplc swapin main/resources/application.yml --config rplc-config.md
5. Swap Back to Originals
# Swap out all files
rplc swapout --config rplc-config.md
# Or swap out specific files only
rplc swapout main/resources/application.yml --config rplc-config.md
Working Directory Requirements
IMPORTANT: rplc must be run from within your project directory (or any subdirectory).
The tool validates that your current working directory is within the project directory to prevent accidental operations on the wrong files.
Project Detection
When no RPLC_PROJ_DIR is set, rplc looks for project markers in the current directory:
.git/- Git repository.envrc- direnv configuration.rplc- rplc marker file (create this to mark your project root)README.md,pyproject.toml,package.json- Common project files
If no markers are found, you must either:
- Set the
RPLC_PROJ_DIRenvironment variable - Use the
--proj-dirflag - Create a
.rplcmarker file in your project root
Examples
# ✓ Running from project root
cd /path/to/myproject
rplc swapin
# ✓ Running from subdirectory
cd /path/to/myproject/src
rplc swapin
# ✗ Running from parent directory (will fail)
cd /path/to
rplc swapin --proj-dir myproject # Error: not within project directory
# ✓ Using environment variable
export RPLC_PROJ_DIR=/path/to/myproject
cd /anywhere/within/myproject
rplc swapin
Environment Variables
RPLC can be configured using environment variables, which serve as defaults when command-line options are not provided:
| Environment Variable | Description | Default |
|---|---|---|
RPLC_CONFIG |
Path to configuration file | sample.md |
RPLC_PROJ_DIR |
Project directory path | Auto-detected from cwd |
RPLC_MIRROR_DIR |
Mirror directory path | ../mirror_proj |
RPLC_NO_ENV |
Disable .envrc management | false |
Example Environment Setup
# In your .bashrc, .zshrc, or project .envrc
export RPLC_CONFIG="$HOME/.config/rplc/config.md"
export RPLC_PROJ_DIR="/workspace/myproject"
export RPLC_MIRROR_DIR="/workspace/mirror_myproject"
export RPLC_NO_ENV="false"
# Now you can use rplc without specifying options
rplc swapin
rplc info
Usage
Commands
info
Display configuration information and current swap status.
rplc info [OPTIONS]
Shows:
- Configuration paths and their sources (CLI option vs environment variable)
- Configured files/directories with their current status
- Overall swap state (SWAPPED IN / NORMAL STATE)
- Environment variable status
swapin
Replace original files with mirror versions.
rplc swapin [FILES...] [OPTIONS]
Arguments:
FILES...: Specific files or directories to swap in (space-separated)
Options:
--pattern, -g: Glob pattern for file selection (e.g., ".yml", "main/**/")--exclude, -x: Exclude patterns (can be used multiple times)--proj-dir, -p: Project directory (env:RPLC_PROJ_DIR)--mirror-dir, -m: Mirror directory (env:RPLC_MIRROR_DIR)--config, -c: Configuration file (env:RPLC_CONFIG)--no-env: Disable.envrcmanagement (env:RPLC_NO_ENV)
Examples:
# Swap all configured files (using environment variables)
rplc swapin
# Swap specific files only
rplc swapin main/resources/application.yml main/src/class.java
# Swap using glob patterns
rplc swapin --pattern "*.yml"
rplc swapin --pattern "main/**/*"
# Swap all except certain files
rplc swapin --exclude "*.log" --exclude "temp/*"
# Combine specific files with exclusions
rplc swapin config/ src/ --exclude "*.backup"
# Swap with custom directories (overrides environment)
rplc swapin --proj-dir /path/to/project --mirror-dir /path/to/mirror
# Real-world examples
rplc swapin config/database.yml # Override database config
rplc swapin --pattern "config/*.yml" # All YAML configs
rplc swapin src/ --exclude "*/test/*" # Source code except tests
swapout
Restore original files and move modified versions to mirror.
rplc swapout [FILES...] [OPTIONS]
Uses same arguments and options as swapin.
Examples:
# Swap out all configured files
rplc swapout
# Swap out specific files only
rplc swapout main/resources/application.yml
# Swap out using patterns
rplc swapout --pattern "*.yml"
# Swap out all except certain files
rplc swapout --exclude "*.log"
delete
Remove files/directories from rplc management.
rplc delete [FILES...] [OPTIONS]
Important: This command only works when files are swapped out (in their original state). Use rplc swapout first if files are currently swapped in.
What it removes:
- Mirror directory content
- Backup files (
.rplc.original) - Configuration file entries
Arguments:
FILES...: Specific files or directories to remove from management (space-separated)
Options:
--pattern, -g: Glob pattern for file selection--exclude, -x: Exclude patterns (can be used multiple times)--proj-dir, -p: Project directory (env:RPLC_PROJ_DIR)--mirror-dir, -m: Mirror directory (env:RPLC_MIRROR_DIR)--config, -c: Configuration file (env:RPLC_CONFIG)--no-env: Disable.envrcmanagement (env:RPLC_NO_ENV)
Examples:
# Remove a specific file from management
rplc delete main/resources/application.yml
# Remove multiple files
rplc delete main/resources/application.yml main/src/class.java
# Remove using glob patterns
rplc delete --pattern "*.yml"
# Remove a directory
rplc delete scratchdir/
# Remove all except certain files
rplc delete --pattern "main/**/*" --exclude "*.log"
# If file is swapped in, you'll get an error:
# Error: Cannot delete - currently swapped in
# Run 'rplc swapout' first to restore original state
swapout-all
Swap out all resources across multiple projects under a base directory.
rplc swapout-all BASE_DIR [OPTIONS]
This command discovers all rplc projects by scanning for .envrc files containing RPLC_MIRROR_DIR, then swaps out any resources that are swapped in on the current host.
Arguments:
BASE_DIR: Base directory to search for rplc projects
Options:
--dry-run, -n: Show what would be done without making changes
Project Discovery:
- Scans recursively for
.envrcfiles under the base directory - Extracts
RPLC_MIRROR_DIRandRPLC_CONFIG(defaults tosample.md) - Only processes projects where the config file exists
Behavior:
- Swaps out resources that are swapped in on the current host
- Warns about resources swapped in on other hosts (doesn't fail)
- Reports summary statistics
Examples:
# Swap out all resources under ~/dev
rplc swapout-all ~/dev
# Preview what would be swapped out
rplc swapout-all ~/dev --dry-run
# Clean up before switching machines
rplc swapout-all ~/projects
Example Output:
Scanning for rplc projects under ~/dev...
Found 2 rplc project(s).
myproject:
✓ Swapped out: main/resources/application.yml
⚠ Other host 'desktop': main/src/Class.java
- Already out: config/db.yml
otherproject:
✓ Swapped out: config/settings.yml
Summary:
2 swapped out
1 on other host (skipped)
2 projects processed
Configuration Format
Configuration files must use this exact Markdown structure:
# Development
## rplc-config
path/to/file.txt
path/to/directory/
another/file.yml
$HOME/.config/app/settings.yml
${PROJECT_ROOT}/temp/cache/
Strict format: The headings
# Developmentand## rplc-configare mandatory and case-sensitive. Only paths listed under## rplc-configare managed by rplc.
Path rules:
- Paths ending with
/are treated as directories - Paths are relative to project root (unless using environment variables)
- Lines starting with
#inside the rplc-config section are treated as comments - Code blocks within the markdown file are ignored
Environment variable support:
- Use
$VARor${VAR}syntax for environment variables - Tilde (
~) expands to user's home directory - Undefined variables are left as-is (no error)
- Example:
~/${PROJECT}/config/
Environment Integration
RPLC automatically manages the RPLC_SWAPPED environment variable in .envrc files:
- swapin: Sets
export RPLC_SWAPPED=1 - swapout: Removes the variable
Disable with --no-env flag or RPLC_NO_ENV=true environment variable.
How It Works
Swap-In Process
- Check Swap State: Verifies file isn't already swapped (on this or another computer)
- Backup Original: Moves original file to
mirror_dir/path.rplc.original - Create Sentinel: Copies mirror content to
mirror_dir/path.<hostname>.rplc_active - Replace Original: Moves mirror file to original location
- Update Environment: Sets
RPLC_SWAPPED=1in.envrc
Swap-Out Process
- Store Changes: Moves modified file from original location to mirror
- Restore Original: Moves backup from
mirror_dir/path.rplc.originalto original location - Cleanup: Removes sentinel files
- Update Environment: Removes
RPLC_SWAPPEDfrom.envrc
File Structure During Operation
project/
├── file.txt # Active file (mirror content during swap-in)
└── .envrc # Contains RPLC_SWAPPED=1 during swap-in
mirror_proj/
├── file.txt # Modified content after swap-out
├── file.txt.rplc.original # Backup of original content
└── file.txt.myhost.rplc_active # Sentinel with hostname (e.g., myhost)
Swap State Tracking
rplc tracks swap state through two mechanisms:
1. Sentinel Files (.<hostname>.rplc_active)
- Purpose: Track which files are currently swapped in and on which computer
- Location: Mirror directory with
.<hostname>.rplc_activesuffix - Content: Copy of the original mirror content
- Multi-Computer: Includes hostname to prevent conflicts across machines
- Cleanup: Removed during
swap_out
Cross-Computer Protection:
- If you try to
swapina file already swapped on another computer, rplc will show an error:✗ path/to/file is swapped in on 'otherhost'. Swap out there first. - This prevents accidental overwrites when working with shared mirror directories
2. Environment Variable (RPLC_SWAPPED)
- Purpose: Global state indicator in
.envrc - Value:
export RPLC_SWAPPED=1when any files are swapped - Management: Automatically added/removed during operations
- Usage: External tools can check this variable
State Flow:
Normal State: No sentinel files, no RPLC_SWAPPED
Swapped State: Sentinel files exist, RPLC_SWAPPED=1
Troubleshooting
Common Errors
Error: "rplc must be run from within the project directory"
This means you're trying to run rplc from a directory that's not within your project.
Solutions:
cdinto your project directory or any subdirectory within it- Set
RPLC_PROJ_DIRenvironment variable to your project path - Use
--proj-dirflag (but still run from within that directory)
Warning: "Current directory doesn't appear to be a project root"
This occurs when no project markers are found in the current directory and RPLC_PROJ_DIR is not set.
Solutions:
- Ensure you're in the correct project directory
- Create a
.rplcmarker file:touch .rplc - Set
RPLC_PROJ_DIRenvironment variable - Use
--proj-dirflag explicitly
General Troubleshooting
# Show detailed help for any command
rplc --help
rplc swapin --help
# Show current configuration and status
rplc info
# Enable verbose output
rplc -v swapin
# Check which directory rplc thinks is the project
rplc info # Shows project directory in configuration table
Development
Setup
# Install dependencies
uv sync --dev
# Install pre-commit hooks
pre-commit install
# Run tests
make test
# Lint and format
make lint
make format
Project Structure
src/rplc/
├── bin/
│ ├── __init__.py
│ └── cli.py # CLI interface
└── lib/
├── __init__.py
├── config.py # Configuration parsing
├── discovery.py # Project discovery via .envrc
├── domain.py # Domain model (Project, SwapState)
└── mirror.py # Core mirroring logic
tests/
├── bin/
│ └── test_cli.py # CLI tests
├── lib/
│ ├── test_config.py # Configuration tests
│ ├── test_discovery.py # Discovery tests
│ └── test_mirror.py # Core logic tests
└── conftest.py # Test fixtures
Testing
# Run all tests with coverage
make test
Building
# Build package
make build
# Create release
make bump-patch # or bump-minor, bump-major
Requirements
- Python 3.12 or higher
- typer>=0.15.1
uvfor faster dependency managementdirenvfor automatic environment variable loading
License
BSD 3-Clause License. See LICENSE for details.
Copyright © 2024, sysid. All rights reserved.
Contributing
- Fork the repository
- Create a feature branch
- Make changes with tests
- Run
make lintandmake test - Submit a pull request
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rplc-0.9.0.tar.gz.
File metadata
- Download URL: rplc-0.9.0.tar.gz
- Upload date:
- Size: 29.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b561a2913e551d6f24f8a0192c22c5d38e628d0d737b72c459765325cb9aae0
|
|
| MD5 |
755ff2e94daf2a5dfc801452d307bc70
|
|
| BLAKE2b-256 |
8488fa1553858ccd86d4f26a0d5af92522150ef2cf71e9efd9449da2581ae057
|
File details
Details for the file rplc-0.9.0-py3-none-any.whl.
File metadata
- Download URL: rplc-0.9.0-py3-none-any.whl
- Upload date:
- Size: 25.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b59729df6d0d8c7da9821f7a7a681bd9dc246f7064a750e95e14b902f27345d6
|
|
| MD5 |
23f6af75b9302d688bac0af95589836b
|
|
| BLAKE2b-256 |
f204d6a1953db8eb06d2836341e6c19316baebaf79e65b4dce38ca3c5fdd0e37
|