mirror of
				https://github.com/kevinveenbirkenbach/bulk-string-replacer.git
				synced 2025-11-04 03:38:03 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			792732b44b
			...
			de4f9511d5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| de4f9511d5 | |||
| 5ffb317dbf | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
*__pycache__
 | 
			
		||||
							
								
								
									
										61
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								README.md
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
# Bulk String Replacer CLI (bsr) 🔄
 | 
			
		||||
[](https://github.com/sponsors/kevinveenbirkenbach) [](https://www.patreon.com/c/kevinveenbirkenbach) [](https://buymeacoffee.com/kevinveenbirkenbach) [](https://s.veen.world/paypaldonate)
 | 
			
		||||
 | 
			
		||||
[](https://github.com/sponsors/kevinveenbirkenbach) [](https://www.patreon.com/c/kevinveenbirkenbach) [](https://buymeacoffee.com/kevinveenbirkenbach) [](https://s.veen.world/paypaldonate)
 | 
			
		||||
 | 
			
		||||
[](https://www.gnu.org/licenses/agpl-3.0.en.html) [](https://github.com/kevinveenbirkenbach/bulk-string-replacer/stargazers)
 | 
			
		||||
 | 
			
		||||
@@ -10,11 +10,12 @@ Bulk String Replacer CLI (bsr) is a powerful Python-based command-line tool that
 | 
			
		||||
 | 
			
		||||
## 🛠 Features
 | 
			
		||||
 | 
			
		||||
- **Comprehensive Replacement:** Replace strings in folder names, file names, and inside file contents.
 | 
			
		||||
- **Recursive Processing:** Traverse directories recursively to update all matching files.
 | 
			
		||||
- **Hidden Files Support:** Option to include hidden files and directories.
 | 
			
		||||
- **Preview Mode:** Preview changes without modifying any files.
 | 
			
		||||
- **Verbose Output:** Display detailed logs of the operations performed.
 | 
			
		||||
* **Comprehensive Replacement:** Replace strings in folder names, file names, and inside file contents.
 | 
			
		||||
* **Recursive Processing:** Traverse directories recursively to update all matching files.
 | 
			
		||||
* **Hidden Files Support:** Option to include hidden files and directories.
 | 
			
		||||
* **Preview Mode:** Preview changes without modifying any files.
 | 
			
		||||
* **Verbose Output:** Display detailed logs of the operations performed.
 | 
			
		||||
* **Full-Path Moves:** Match an `old_string` as a relative path (including `/`) and move matching subtrees to a new location.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
@@ -36,36 +37,50 @@ This command makes the tool globally available as `bsr` in your terminal. 🚀
 | 
			
		||||
Once installed, run Bulk String Replacer CLI using the alias:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
bsr old_string --new-string "replacement_value" [options] [paths...]
 | 
			
		||||
bsr old_string -n "replacement_value" [options] [paths...]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Options
 | 
			
		||||
 | 
			
		||||
- **`old_string`**: The string to search for and replace.
 | 
			
		||||
- **`--new-string`**: The string that will replace `old_string` (default is an empty string).
 | 
			
		||||
- **`--recursive`**: Process all subdirectories and files recursively.
 | 
			
		||||
- **`--folder`**: Replace occurrences within folder names.
 | 
			
		||||
- **`--files`**: Replace occurrences within file names.
 | 
			
		||||
- **`--content`**: Replace occurrences inside file contents.
 | 
			
		||||
- **`--preview`**: Preview changes without applying them.
 | 
			
		||||
- **`--verbose`**: Display detailed logs during execution.
 | 
			
		||||
- **`--hidden`**: Include hidden files and directories in the operation.
 | 
			
		||||
* **`old_string`**: The string or relative path to search for.
 | 
			
		||||
* **`-n, --new`**: The replacement string or new relative path (default is empty string).
 | 
			
		||||
* **`-r, --recursive`**: Recurse into all subdirectories and files.
 | 
			
		||||
* **`-F, --folders`**: Replace occurrences within folder names.
 | 
			
		||||
* **`-f, --files`**: Replace occurrences within file names.
 | 
			
		||||
* **`-c, --content`**: Replace occurrences inside file contents.
 | 
			
		||||
* **`-P, --path`**: Match `old_string` as a relative path (e.g. `vars/config.yml`) and move matching subtree to `new` relative path.
 | 
			
		||||
* **`-p, --preview`**: Preview changes without applying them.
 | 
			
		||||
* **`-v, --verbose`**: Display detailed logs during execution.
 | 
			
		||||
* **`-H, --hidden`**: Include hidden files and directories in the operation.
 | 
			
		||||
 | 
			
		||||
### Example Command
 | 
			
		||||
### Examples
 | 
			
		||||
 | 
			
		||||
Replace text within filenames, folder names, and file contents:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
bsr "old_value" --new-string "new_value" --recursive --verbose /path/to/first/directory /path/to/second/directory
 | 
			
		||||
bsr "old_value" -n "new_value" -r -F -f -c /path/to/dir
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Replace `/path/to/first/directory` and `/path/to/second/directory` with the paths you wish to process.
 | 
			
		||||
Move every `vars/configuration.yml` to `config/main.yml` in each parent directory:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
bsr "vars/configuration.yml" -n "config/main.yml" -r -P ./
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Preview a full-path move without changes:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
bsr "vars/configuration.yml" -n "config/main.yml" -r -P -p ./
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## 🧑💻 Author
 | 
			
		||||
 | 
			
		||||
Developed by **Kevin Veen-Birkenbach**  
 | 
			
		||||
- 📧 [kevin@veen.world](mailto:kevin@veen.world)  
 | 
			
		||||
- 🌐 [https://www.veen.world/](https://www.veen.world/)
 | 
			
		||||
Developed by **Kevin Veen-Birkenbach**
 | 
			
		||||
 | 
			
		||||
* 📧 [kevin@veen.world](mailto:kevin@veen.world)
 | 
			
		||||
* 🌐 [https://www.veen.world/](https://www.veen.world/)
 | 
			
		||||
 | 
			
		||||
Learn more about the development of this tool in the [original ChatGPT conversation](https://chat.openai.com/share/cfdbc008-8374-47f8-8853-2e00ee27c959).
 | 
			
		||||
 | 
			
		||||
@@ -73,7 +88,7 @@ Learn more about the development of this tool in the [original ChatGPT conversat
 | 
			
		||||
 | 
			
		||||
## 📜 License
 | 
			
		||||
 | 
			
		||||
This project is licensed under the **GNU Affero General Public License, Version 3, 19 November 2007**.  
 | 
			
		||||
This project is licensed under the **GNU Affero General Public License, Version 3, 19 November 2007**.
 | 
			
		||||
For more details, see the [LICENSE](./LICENSE) file.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										205
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										205
									
								
								main.py
									
									
									
									
									
								
							@@ -3,136 +3,143 @@ import os
 | 
			
		||||
import argparse
 | 
			
		||||
 | 
			
		||||
def replace_content(path, old_string, new_string, preview, verbose):
 | 
			
		||||
    """
 | 
			
		||||
    Replace occurrences of old_string with new_string inside the file at path.
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        with open(path, 'r', encoding='utf-8') as f:
 | 
			
		||||
            content = f.read()
 | 
			
		||||
 | 
			
		||||
        if old_string in content:
 | 
			
		||||
            new_content = content.replace(old_string, new_string)
 | 
			
		||||
            
 | 
			
		||||
            print_verbose(f"Replacing content in: {path}", verbose)
 | 
			
		||||
                
 | 
			
		||||
            if not preview:
 | 
			
		||||
                with open(path, 'w', encoding='utf-8') as f:
 | 
			
		||||
                    f.write(new_content)
 | 
			
		||||
                with open(path, 'w', encoding='utf-8') as fw:
 | 
			
		||||
                    fw.write(new_content)
 | 
			
		||||
    except UnicodeDecodeError:
 | 
			
		||||
        print_verbose(f"Warning: Unicode decode error in file {path}. Skipping.", verbose)
 | 
			
		||||
 | 
			
		||||
    except UnicodeDecodeError as e:
 | 
			
		||||
        print_verbose(f"Warning: Unicode decode error encountered in file {path}. Skipping file.", verbose)
 | 
			
		||||
 | 
			
		||||
def print_verbose(content,verbose):
 | 
			
		||||
def print_verbose(message, verbose):
 | 
			
		||||
    if verbose:
 | 
			
		||||
        print(content)
 | 
			
		||||
    
 | 
			
		||||
        print(message)
 | 
			
		||||
 | 
			
		||||
def process_directory(base_path, old_string, new_string, recursive, folder, files, content, preview, verbose, hidden):
 | 
			
		||||
    # Eine Liste, um die Pfade der umzubenennenden Ordner zu speichern
 | 
			
		||||
    directories_to_rename = []
 | 
			
		||||
def process_directory(base_path, old_string, new_string, recursive,
 | 
			
		||||
                      rename_folders, rename_files, replace_in_content,
 | 
			
		||||
                      preview, verbose, include_hidden, rename_paths):
 | 
			
		||||
    """
 | 
			
		||||
    Traverse directory tree and perform operations based on flags:
 | 
			
		||||
    - replace_in_content: replace inside file contents
 | 
			
		||||
    - rename_files: rename files whose names contain old_string
 | 
			
		||||
    - rename_folders: rename folders whose names contain old_string
 | 
			
		||||
    - rename_paths: match old_string as a relative path and move matching items to new_string
 | 
			
		||||
    """
 | 
			
		||||
    # Full-path move logic
 | 
			
		||||
    if rename_paths:
 | 
			
		||||
        for root, dirs, files in os.walk(base_path):
 | 
			
		||||
            if not include_hidden:
 | 
			
		||||
                dirs[:] = [d for d in dirs if not d.startswith('.')]
 | 
			
		||||
                files = [f for f in files if not f.startswith('.')]
 | 
			
		||||
            for name in files + dirs:
 | 
			
		||||
                full_src = os.path.join(root, name)
 | 
			
		||||
                rel = os.path.relpath(full_src, base_path)
 | 
			
		||||
                if old_string in rel:
 | 
			
		||||
                    new_rel = rel.replace(old_string, new_string)
 | 
			
		||||
                    full_dst = os.path.join(base_path, new_rel)
 | 
			
		||||
                    print_verbose(f"Moving {full_src} → {full_dst}", verbose)
 | 
			
		||||
                    if not preview:
 | 
			
		||||
                        os.makedirs(os.path.dirname(full_dst), exist_ok=True)
 | 
			
		||||
                        os.rename(full_src, full_dst)
 | 
			
		||||
            if not recursive:
 | 
			
		||||
                break
 | 
			
		||||
        # Only return early when only path-mode is active
 | 
			
		||||
        if not (rename_files or replace_in_content):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
    for root, dirs, filenames in os.walk(base_path):
 | 
			
		||||
        # Wenn "hidden" nicht gesetzt ist, versteckte Dateien/Ordner aus der Liste entfernen
 | 
			
		||||
        if not hidden:
 | 
			
		||||
            dirs[:] = [d for d in dirs if not d.startswith(".")]
 | 
			
		||||
            filenames = [f for f in filenames if not f.startswith(".")]
 | 
			
		||||
    # Collect folder renames to apply after traversal
 | 
			
		||||
    folders_to_rename = []
 | 
			
		||||
    for root, dirs, files in os.walk(base_path):
 | 
			
		||||
        if not include_hidden:
 | 
			
		||||
            dirs[:] = [d for d in dirs if not d.startswith('.')]
 | 
			
		||||
            files = [f for f in files if not f.startswith('.')]
 | 
			
		||||
 | 
			
		||||
        if content:
 | 
			
		||||
            for f in filenames:
 | 
			
		||||
        # Content replacement
 | 
			
		||||
        if replace_in_content:
 | 
			
		||||
            for f in files:
 | 
			
		||||
                replace_content(os.path.join(root, f), old_string, new_string, preview, verbose)
 | 
			
		||||
 | 
			
		||||
        if files:
 | 
			
		||||
            for f in filenames:
 | 
			
		||||
        # File renaming
 | 
			
		||||
        if rename_files:
 | 
			
		||||
            for f in files:
 | 
			
		||||
                if old_string in f:
 | 
			
		||||
                    old_path = os.path.join(root, f)
 | 
			
		||||
                    new_path = os.path.join(root, f.replace(old_string, new_string))
 | 
			
		||||
                    print_verbose(f"Renaming file from: {old_path} to: {new_path}",verbose)
 | 
			
		||||
                    src = os.path.join(root, f)
 | 
			
		||||
                    dst = os.path.join(root, f.replace(old_string, new_string))
 | 
			
		||||
                    print_verbose(f"Renaming file: {src} → {dst}", verbose)
 | 
			
		||||
                    if not preview:
 | 
			
		||||
                        os.rename(old_path, new_path)
 | 
			
		||||
                        os.rename(src, dst)
 | 
			
		||||
 | 
			
		||||
        # Pfade von zu ändernden Ordnern speichern
 | 
			
		||||
        if folder:
 | 
			
		||||
        # Gather folder renames
 | 
			
		||||
        if rename_folders:
 | 
			
		||||
            for d in dirs:
 | 
			
		||||
                if old_string in d:
 | 
			
		||||
                    old_path = os.path.join(root, d)
 | 
			
		||||
                    new_path = os.path.join(root, d.replace(old_string, new_string))
 | 
			
		||||
                    directories_to_rename.append((old_path, new_path))
 | 
			
		||||
                    src = os.path.join(root, d)
 | 
			
		||||
                    dst = os.path.join(root, d.replace(old_string, new_string))
 | 
			
		||||
                    folders_to_rename.append((src, dst))
 | 
			
		||||
 | 
			
		||||
        if not recursive:
 | 
			
		||||
            break
 | 
			
		||||
 | 
			
		||||
    # Ordnernamen ändern nach dem os.walk() Durchlauf
 | 
			
		||||
    for old_path, new_path in directories_to_rename:
 | 
			
		||||
        print_verbose(f"Renaming directory from: {old_path} to: {new_path}",verbose)
 | 
			
		||||
    # Apply folder renames
 | 
			
		||||
    for src, dst in folders_to_rename:
 | 
			
		||||
        print_verbose(f"Renaming directory: {src} → {dst}", verbose)
 | 
			
		||||
        if not preview:
 | 
			
		||||
            os.rename(old_path, new_path)
 | 
			
		||||
            os.rename(src, dst)
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    parser = argparse.ArgumentParser(
 | 
			
		||||
        description="Replace strings in directories and files."
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # positional args
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        'paths',
 | 
			
		||||
        nargs='+',
 | 
			
		||||
        help="Paths in which replacements should be made."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        'old_string',
 | 
			
		||||
        help="The string to be replaced."
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # options with short and long flags
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-n', '--new-string',
 | 
			
		||||
        dest='new_string',
 | 
			
		||||
        default="",
 | 
			
		||||
        help="The string to replace with. Default is empty string."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-r', '--recursive',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help="Replace in all subdirectories and files."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-F', '--folder',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help="Replace in folder names."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-f', '--files',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help="Replace in file names."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-c', '--content',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help="Replace inside file contents."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-p', '--preview',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help="Preview changes without replacing."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-v', '--verbose',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help="Verbose mode."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '-H', '--hidden',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help="Apply to hidden files and folders."
 | 
			
		||||
        description="Bulk string replacer with optional full-path moves."
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument('paths', nargs='+', help="Base directories to process.")
 | 
			
		||||
    parser.add_argument('old_string', help="String or relative path to replace.")
 | 
			
		||||
    parser.add_argument('-n', '--new', dest='new_string', default='',
 | 
			
		||||
                        help="Replacement string or new relative path.")
 | 
			
		||||
    parser.add_argument('-r', '--recursive', action='store_true',
 | 
			
		||||
                        help="Recurse into subdirectories.")
 | 
			
		||||
    parser.add_argument('-F', '--folders', action='store_true',
 | 
			
		||||
                        help="Rename folder names.")
 | 
			
		||||
    parser.add_argument('-f', '--files', action='store_true',
 | 
			
		||||
                        help="Rename file names.")
 | 
			
		||||
    parser.add_argument('-c', '--content', action='store_true',
 | 
			
		||||
                        help="Replace inside file contents.")
 | 
			
		||||
    parser.add_argument('-p', '--preview', action='store_true',
 | 
			
		||||
                        help="Preview only; no changes.")
 | 
			
		||||
    parser.add_argument('-P', '--path', dest='rename_paths', action='store_true',
 | 
			
		||||
                        help="Match old_string as relative path and move to new_string path.")
 | 
			
		||||
    parser.add_argument('-v', '--verbose', action='store_true',
 | 
			
		||||
                        help="Verbose mode.")
 | 
			
		||||
    parser.add_argument('-H', '--hidden', action='store_true',
 | 
			
		||||
                        help="Include hidden files and folders.")
 | 
			
		||||
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Disallow using --path and --folders together
 | 
			
		||||
    if args.rename_paths and args.folders:
 | 
			
		||||
        parser.error("Cannot use --path and --folders together.")
 | 
			
		||||
 | 
			
		||||
    # Use os.path.expanduser to expand the tilde to the home directory
 | 
			
		||||
    expanded_paths = [os.path.expanduser(path) for path in args.paths]
 | 
			
		||||
    base_paths = [os.path.expanduser(p) for p in args.paths]
 | 
			
		||||
    for base in base_paths:
 | 
			
		||||
        print_verbose(f"Processing: {base}", args.verbose)
 | 
			
		||||
        process_directory(
 | 
			
		||||
            base_path=base,
 | 
			
		||||
            old_string=args.old_string,
 | 
			
		||||
            new_string=args.new_string,
 | 
			
		||||
            recursive=args.recursive,
 | 
			
		||||
            rename_folders=args.folders,
 | 
			
		||||
            rename_files=args.files,
 | 
			
		||||
            replace_in_content=args.content,
 | 
			
		||||
            preview=args.preview,
 | 
			
		||||
            verbose=args.verbose,
 | 
			
		||||
            include_hidden=args.hidden,
 | 
			
		||||
            rename_paths=args.rename_paths
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    for path in expanded_paths:
 | 
			
		||||
        print_verbose(f"Replacing in path: {path}",args.verbose)
 | 
			
		||||
        process_directory(path, args.old_string, args.new_string, args.recursive, args.folder, args.files, args.content, args.preview, args.verbose, args.hidden)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    main()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										113
									
								
								test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								test.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
import os
 | 
			
		||||
import shutil
 | 
			
		||||
import tempfile
 | 
			
		||||
import unittest
 | 
			
		||||
import argparse
 | 
			
		||||
 | 
			
		||||
from main import process_directory, replace_content, main as cli_main
 | 
			
		||||
 | 
			
		||||
class TestBulkStringReplacer(unittest.TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        # create isolated temp dir
 | 
			
		||||
        self.base = tempfile.mkdtemp()
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        shutil.rmtree(self.base)
 | 
			
		||||
 | 
			
		||||
    def create_file(self, relpath, content=''):
 | 
			
		||||
        full = os.path.join(self.base, relpath)
 | 
			
		||||
        os.makedirs(os.path.dirname(full), exist_ok=True)
 | 
			
		||||
        with open(full, 'w', encoding='utf-8') as f:
 | 
			
		||||
            f.write(content)
 | 
			
		||||
        return full
 | 
			
		||||
 | 
			
		||||
    def test_replace_content(self):
 | 
			
		||||
        f = self.create_file('foo.txt', 'hello OLD world')
 | 
			
		||||
        replace_content(f, 'OLD', 'NEW', preview=False, verbose=False)
 | 
			
		||||
        with open(f, 'r', encoding='utf-8') as fp:
 | 
			
		||||
            self.assertIn('hello NEW world', fp.read())
 | 
			
		||||
 | 
			
		||||
    def test_rename_file(self):
 | 
			
		||||
        f = self.create_file('OLDfile.txt', '')
 | 
			
		||||
        process_directory(
 | 
			
		||||
            base_path=self.base,
 | 
			
		||||
            old_string='OLD', new_string='NEW',
 | 
			
		||||
            recursive=False,
 | 
			
		||||
            rename_folders=False, rename_files=True,
 | 
			
		||||
            replace_in_content=False,
 | 
			
		||||
            preview=False, verbose=False,
 | 
			
		||||
            include_hidden=True, rename_paths=False
 | 
			
		||||
        )
 | 
			
		||||
        # file moved
 | 
			
		||||
        self.assertTrue(os.path.exists(os.path.join(self.base, 'NEWfile.txt')))
 | 
			
		||||
        self.assertFalse(os.path.exists(f))
 | 
			
		||||
 | 
			
		||||
    def test_rename_folder(self):
 | 
			
		||||
        os.makedirs(os.path.join(self.base, 'OLDfolder', 'x'))
 | 
			
		||||
        process_directory(
 | 
			
		||||
            base_path=self.base,
 | 
			
		||||
            old_string='OLD', new_string='NEW',
 | 
			
		||||
            recursive=False,
 | 
			
		||||
            rename_folders=True, rename_files=False,
 | 
			
		||||
            replace_in_content=False,
 | 
			
		||||
            preview=False, verbose=False,
 | 
			
		||||
            include_hidden=True, rename_paths=False
 | 
			
		||||
        )
 | 
			
		||||
        self.assertTrue(os.path.isdir(os.path.join(self.base, 'NEWfolder')))
 | 
			
		||||
        self.assertFalse(os.path.isdir(os.path.join(self.base, 'OLDfolder')))
 | 
			
		||||
 | 
			
		||||
    def test_full_path_move(self):
 | 
			
		||||
        # prepare vars/configuration.yml
 | 
			
		||||
        cfg = 'vars/configuration.yml'
 | 
			
		||||
        full = self.create_file(cfg, 'DATA')
 | 
			
		||||
        # run with -P
 | 
			
		||||
        process_directory(
 | 
			
		||||
            base_path=self.base,
 | 
			
		||||
            old_string='vars/configuration.yml',
 | 
			
		||||
            new_string='config/main.yml',
 | 
			
		||||
            recursive=True,
 | 
			
		||||
            rename_folders=False, rename_files=False,
 | 
			
		||||
            replace_in_content=False,
 | 
			
		||||
            preview=False, verbose=False,
 | 
			
		||||
            include_hidden=True, rename_paths=True
 | 
			
		||||
        )
 | 
			
		||||
        # original gone, new exists
 | 
			
		||||
        self.assertFalse(os.path.exists(full))
 | 
			
		||||
        self.assertTrue(os.path.exists(os.path.join(self.base, 'config', 'main.yml')))
 | 
			
		||||
 | 
			
		||||
    def test_path_and_folders_conflict(self):
 | 
			
		||||
        # simulate CLI error when combining --path and --folders
 | 
			
		||||
        parser = argparse.ArgumentParser()
 | 
			
		||||
        # replicate only the conflict check
 | 
			
		||||
        parser.add_argument('-P', dest='rename_paths', action='store_true')
 | 
			
		||||
        parser.add_argument('-F', dest='folders', action='store_true')
 | 
			
		||||
        args = parser.parse_args(['-P', '-F'])
 | 
			
		||||
        # manual conflict
 | 
			
		||||
        with self.assertRaises(SystemExit) as cm:
 | 
			
		||||
            # mimic the parser.error behavior
 | 
			
		||||
            if args.rename_paths and args.folders:
 | 
			
		||||
                parser.error("Cannot use --path and --folders together.")
 | 
			
		||||
        self.assertNotEqual(cm.exception.code, 0)
 | 
			
		||||
 | 
			
		||||
    def test_preview_does_nothing(self):
 | 
			
		||||
        # create file and folder matching
 | 
			
		||||
        f = self.create_file('OLD.txt', 'OLD')
 | 
			
		||||
        os.makedirs(os.path.join(self.base, 'OLDdir'))
 | 
			
		||||
        process_directory(
 | 
			
		||||
            base_path=self.base,
 | 
			
		||||
            old_string='OLD', new_string='NEW',
 | 
			
		||||
            recursive=True,
 | 
			
		||||
            rename_folders=True, rename_files=True,
 | 
			
		||||
            replace_in_content=True,
 | 
			
		||||
            preview=True, verbose=False,
 | 
			
		||||
            include_hidden=True, rename_paths=True
 | 
			
		||||
        )
 | 
			
		||||
        # nothing changed
 | 
			
		||||
        self.assertTrue(os.path.exists(f))
 | 
			
		||||
        self.assertTrue(os.path.isdir(os.path.join(self.base, 'OLDdir')))
 | 
			
		||||
        with open(f, 'r', encoding='utf-8') as fp:
 | 
			
		||||
            self.assertIn('OLD', fp.read())
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
		Reference in New Issue
	
	Block a user