Bulk text replacement in files using dictionary mappings.
Replace text across multiple files using find/replace dictionaries.
- Reversible: apply a dictionary forward (keys-to-values) or backward (values-to-keys) — encode/decode, rename/undo
- Swap-safe: all replacements happen in a single pass, so
{"foo": "bar", "bar": "foo"}swaps cleanly and chains never cascade - Whole-word mode:
-wkeeps a key likecatfrom matching insidecategory— built for renaming identifiers - Safe by default: dry-run diffs before you commit, automatic backups with
-b
pip install textswap- Create a config file (
config.json):
{
"dictionaries": {
"example": {
"old_text": "new_text",
"foo": "bar"
}
},
"ignore_extensions": [".exe", ".bin"],
"ignore_directories": ["node_modules", ".git"],
"ignore_file_prefixes": [".", "_"]
}- Run:
textswap -f ./my_folder -d 1# Interactive mode
textswap
# With options
textswap --folder ./src --direction 1 --config my_config.json
# Dry run (preview changes with diff output)
textswap -f ./src -d 1 --dry-run
# Back up originals before modifying
textswap -f ./src -d 1 -b ./backups
# Only match whole words (won't touch "category" when renaming "cat")
textswap -f ./src -d 1 -w
# Reverse direction (values-to-keys)
textswap -f ./src -d 2| Option | Short | Description |
|---|---|---|
--folder |
-f |
Folder to process |
--direction |
-d |
1 = keys-to-values, 2 = values-to-keys |
--config |
-c |
Path to config file (default: config.json) |
--dict-name |
-n |
Dictionary name (auto-selects if only one) |
--backup-dir |
-b |
Copy originals here (mirroring folder structure) before modifying |
--whole-words |
-w |
Only match keys as whole words (not inside identifiers) |
--dry-run |
Preview changes without modifying files |
{
"dictionaries": {
"my_replacements": {
"find_this": "replace_with_this",
"old": "new"
}
},
"ignore_extensions": [".exe", ".dll"],
"ignore_directories": ["node_modules", "venv"],
"ignore_file_prefixes": [".", "_"]
}- Load config: Reads your JSON config file containing replacement dictionaries
- Walk directory: Recursively traverses the target folder
- Filter files: Skips files matching ignore rules (extensions, prefixes, directories)
- Read & replace: For each file, applies every mapping in a single pass — longest key wins on overlaps, and replaced text is never re-replaced
- Write back: Backs up originals (if
--backup-diris set), then saves modified files (or shows diff in dry-run mode)
All files are processed as UTF-8. Non-UTF-8 files are automatically skipped with a warning.
The --dry-run flag shows exactly what would change without modifying files:
$ textswap -f ./src -d 1 --dry-run
Dry run mode - no files will be modified
Would modify: ./src/example.txt
--- a/./src/example.txt
+++ b/./src/example.txt
@@ -1 +1 @@
-Hello world
+Goodbye world
Processed 5 files, 1 modifiedYou can define multiple dictionaries in your config for different replacement scenarios:
{
"dictionaries": {
"encode": {
"secret": "s3cr3t",
"password": "p4ssw0rd"
},
"localize_fr": {
"Hello": "Bonjour",
"Goodbye": "Au revoir"
}
}
}Select which dictionary to use with --dict-name:
textswap -f ./src -d 1 -n encode
textswap -f ./src -d 1 -n localize_frYour config file has a syntax error. Common issues:
- Missing commas between items
- Trailing commas (not allowed in JSON)
- Unquoted strings
Use a JSON validator to check your config.
Your config file is missing the required dictionaries key:
{
"dictionaries": {
"my_dict": {"find": "replace"}
}
}Files are skipped for these reasons (shown in output):
- Not UTF-8 encoded: Binary files or files with different encoding
- Permission denied: No read/write access to the file
Check that:
- Your search terms exactly match the file content (case-sensitive)
- Files aren't being filtered by ignore rules
- The target folder contains text files
All replacements happen in a single pass, longest key first:
- Overlapping keys (e.g., "hello" and "hello world"): the longest key wins wherever it matches.
- Replaced text is never re-replaced, so
{"cat": "dog", "dog": "wolf"}turns "cat" into "dog" (not "wolf"). - Two names can be swapped safely:
{"foo": "bar", "bar": "foo"}exchanges them in one run.
When renaming identifiers, add --whole-words so a key like cat doesn't match inside category or concat.
Direction 2 inverts the dictionary (values become keys). If two keys map to the same value, only the last mapping survives the inversion — a warning is printed when this happens.
- Encoding/decoding: Obfuscate or de-obfuscate text in files
- Localization: Batch replace text for different languages
- Refactoring: Rename variables, functions, or classes across a codebase (use
--whole-words) - Template substitution: Replace placeholders with actual values
- Migration: Update deprecated API calls or import paths
All files are read and written as UTF-8. Files that cannot be decoded as UTF-8 (binary files, files with other encodings) are automatically skipped and reported in the output.
GPL v3