Re: [PATCH 17/17] RFC: tools: Add a camel-case conversion script

2023-03-01 Thread Simon Glass
This is only for posterity, since once the conversion is done, the script
is of no use.

Signed-off-by: Simon Glass 
---

 scripts/style.py | 180 +++
 1 file changed, 180 insertions(+)
 create mode 100755 scripts/style.py

Applied to u-boot-dm, thanks!


[PATCH 17/17] RFC: tools: Add a camel-case conversion script

2022-01-29 Thread Simon Glass
This is only for posterity, since once the conversion is done, the script
is of no use.

Signed-off-by: Simon Glass 
---

 scripts/style.py | 180 +++
 1 file changed, 180 insertions(+)
 create mode 100755 scripts/style.py

diff --git a/scripts/style.py b/scripts/style.py
new file mode 100755
index 000..7b73b007dea
--- /dev/null
+++ b/scripts/style.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2021 Google LLC
+#
+
+"""Changes the functions and class methods in a file to use snake case, 
updating
+other tools which use them"""
+
+from argparse import ArgumentParser
+import glob
+import os
+import re
+import subprocess
+
+import camel_case
+
+# Exclude functions with these names
+EXCLUDE_NAMES = set(['setUp', 'tearDown', 'setUpClass', 'tearDownClass'])
+
+# Find function definitions in a file
+RE_FUNC = re.compile(r' *def (\w+)\(')
+
+# Where to find files that might call the file being converted
+FILES_GLOB = 'tools/**/*.py'
+
+def collect_funcs(fname):
+"""Collect a list of functions in a file
+
+Args:
+fname (str): Filename to read
+
+Returns:
+tuple:
+str: contents of file
+list of str: List of function names
+"""
+with open(fname, encoding='utf-8') as inf:
+data = inf.read()
+funcs = RE_FUNC.findall(data)
+return data, funcs
+
+def get_module_name(fname):
+"""Convert a filename to a module name
+
+Args:
+fname (str): Filename to convert, e.g. 'tools/patman/command.py'
+
+Returns:
+tuple:
+str: Full module name, e.g. 'patman.command'
+str: Leaf module name, e.g. 'command'
+str: Program name, e.g. 'patman'
+"""
+parts = os.path.splitext(fname)[0].split('/')[1:]
+module_name = '.'.join(parts)
+return module_name, parts[-1], parts[0]
+
+def process_caller(data, conv, module_name, leaf):
+"""Process a file that might call another module
+
+This converts all the camel-case references in the provided file contents
+with the corresponding snake-case references.
+
+Args:
+data (str): Contents of file to convert
+conv (dict): Identifies to convert
+key: Current name in camel case, e.g. 'DoIt'
+value: New name in snake case, e.g. 'do_it'
+module_name: Name of module as referenced by the file, e.g.
+'patman.command'
+leaf: Leaf module name, e.g. 'command'
+
+Returns:
+str: New file contents, or None if it was not modified
+"""
+total = 0
+
+# Update any simple functions calls into the module
+for name, new_name in conv.items():
+newdata, count = re.subn(fr'{leaf}.{name}\(',
+ f'{leaf}.{new_name}(', data)
+total += count
+data = newdata
+
+# Deal with files that import symbols individually
+imports = re.findall(fr'from {module_name} import (.*)\n', data)
+for item in imports:
+#print('item', item)
+names = [n.strip() for n in item.split(',')]
+new_names = [conv.get(n) or n for n in names]
+new_line = f"from {module_name} import {', '.join(new_names)}\n"
+data = re.sub(fr'from {module_name} import (.*)\n', new_line, data)
+for name in names:
+new_name = conv.get(name)
+if new_name:
+newdata = re.sub(fr'\b{name}\(', f'{new_name}(', data)
+data = newdata
+
+# Deal with mocks like:
+# unittest.mock.patch.object(module, 'Function', ...
+for name, new_name in conv.items():
+newdata, count = re.subn(fr"{leaf}, '{name}'",
+ f"{leaf}, '{new_name}'", data)
+total += count
+data = newdata
+
+if total or imports:
+return data
+return None
+
+def process_file(srcfile, do_write, commit):
+"""Process a file to rename its camel-case functions
+
+This renames the class methods and functions in a file so that they use
+snake case. Then it updates other modules that call those functions.
+
+Args:
+srcfile (str): Filename to process
+do_write (bool): True to write back to files, False to do a dry run
+commit (bool): True to create a commit with the changes
+"""
+data, funcs = collect_funcs(srcfile)
+module_name, leaf, prog = get_module_name(srcfile)
+#print('module_name', module_name)
+#print(len(funcs))
+#print(funcs[0])
+conv = {}
+for name in funcs:
+if name not in EXCLUDE_NAMES:
+conv[name] = camel_case.to_snake(name)
+
+# Convert name to new_name in the file
+for name, new_name in conv.items():
+#print(name, new_name)
+# Don't match if it is preceded by a '.', since that indicates that
+# it is calling this same function name but in a different module
+newdata = re.sub(fr'(?