diff --git a/utils_maintenance/autopep8_format_paths.py b/utils_maintenance/autopep8_format_paths.py
new file mode 100644
index 0000000000000000000000000000000000000000..158ec327bd42a903836726132ce7d571b236bcd5
--- /dev/null
+++ b/utils_maintenance/autopep8_format_paths.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+import sys
+import subprocess
+
+VERSION_MIN = (1, 6, 0)
+VERSION_MAX_RECOMMENDED = (1, 6, 0)
+AUTOPEP8_FORMAT_CMD = "autopep8"
+
+BASE_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
+os.chdir(BASE_DIR)
+
+
+extensions = (
+ ".py",
+)
+
+ignore_files = {
+ "release/scripts/modules/rna_manual_reference.py", # Large generated file, don't format.
+}
+
+
+def compute_paths(paths, use_default_paths):
+ # Optionally pass in files to operate on.
+ if use_default_paths:
+ paths = (
+ "build_files",
+ "release",
+ "doc",
+ "source",
+ "tests",
+ )
+
+ if os.sep != "/":
+ paths = [f.replace("/", os.sep) for f in paths]
+ return paths
+
+
+def source_files_from_git(paths, changed_only):
+ if changed_only:
+ cmd = ("git", "diff", "HEAD", "--name-only", "-z", "--", *paths)
+ else:
+ cmd = ("git", "ls-tree", "-r", "HEAD", *paths, "--name-only", "-z")
+ files = subprocess.check_output(cmd).split(b'\0')
+ return [f.decode('ascii') for f in files]
+
+
+def autopep8_ensure_version():
+ global AUTOPEP8_FORMAT_CMD
+ autopep8_format_cmd = None
+ version_output = None
+ # TODO: search paths.
+ for _ in (None,):
+ autopep8_format_cmd = "autopep8"
+ try:
+ version_output = subprocess.check_output((autopep8_format_cmd, "--version")).decode('utf-8')
+ except FileNotFoundError as e:
+ continue
+ AUTOPEP8_FORMAT_CMD = autopep8_format_cmd
+ break
+ version = next(iter(v for v in version_output.split() if v[0].isdigit()), None)
+ if version is not None:
+ version = version.split("-")[0]
+ version = tuple(int(n) for n in version.split("."))
+ if version is not None:
+ print("Using %s (%d.%d.%d)..." % (AUTOPEP8_FORMAT_CMD, version[0], version[1], version[2]))
+ return version
+
+
+def autopep8_format(files):
+ cmd = [AUTOPEP8_FORMAT_CMD, "--recursive", "--in-place", "--jobs=0"] + files
+ return subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+
+
+def argparse_create():
+ import argparse
+
+ # When --help or no args are given, print this help
+ usage_text = "Format source code"
+ epilog = "This script runs autopep8 on multiple files/directories"
+ parser = argparse.ArgumentParser(description=usage_text, epilog=epilog)
+ parser.add_argument(
+ "--changed-only",
+ dest="changed_only",
+ default=False,
+ action='store_true',
+ help=(
+ "Format only edited files, including the staged ones. "
+ "Using this with \"paths\" will pick the edited files lying on those paths. "
+ "(default=False)"
+ ),
+ required=False,
+ )
+ parser.add_argument(
+ "paths",
+ nargs=argparse.REMAINDER,
+ help="All trailing arguments are treated as paths."
+ )
+
+ return parser
+
+
+def main():
+ version = autopep8_ensure_version()
+ if version is None:
+ print("Unable to detect 'autopep8 --version'")
+ sys.exit(1)
+ if version < VERSION_MIN:
+ print("Version of autopep8 is too old:", version, "<", VERSION_MIN)
+ sys.exit(1)
+ if version > VERSION_MAX_RECOMMENDED:
+ print(
+ "WARNING: Version of autopep8 is too recent:",
+ version, ">", VERSION_MAX_RECOMMENDED,
+ )
+ print(
+ "You may want to install autopep8-%d.%d, "
+ "or use the precompiled libs repository." %
+ (VERSION_MAX_RECOMMENDED[0], VERSION_MAX_RECOMMENDED[1]),
+ )
+
+ args = argparse_create().parse_args()
+
+ use_default_paths = not (bool(args.paths) or bool(args.changed_only))
+
+ paths = compute_paths(args.paths, use_default_paths)
+ print("Operating on:" + (" (%d changed paths)" % len(paths) if args.changed_only else ""))
+ for p in paths:
+ print(" ", p)
+
+ files = [
+ f for f in source_files_from_git(paths, args.changed_only)
+ if f.endswith(extensions)
+ if f not in ignore_files
+ ]
+
+ autopep8_format(files)
+
+
+if __name__ == "__main__":
+ main()