1#!/usr/bin/env python3 2# 3# Change prefixes in files/filenames. Useful for creating different versions 4# of a codebase that don't conflict at compile time. 5# 6# Example: 7# $ ./scripts/changeprefix.py lfs lfs3 8# 9# Copyright (c) 2022, The littlefs authors. 10# Copyright (c) 2019, Arm Limited. All rights reserved. 11# SPDX-License-Identifier: BSD-3-Clause 12# 13 14import glob 15import itertools 16import os 17import os.path 18import re 19import shlex 20import shutil 21import subprocess 22import tempfile 23 24GIT_PATH = ['git'] 25 26 27def openio(path, mode='r', buffering=-1): 28 # allow '-' for stdin/stdout 29 if path == '-': 30 if mode == 'r': 31 return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering) 32 else: 33 return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering) 34 else: 35 return open(path, mode, buffering) 36 37def changeprefix(from_prefix, to_prefix, line): 38 line, count1 = re.subn( 39 '\\b'+from_prefix, 40 to_prefix, 41 line) 42 line, count2 = re.subn( 43 '\\b'+from_prefix.upper(), 44 to_prefix.upper(), 45 line) 46 line, count3 = re.subn( 47 '\\B-D'+from_prefix.upper(), 48 '-D'+to_prefix.upper(), 49 line) 50 return line, count1+count2+count3 51 52def changefile(from_prefix, to_prefix, from_path, to_path, *, 53 no_replacements=False): 54 # rename any prefixes in file 55 count = 0 56 57 # create a temporary file to avoid overwriting ourself 58 if from_path == to_path and to_path != '-': 59 to_path_temp = tempfile.NamedTemporaryFile('w', delete=False) 60 to_path = to_path_temp.name 61 else: 62 to_path_temp = None 63 64 with openio(from_path) as from_f: 65 with openio(to_path, 'w') as to_f: 66 for line in from_f: 67 if not no_replacements: 68 line, n = changeprefix(from_prefix, to_prefix, line) 69 count += n 70 to_f.write(line) 71 72 if from_path != '-' and to_path != '-': 73 shutil.copystat(from_path, to_path) 74 75 if to_path_temp: 76 os.rename(to_path, from_path) 77 elif from_path != '-': 78 os.remove(from_path) 79 80 # Summary 81 print('%s: %d replacements' % ( 82 '%s -> %s' % (from_path, to_path) if not to_path_temp else from_path, 83 count)) 84 85def main(from_prefix, to_prefix, paths=[], *, 86 verbose=False, 87 output=None, 88 no_replacements=False, 89 no_renames=False, 90 git=False, 91 no_stage=False, 92 git_path=GIT_PATH): 93 if not paths: 94 if git: 95 cmd = git_path + ['ls-tree', '-r', '--name-only', 'HEAD'] 96 if verbose: 97 print(' '.join(shlex.quote(c) for c in cmd)) 98 paths = subprocess.check_output(cmd, encoding='utf8').split() 99 else: 100 print('no paths?', file=sys.stderr) 101 sys.exit(1) 102 103 for from_path in paths: 104 # rename filename? 105 if output: 106 to_path = output 107 elif no_renames: 108 to_path = from_path 109 else: 110 to_path = os.path.join( 111 os.path.dirname(from_path), 112 changeprefix(from_prefix, to_prefix, 113 os.path.basename(from_path))[0]) 114 115 # rename contents 116 changefile(from_prefix, to_prefix, from_path, to_path, 117 no_replacements=no_replacements) 118 119 # stage? 120 if git and not no_stage: 121 if from_path != to_path: 122 cmd = git_path + ['rm', '-q', from_path] 123 if verbose: 124 print(' '.join(shlex.quote(c) for c in cmd)) 125 subprocess.check_call(cmd) 126 cmd = git_path + ['add', to_path] 127 if verbose: 128 print(' '.join(shlex.quote(c) for c in cmd)) 129 subprocess.check_call(cmd) 130 131 132if __name__ == "__main__": 133 import argparse 134 import sys 135 parser = argparse.ArgumentParser( 136 description="Change prefixes in files/filenames. Useful for creating " 137 "different versions of a codebase that don't conflict at compile " 138 "time.", 139 allow_abbrev=False) 140 parser.add_argument( 141 'from_prefix', 142 help="Prefix to replace.") 143 parser.add_argument( 144 'to_prefix', 145 help="Prefix to replace with.") 146 parser.add_argument( 147 'paths', 148 nargs='*', 149 help="Files to operate on.") 150 parser.add_argument( 151 '-v', '--verbose', 152 action='store_true', 153 help="Output commands that run behind the scenes.") 154 parser.add_argument( 155 '-o', '--output', 156 help="Output file.") 157 parser.add_argument( 158 '-N', '--no-replacements', 159 action='store_true', 160 help="Don't change prefixes in files") 161 parser.add_argument( 162 '-R', '--no-renames', 163 action='store_true', 164 help="Don't rename files") 165 parser.add_argument( 166 '--git', 167 action='store_true', 168 help="Use git to find/update files.") 169 parser.add_argument( 170 '--no-stage', 171 action='store_true', 172 help="Don't stage changes with git.") 173 parser.add_argument( 174 '--git-path', 175 type=lambda x: x.split(), 176 default=GIT_PATH, 177 help="Path to git executable, may include flags. " 178 "Defaults to %r." % GIT_PATH) 179 sys.exit(main(**{k: v 180 for k, v in vars(parser.parse_intermixed_args()).items() 181 if v is not None})) 182