This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/main by this push:
     new 513a192  Add a script to fix function order
513a192 is described below

commit 513a1922d18d177c9d6b4dd9e9fdbce9a1a16f9f
Author: Sean B. Palmer <[email protected]>
AuthorDate: Tue Dec 2 14:46:16 2025 +0000

    Add a script to fix function order
---
 scripts/fix_order.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++
 scripts/fix_order.sh |  31 +++++++++++++
 2 files changed, 159 insertions(+)

diff --git a/scripts/fix_order.py b/scripts/fix_order.py
new file mode 100755
index 0000000..13d3f27
--- /dev/null
+++ b/scripts/fix_order.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python3
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import pathlib
+import sys
+
+
+def main() -> None:
+    if len(sys.argv) != 2:
+        print("Usage: fix_order.py <filename>", file=sys.stderr)
+        sys.exit(2)
+
+    path = pathlib.Path(sys.argv[1])
+    blocks = _parse_blocks(path.read_text(encoding="utf-8"))
+    nonfunc, func = [], []
+    seen_func = False
+    main_guard: list[str] | None = None
+
+    if blocks and _is_main_guard(blocks[-1][1]):
+        main_guard = blocks[-1][1]
+        blocks = blocks[:-1]
+
+    for lineno, lines in blocks:
+        count = _count_defs(lines)
+        if count > 1:
+            sys.exit(f"{path}:{lineno}: multiple function definitions in 
block")
+        if _is_func(lines):
+            seen_func = True
+            func.append((lineno, lines))
+        elif seen_func:
+            sys.exit(f"{path}:{lineno}: non-function block after function 
block")
+        else:
+            nonfunc.append((lineno, lines))
+
+    func.sort(key=_sort_key)
+    nonfunc_text = "".join("".join(b) for _, b in nonfunc)
+    func_parts = [_normalise(b) for _, b in func]
+    func_text = "\n\n".join(func_parts)
+    if nonfunc_text and func_text:
+        output = nonfunc_text.rstrip("\n") + "\n\n\n" + func_text
+    else:
+        output = nonfunc_text + func_text
+    if main_guard:
+        if output:
+            output = output.rstrip("\n") + "\n\n\n" + _normalise(main_guard)
+        else:
+            output = _normalise(main_guard)
+    print(output, end="")
+
+
+def _count_defs(block: list[str]) -> int:
+    return sum(1 for line in block if line.startswith(("def ", "async def ")))
+
+
+def _is_func(block: list[str]) -> bool:
+    for line in block:
+        if not line.strip():
+            continue
+        if line.lstrip() != line:
+            break
+        if line.startswith(("def ", "async def ")):
+            return True
+    return False
+
+
+def _is_main_guard(block: list[str]) -> bool:
+    for line in block:
+        if line.strip():
+            return line.startswith("if __name__")
+    return False
+
+
+def _normalise(block: list[str]) -> str:
+    while block and not block[-1].strip():
+        block = block[:-1]
+    return "".join(block).rstrip("\n") + "\n"
+
+
+def _parse_blocks(text: str) -> list[tuple[int, list[str]]]:
+    blocks: list[tuple[int, list[str]]] = []
+    cur: list[str] = []
+    start = 1
+    after_blank = False
+    for lineno, line in enumerate(text.splitlines(keepends=True), 1):
+        is_blank = not line.strip()
+        is_unindented = line.lstrip() == line
+        if is_unindented and (not is_blank) and after_blank and cur:
+            blocks.append((start, cur))
+            cur = []
+            start = lineno
+        cur.append(line)
+        after_blank = is_blank
+    if cur:
+        blocks.append((start, cur))
+    return blocks
+
+
+def _sort_key(block: tuple[int, list[str]]) -> str:
+    for line in block[1]:
+        if line.startswith("def "):
+            return _toggle(line[4:].split("(")[0])
+        if line.startswith("async def "):
+            return _toggle(line[10:].split("(")[0])
+    return ""
+
+
+def _toggle(name: str) -> str:
+    return name[1:] if name.startswith("_") else "_" + name
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/fix_order.sh b/scripts/fix_order.sh
new file mode 100755
index 0000000..b065c95
--- /dev/null
+++ b/scripts/fix_order.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+set -eu
+
+if [ $# -ne 1 ]
+then
+  echo "Usage: fix_order.sh <filename>" >&2
+  exit 2
+fi
+
+file="$1"
+tmp="$$.tmp.py"
+backup="$HOME/.fix_order.backup.py"
+script_dir="$(dirname "$0")"
+
+# TODO: Use uv here?
+python3 "$script_dir/fix_order.py" "$file" > "$tmp"
+status=$?
+if [ $status -ne 0 ]
+then
+  rm -f "$tmp"
+  exit $status
+fi
+
+if cmp -s "$file" "$tmp"
+then
+  rm -f "$tmp"
+else
+  cp "$file" "$backup"
+  diff -u "$file" "$tmp"
+  mv "$tmp" "$file"
+fi


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to