changeprefix.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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_TOOL = ['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. os.rename(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_tool=GIT_TOOL):
  81. if not paths:
  82. if git:
  83. cmd = git_tool + ['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, _ = changeprefix(from_prefix, to_prefix, from_path)
  98. # rename contents
  99. changefile(from_prefix, to_prefix, from_path, to_path,
  100. no_replacements=no_replacements)
  101. # stage?
  102. if git and not no_stage:
  103. if from_path != to_path:
  104. cmd = git_tool + ['rm', '-q', from_path]
  105. if verbose:
  106. print(' '.join(shlex.quote(c) for c in cmd))
  107. subprocess.check_call(cmd)
  108. cmd = git_tool + ['add', to_path]
  109. if verbose:
  110. print(' '.join(shlex.quote(c) for c in cmd))
  111. subprocess.check_call(cmd)
  112. if __name__ == "__main__":
  113. import argparse
  114. import sys
  115. parser = argparse.ArgumentParser(
  116. description="Change prefixes in files/filenames. Useful for creating "
  117. "different versions of a codebase that don't conflict at compile "
  118. "time.",
  119. allow_abbrev=False)
  120. parser.add_argument(
  121. 'from_prefix',
  122. help="Prefix to replace.")
  123. parser.add_argument(
  124. 'to_prefix',
  125. help="Prefix to replace with.")
  126. parser.add_argument(
  127. 'paths',
  128. nargs='*',
  129. help="Files to operate on.")
  130. parser.add_argument(
  131. '-v', '--verbose',
  132. action='store_true',
  133. help="Output commands that run behind the scenes.")
  134. parser.add_argument(
  135. '-o', '--output',
  136. help="Output file.")
  137. parser.add_argument(
  138. '-N', '--no-replacements',
  139. action='store_true',
  140. help="Don't change prefixes in files")
  141. parser.add_argument(
  142. '-R', '--no-renames',
  143. action='store_true',
  144. help="Don't rename files")
  145. parser.add_argument(
  146. '--git',
  147. action='store_true',
  148. help="Use git to find/update files.")
  149. parser.add_argument(
  150. '--no-stage',
  151. action='store_true',
  152. help="Don't stage changes with git.")
  153. parser.add_argument(
  154. '--git-tool',
  155. type=lambda x: x.split(),
  156. default=GIT_TOOL,
  157. help="Path to git tool to use. Defaults to %r." % GIT_TOOL)
  158. sys.exit(main(**{k: v
  159. for k, v in vars(parser.parse_intermixed_args()).items()
  160. if v is not None}))