changeprefix.py 5.3 KB

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