| #!/usr/bin/env python3 |
| # |
| # Change prefixes in files/filenames. Useful for creating different versions |
| # of a codebase that don't conflict at compile time. |
| # |
| # Example: |
| # $ ./scripts/changeprefix.py lfs lfs3 |
| # |
| # Copyright (c) 2022, The littlefs authors. |
| # Copyright (c) 2019, Arm Limited. All rights reserved. |
| # SPDX-License-Identifier: BSD-3-Clause |
| # |
| |
| import glob |
| import itertools |
| import os |
| import os.path |
| import re |
| import shlex |
| import shutil |
| import subprocess |
| import tempfile |
| |
| GIT_PATH = ['git'] |
| |
| |
| def openio(path, mode='r', buffering=-1): |
| # allow '-' for stdin/stdout |
| if path == '-': |
| if mode == 'r': |
| return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) |
| else: |
| return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) |
| else: |
| return open(path, mode, buffering) |
| |
| def changeprefix(from_prefix, to_prefix, line): |
| line, count1 = re.subn( |
| '\\b'+from_prefix, |
| to_prefix, |
| line) |
| line, count2 = re.subn( |
| '\\b'+from_prefix.upper(), |
| to_prefix.upper(), |
| line) |
| line, count3 = re.subn( |
| '\\B-D'+from_prefix.upper(), |
| '-D'+to_prefix.upper(), |
| line) |
| return line, count1+count2+count3 |
| |
| def changefile(from_prefix, to_prefix, from_path, to_path, *, |
| no_replacements=False): |
| # rename any prefixes in file |
| count = 0 |
| |
| # create a temporary file to avoid overwriting ourself |
| if from_path == to_path and to_path != '-': |
| to_path_temp = tempfile.NamedTemporaryFile('w', delete=False) |
| to_path = to_path_temp.name |
| else: |
| to_path_temp = None |
| |
| with openio(from_path) as from_f: |
| with openio(to_path, 'w') as to_f: |
| for line in from_f: |
| if not no_replacements: |
| line, n = changeprefix(from_prefix, to_prefix, line) |
| count += n |
| to_f.write(line) |
| |
| if from_path != '-' and to_path != '-': |
| shutil.copystat(from_path, to_path) |
| |
| if to_path_temp: |
| shutil.move(to_path, from_path) |
| elif from_path != '-': |
| os.remove(from_path) |
| |
| # Summary |
| print('%s: %d replacements' % ( |
| '%s -> %s' % (from_path, to_path) if not to_path_temp else from_path, |
| count)) |
| |
| def main(from_prefix, to_prefix, paths=[], *, |
| verbose=False, |
| output=None, |
| no_replacements=False, |
| no_renames=False, |
| git=False, |
| no_stage=False, |
| git_path=GIT_PATH): |
| if not paths: |
| if git: |
| cmd = git_path + ['ls-tree', '-r', '--name-only', 'HEAD'] |
| if verbose: |
| print(' '.join(shlex.quote(c) for c in cmd)) |
| paths = subprocess.check_output(cmd, encoding='utf8').split() |
| else: |
| print('no paths?', file=sys.stderr) |
| sys.exit(1) |
| |
| for from_path in paths: |
| # rename filename? |
| if output: |
| to_path = output |
| elif no_renames: |
| to_path = from_path |
| else: |
| to_path = os.path.join( |
| os.path.dirname(from_path), |
| changeprefix(from_prefix, to_prefix, |
| os.path.basename(from_path))[0]) |
| |
| # rename contents |
| changefile(from_prefix, to_prefix, from_path, to_path, |
| no_replacements=no_replacements) |
| |
| # stage? |
| if git and not no_stage: |
| if from_path != to_path: |
| cmd = git_path + ['rm', '-q', from_path] |
| if verbose: |
| print(' '.join(shlex.quote(c) for c in cmd)) |
| subprocess.check_call(cmd) |
| cmd = git_path + ['add', to_path] |
| if verbose: |
| print(' '.join(shlex.quote(c) for c in cmd)) |
| subprocess.check_call(cmd) |
| |
| |
| if __name__ == "__main__": |
| import argparse |
| import sys |
| parser = argparse.ArgumentParser( |
| description="Change prefixes in files/filenames. Useful for creating " |
| "different versions of a codebase that don't conflict at compile " |
| "time.", |
| allow_abbrev=False) |
| parser.add_argument( |
| 'from_prefix', |
| help="Prefix to replace.") |
| parser.add_argument( |
| 'to_prefix', |
| help="Prefix to replace with.") |
| parser.add_argument( |
| 'paths', |
| nargs='*', |
| help="Files to operate on.") |
| parser.add_argument( |
| '-v', '--verbose', |
| action='store_true', |
| help="Output commands that run behind the scenes.") |
| parser.add_argument( |
| '-o', '--output', |
| help="Output file.") |
| parser.add_argument( |
| '-N', '--no-replacements', |
| action='store_true', |
| help="Don't change prefixes in files") |
| parser.add_argument( |
| '-R', '--no-renames', |
| action='store_true', |
| help="Don't rename files") |
| parser.add_argument( |
| '--git', |
| action='store_true', |
| help="Use git to find/update files.") |
| parser.add_argument( |
| '--no-stage', |
| action='store_true', |
| help="Don't stage changes with git.") |
| parser.add_argument( |
| '--git-path', |
| type=lambda x: x.split(), |
| default=GIT_PATH, |
| help="Path to git executable, may include flags. " |
| "Defaults to %r." % GIT_PATH) |
| sys.exit(main(**{k: v |
| for k, v in vars(parser.parse_intermixed_args()).items() |
| if v is not None})) |