From 2c95a3e87d63a150e1976774bc98c52d618bcf62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Valyi?= Date: Sat, 30 Aug 2025 06:36:13 +0000 Subject: [PATCH] add shrink-aggressive / -S option --- README.md | 17 +++++++------- src/akaidoo/cli.py | 13 ++++++----- src/akaidoo/shrinker.py | 26 +++++++++++++++++----- tests/test_shrinker.py | 49 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 tests/test_shrinker.py diff --git a/README.md b/README.md index 2931f5e..b67493a 100644 --- a/README.md +++ b/README.md @@ -20,18 +20,19 @@ --- **Akaidoo** extends the [manifestoo](https://github.com/acsone/manifestoo) CLI to list -and copy all relevant source files (Python models, XML views, wizards, data, reports, and -even OpenUpgrade migration scripts) from a specific Odoo addon and its _entire_ dependency -tree. It's designed to feed AI LLMs. +and copy all relevant source files (Python models, XML views, wizards, data, reports, +and even OpenUpgrade migration scripts) from a specific Odoo addon and its _entire_ +dependency tree. It's designed to feed AI LLMs. Akaidoo bridges the AI gap for Odoo by helping you: -- 🤖 **Boost AI Tools:** Feed precisely the right context to AI LLMs. Works best with Gemini and - its 1 million tokens context. +- 🤖 **Boost AI Tools:** Feed precisely the right context to AI LLMs. Works best with + Gemini and its 1 million tokens context. - 📝 **Streamline Editing:** Open all pertinent files in your editor with a single command. - 🧩 **Understand Scope:** Quickly grasp the breadth of an addon's interactions. -- 🔍 **Perform searches:** (`akaidoo sale_stock -c ~/DEV/odoo16/odoo.cfg | xargs grep "def _compute_price_unit"`) +- 🔍 **Perform searches:** + (`akaidoo sale_stock -c ~/DEV/odoo16/odoo.cfg | xargs grep "def _compute_price_unit"`) - 🚀 **Accelerate Migrations:** Gather module code, dependencies, and their corresponding OpenUpgrade migration scripts in one go. @@ -104,8 +105,8 @@ Imagine you're working on the `sale_timesheet` addon in an Odoo project. akaidoo sale_timesheet -c ~/path/to/your/odoo.conf ``` -2. **Copy all Python model code for `sale_timesheet` (without its deps -l) to your clipboard - for an AI prompt:** +2. **Copy all Python model code for `sale_timesheet` (without its deps -l) to your + clipboard for an AI prompt:** ```console akaidoo sale_timesheet -c odoo.conf --only-models -l -x diff --git a/src/akaidoo/cli.py b/src/akaidoo/cli.py index afae665..51b6149 100644 --- a/src/akaidoo/cli.py +++ b/src/akaidoo/cli.py @@ -342,7 +342,7 @@ def akaidoo_command_entrypoint( help=f"Exclude {FRAMEWORK_ADDONS} framework addons.", ), separator: str = typer.Option( - "\n", "--separator", "-S", help="Separator character between filenames." + "\n", "--separator", help="Separator character between filenames." ), shrink: bool = typer.Option( False, @@ -352,11 +352,12 @@ def akaidoo_command_entrypoint( ), shrink_aggressive: bool = typer.Option( False, - "--shrink_aggressive", + "--shrink-aggressive", "-S", - help="Shrink dependency Python files to essentials (classes, methods, fields).", + help="Enable aggressive shrinking, removing method bodies entirely.", ), output_file: Optional[Path] = typer.Option( + # Path("akaidoo.out"), None, "--output-file", "-o", @@ -411,7 +412,7 @@ def akaidoo_command_entrypoint( if item.is_file(): if ( "__pycache__" in str(item) - or "/." in str(item) + or str(item).replace(str(potential_path), "").startswith("/") or ".png" in str(item) ): continue @@ -721,8 +722,8 @@ def akaidoo_command_entrypoint( or addon_to_scan_name != addon_name ): shrunken_content = shrink_python_file( - found_file.parts[0] - + "/".join(found_file.parts[1:]) + str(found_file), + aggressive=shrink_aggressive, ) shrunken_files_content[abs_file_path] = ( shrunken_content diff --git a/src/akaidoo/shrinker.py b/src/akaidoo/shrinker.py index 29cbeb4..f5c8940 100644 --- a/src/akaidoo/shrinker.py +++ b/src/akaidoo/shrinker.py @@ -10,7 +10,7 @@ parser.language = Language(python_language()) -def shrink_python_file(path: str) -> str: +def shrink_python_file(path: str, aggressive: bool = False) -> str: """ Shrinks Python code from a file to keep only class/function definitions (with decorators), class attributes, and field assignments. @@ -52,8 +52,8 @@ def process_function(node, indent=""): stripped_line = line.strip() if stripped_line: shrunken_parts.append(f"{indent}{stripped_line}") - - shrunken_parts.append(f"{indent} pass # shrunk") + if not aggressive: + shrunken_parts.append(f"{indent} pass # shrunk") # --- Main Processing Loop --- for node in root_node.children: @@ -78,12 +78,18 @@ def process_function(node, indent=""): line_bytes = code_bytes[child.start_byte : child.end_byte] line_text = line_bytes.decode("utf8").strip() shrunken_parts.append(f" {line_text}") - elif child.type in ("function_definition", "decorated_definition"): + elif ( + child.type in ("function_definition", "decorated_definition") + and not aggressive + ): shrunken_parts.append("") process_function(child, indent=" ") shrunken_parts.append("") - elif node.type in ("function_definition", "decorated_definition"): + elif ( + node.type in ("function_definition", "decorated_definition") + and not aggressive + ): process_function(node, indent="") shrunken_parts.append("") @@ -112,6 +118,12 @@ def main(): cli_parser.add_argument( "input_file", type=str, help="The path to the Python file you want to shrink." ) + cli_parser.add_argument( + "-S", + "--shrink-aggressive", + action="store_true", + help="Enable aggressive shrinking, removing method bodies entirely.", + ) cli_parser.add_argument( "-o", "--output", @@ -121,7 +133,9 @@ def main(): args = cli_parser.parse_args() try: - shrunken_content = shrink_python_file(args.input_file) + shrunken_content = shrink_python_file( + args.input_file, aggressive=args.shrink_aggressive + ) if args.output: # Write to the specified output file diff --git a/tests/test_shrinker.py b/tests/test_shrinker.py new file mode 100644 index 0000000..6e5c054 --- /dev/null +++ b/tests/test_shrinker.py @@ -0,0 +1,49 @@ +from pathlib import Path +import pytest +from akaidoo.shrinker import shrink_python_file + + +@pytest.fixture +def sample_python_file(tmp_path: Path) -> Path: + """Create a sample Python file for testing.""" + file_path = tmp_path / "sample.py" + file_path.write_text( + """ +class MyClass: + field = "value" + + def my_method(self): + print("Hello") + +@decorator +def my_function(): + return 1 +""" + ) + return file_path + + +def test_shrink_python_file_default(sample_python_file: Path): + """Test the default shrinking behavior.""" + shrunken_content = shrink_python_file(str(sample_python_file)) + assert "class MyClass:" in shrunken_content + assert 'field = "value"' in shrunken_content + assert "def my_method(self):" in shrunken_content + assert "pass # shrunk" in shrunken_content + assert "@decorator" in shrunken_content + assert "def my_function():" in shrunken_content + assert 'print("Hello")' not in shrunken_content + assert "return 1" not in shrunken_content + + +def test_shrink_python_file_aggressive(sample_python_file: Path): + """Test the aggressive shrinking behavior.""" + shrunken_content = shrink_python_file(str(sample_python_file), aggressive=True) + assert "class MyClass:" in shrunken_content + assert 'field = "value"' in shrunken_content + assert "def my_method(self):" not in shrunken_content + assert "pass # shrunk" not in shrunken_content + assert "@decorator" not in shrunken_content + assert "def my_function():" not in shrunken_content + assert 'print("Hello")' not in shrunken_content + assert "return 1" not in shrunken_content