From 369c1b148bb7fed796012bf078645c1ca0179201 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:16:27 +0100 Subject: [PATCH 01/25] feat:code complexity toolkit --- .DS_Store | Bin 0 -> 6148 bytes pyproject.toml | 2 + src/goose_plugins/toolkits/code_complexity.py | 162 ++++++++++++++++++ src/goose_plugins/toolkits/todo.py | 8 +- test_file.py | 2 + tests/toolkits/test_code_complexity.py | 105 ++++++++++++ 6 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 .DS_Store create mode 100644 src/goose_plugins/toolkits/code_complexity.py create mode 100644 test_file.py create mode 100644 tests/toolkits/test_code_complexity.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..05d35019cefb504976c9fc393b3d3614d4620b83 GIT binary patch literal 6148 zcmeHLu}%U(5Pd60B-&7Ffw|g(h^@^*&eDq30yzvA!6O2Vg}K;Z;ZJBRtgQS3O-%G_ zEc^gxc9*+d4lIq)on-eNJ2N+LAK6_N0HQJJ)POR85;aC|jk*QKer`Ei(IXp#g5S~a z)|yc~80I3~fK$LJ@EaB2wd1AE*6-ylujAA=h{LemkJ~g>y8GxJ+>BrAw!ibO zf7mnjhAW>^u{x`Z9wum8)v;I1N0=P8pPqN8io@J{264t@d6dNmiQ*F>#u>&q=HBAd z`h1jptO%1&{qQa0GZ&TdSsrEaISQDMhYsdHd1l4D*Qy6uuP7BCQE$%_oA*@H@S1g}4{2R1BGYHTcR;Ty-G8`gM=z6DsQ@~cBpgxxQ z{NJZFyyX6GC%I2f0jI#9Qb2fNGi=b5^x2x59G|s5wH7rtj!P8E6zcSGEFbtN=KmF> akzo2Vs=ya$yTFwI literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml index 8db0362..b85ff8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.10" dependencies = [ "ai-exchange>=0.8.4", "goose-ai>=0.9.8", + "radon>=6.0.1", ] author = [{ name = "Block", email = "ai-oss-tools@block.xyz" }] packages = [{ include = "goose_plugins", from = "src" }] @@ -20,6 +21,7 @@ goose-plugins = "goose_plugins:module_name" [project.entry-points."goose.toolkit"] artify = "goose_plugins.toolkits.artify:VincentVanCode" todo = "goose_plugins.toolkits.todo:TodoToolkit" +code_complexity = "goose_plugins.toolkits.code_complexity:CodeComplexityToolkit" [build-system] diff --git a/src/goose_plugins/toolkits/code_complexity.py b/src/goose_plugins/toolkits/code_complexity.py new file mode 100644 index 0000000..87f2f74 --- /dev/null +++ b/src/goose_plugins/toolkits/code_complexity.py @@ -0,0 +1,162 @@ +import os +import ast +from goose.toolkit.base import Toolkit, tool +import radon.complexity as rc +import radon.metrics as rm + + +class CodeComplexityToolkit(Toolkit): + """A toolkit for analyzing the complexity of Python code in a given directory.""" + + def __init__(self, *args: tuple, **kwargs: dict) -> None: + super().__init__(*args, **kwargs) + + @tool + def get_python_files(self, directory: str) -> list: + """Retrieve all Python files from the specified directory. + + Args: + directory (str): The directory to search for Python files. + + Returns: + list: A list of paths to all Python files in the directory. + """ + return [ + os.path.join(root, file) + for root, _, files in os.walk(directory) + for file in files + if file.endswith(".py") + ] + + @tool + def analyze_complexity(self, directory: str) -> dict: + """Analyze the complexity of Python code in a directory. + + Args: + directory (str): The path to the directory containing Python files to analyze. + + Returns: + dict: A dictionary containing the average complexity metrics (Cyclomatic Complexity, Halstead Metrics, + and Maintainability Index) for all Python files in the directory, or an error message if no + valid Python files are found. + """ + python_files = self.get_python_files(directory) + if not python_files: + return {"error": f"No Python files found in the directory: {directory}"} + + complexity_results = { + "cyclomatic_complexity": 0, + "halstead_metrics": 0, + "maintainability_index": 0, + "file_count": 0, + } + + for file in python_files: + try: + with open(file, "r", encoding="utf-8") as f: + code = f.read() + + # Process each complexity metric and update the results + complexity_results[ + "cyclomatic_complexity" + ] += self.cyclomatic_complexity(code) + halstead_result = self.halstead_complexity(code) + complexity_results["halstead_metrics"] += ( + halstead_result["halstead_volume"] if halstead_result else 0 + ) + complexity_results[ + "maintainability_index" + ] += self.maintainability_index(code) + complexity_results["file_count"] += 1 + + except Exception as e: + complexity_results["error"] = f"Error processing {file}: {str(e)}" + continue + + if complexity_results["file_count"] > 0: + # Average the results + return { + "avg_cyclomatic_complexity": complexity_results["cyclomatic_complexity"] + / complexity_results["file_count"], + "avg_halstead_complexity": complexity_results["halstead_metrics"] + / complexity_results["file_count"], + "avg_maintainability_index": complexity_results["maintainability_index"] + / complexity_results["file_count"], + } + else: + return {"error": "No valid Python files to analyze."} + + @tool + def cyclomatic_complexity(self, code: str) -> int: + """Calculate the Cyclomatic Complexity of a given Python code. + + Args: + code (str): The Python code as a string to analyze. + + Returns: + int: The Cyclomatic Complexity of the code. + """ + try: + complexity = rc.cc_visit(ast.parse(code)) + return complexity + except Exception as e: + self.notifier.log(f"Error calculating cyclomatic complexity: {str(e)}") + return 0 + + @tool + def halstead_complexity(self, code: str) -> dict: + """Calculate Halstead Complexity metrics of the given Python code. + + Args: + code (str): The Python code as a string to analyze. + + Returns: + dict: A dictionary of Halstead metrics (e.g., volume, difficulty, effort). + """ + try: + return rm.halstead_metrics(code) + except Exception as e: + self.notifier.log(f"Error calculating Halstead complexity: {str(e)}") + return {} + + @tool + def maintainability_index(self, code: str) -> int: + """Calculate the Maintainability Index of the given Python code. + + Args: + code (str): The Python code as a string to analyze. + + Returns: + int: The Maintainability Index of the code. + """ + try: + return rm.maintainability_index(code) + except Exception as e: + self.notifier.log(f"Error calculating maintainability index: {str(e)}") + return 0 + + @tool + def aggregate_results(self, results: dict) -> dict: + """Aggregate the complexity results from all analyzed files. + + Args: + results (dict): A dictionary containing lists of complexity metrics across multiple files, + including Cyclomatic Complexity, Halstead Metrics, and Maintainability Index. + + Returns: + dict: A dictionary containing the aggregated averages for each complexity metric. + """ + try: + aggregated_results = { + "avg_cyclomatic_complexity": sum(results["cyclomatic_complexity"]) + / len(results["cyclomatic_complexity"]), + "avg_halstead_complexity": sum( + [h["halstead_volume"] for h in results["halstead_metrics"]] + ) + / len(results["halstead_metrics"]), + "avg_maintainability_index": sum(results["maintainability_index"]) + / len(results["maintainability_index"]), + } + return aggregated_results + except ZeroDivisionError: + return {"error": "No valid results to aggregate."} diff --git a/src/goose_plugins/toolkits/todo.py b/src/goose_plugins/toolkits/todo.py index 573c952..a8b9836 100644 --- a/src/goose_plugins/toolkits/todo.py +++ b/src/goose_plugins/toolkits/todo.py @@ -62,7 +62,9 @@ def mark_as_complete(self, task_number: int) -> str: """ try: self.tasks[task_number - 1]["completed"] = True - self.notifier.log(f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'") + self.notifier.log( + f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'" + ) return f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'" except IndexError: self.notifier.log("Invalid task number. Please try again.") @@ -97,7 +99,9 @@ def update_task(self, task_number: int, new_description: str) -> str: try: old_description = self.tasks[task_number - 1]["description"] self.tasks[task_number - 1]["description"] = new_description - self.notifier.log(f"Updated task {task_number} from '{old_description}' to '{new_description}'") + self.notifier.log( + f"Updated task {task_number} from '{old_description}' to '{new_description}'" + ) return f"Updated task {task_number} successfully." except IndexError: self.notifier.log("Invalid task number. Unable to update.") diff --git a/test_file.py b/test_file.py new file mode 100644 index 0000000..7351411 --- /dev/null +++ b/test_file.py @@ -0,0 +1,2 @@ +def example_function(): + return 42 \ No newline at end of file diff --git a/tests/toolkits/test_code_complexity.py b/tests/toolkits/test_code_complexity.py new file mode 100644 index 0000000..ce45f8e --- /dev/null +++ b/tests/toolkits/test_code_complexity.py @@ -0,0 +1,105 @@ +import pytest +from unittest.mock import MagicMock +from goose_plugins.toolkits.code_complexity import CodeComplexityToolkit + + +@pytest.fixture +def toolkit(): + toolkit = CodeComplexityToolkit(notifier=MagicMock()) + return toolkit + + +def test_get_python_files(toolkit): + directory = "test_directory" + + # Simulate os.walk to mock the file retrieval process + toolkit.get_python_files = MagicMock( + return_value=["test_file.py", "another_test_file.py"] + ) + + result = toolkit.get_python_files(directory) + + # Check that the mocked method was called with the correct argument + toolkit.get_python_files.assert_called_with(directory) + assert result == ["test_file.py", "another_test_file.py"] + + +def test_analyze_complexity(toolkit): + directory = "test_directory" + + # Mock methods that would be used during complexity analysis + toolkit.get_python_files = MagicMock(return_value=["test_file.py"]) + toolkit.cyclomatic_complexity = MagicMock(return_value=5) + toolkit.halstead_complexity = MagicMock(return_value={"halstead_volume": 100}) + toolkit.maintainability_index = MagicMock(return_value=70) + + # Mock file content reading + with open("test_file.py", "w") as f: + f.write("def example_function():\n return 42") + + result = toolkit.analyze_complexity(directory) + assert "avg_cyclomatic_complexity" in result + assert "avg_halstead_complexity" in result + assert "avg_maintainability_index" in result + + +def test_cyclomatic_complexity(toolkit): + code = "def test_func():\n if True:\n return 1" + + try: + result = toolkit.cyclomatic_complexity(code) + except Exception as e: + result = None + toolkit.notifier.log.assert_called_with( + f"Error calculating cyclomatic complexity: {str(e)}" + ) + + # Adjust the expected result based on the actual output + assert result[0].complexity == 2 + + +def test_halstead_complexity(toolkit): + code = "def test_func():\n return 42" + + try: + result = toolkit.halstead_complexity(code) + except Exception as e: + result = None + toolkit.notifier.log.assert_called_with( + f"Error calculating Halstead complexity: {str(e)}" + ) + + # In case no error occurred, verify expected result + assert isinstance(result, dict) # Should return a dictionary + + +def test_maintainability_index(toolkit): + code = "def test_func():\n return 42" + + try: + result = toolkit.maintainability_index(code) + except Exception as e: + result = None + toolkit.notifier.log.assert_called_with( + f"Error calculating maintainability index: {str(e)}" + ) + + # In case no error occurred, verify expected result + assert isinstance(result, int) # Should return an integer + + +def test_aggregate_results(toolkit): + results = { + "cyclomatic_complexity": [5, 10], + "halstead_metrics": [{"halstead_volume": 100}, {"halstead_volume": 200}], + "maintainability_index": [70, 60], + } + + aggregated = toolkit.aggregate_results(results) + + assert "avg_cyclomatic_complexity" in aggregated + assert "avg_halstead_complexity" in aggregated + assert "avg_maintainability_index" in aggregated + assert aggregated["avg_cyclomatic_complexity"] == 7.5 + assert aggregated["avg_halstead_complexity"] == 150 + assert aggregated["avg_maintainability_index"] == 65 From fa0972ab93822753b4f6a312f13be89309a1e4bb Mon Sep 17 00:00:00 2001 From: evans <58369673+Johnnyevans32@users.noreply.github.com> Date: Sun, 17 Nov 2024 02:21:37 +0100 Subject: [PATCH 02/25] Delete .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 05d35019cefb504976c9fc393b3d3614d4620b83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHLu}%U(5Pd60B-&7Ffw|g(h^@^*&eDq30yzvA!6O2Vg}K;Z;ZJBRtgQS3O-%G_ zEc^gxc9*+d4lIq)on-eNJ2N+LAK6_N0HQJJ)POR85;aC|jk*QKer`Ei(IXp#g5S~a z)|yc~80I3~fK$LJ@EaB2wd1AE*6-ylujAA=h{LemkJ~g>y8GxJ+>BrAw!ibO zf7mnjhAW>^u{x`Z9wum8)v;I1N0=P8pPqN8io@J{264t@d6dNmiQ*F>#u>&q=HBAd z`h1jptO%1&{qQa0GZ&TdSsrEaISQDMhYsdHd1l4D*Qy6uuP7BCQE$%_oA*@H@S1g}4{2R1BGYHTcR;Ty-G8`gM=z6DsQ@~cBpgxxQ z{NJZFyyX6GC%I2f0jI#9Qb2fNGi=b5^x2x59G|s5wH7rtj!P8E6zcSGEFbtN=KmF> akzo2Vs=ya$yTFwI From 09e50d609fcb2b9ab9d597b5b5ec634ab13f05dc Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:22:21 +0100 Subject: [PATCH 03/25] feat:code complexity toolkit --- .DS_Store | Bin 6148 -> 8196 bytes .gitignore | 3 +++ test_file.py | 2 -- 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 test_file.py diff --git a/.DS_Store b/.DS_Store index 05d35019cefb504976c9fc393b3d3614d4620b83..0e5e5f04ff711840ee80a7ccf42f773755355025 100644 GIT binary patch literal 8196 zcmeHML2DC16n~8cVV&1^aH_Ur)-#1@oH+dT(GFwOe4WbnyYN9hP zw=k46wsSAEYi{KR&>)_MdmG!mXf!E!>Vhhu3aA3AfGVI0{3{CJJDXdx;(Om(byNjZ zfrV6npAP{#W9Trkm_9l%q$L2bj^(srzRVL~;4pNUScC=5*ifJiHLk=kHXQ4L^9vm& z7Hv2gS9}%$-B?eHj3Kq?l9_N zt$B6T*m)Cw*!1oFx%SCGyc>8LB9xYbO==reO~u}R(nc*NxyY)Vfj^DSMvP5x6il3e6NG=1bpL^ zX-p5ON4w}??sIPP!*Y(dW5Kspw5ri;IITWg@@#^x$`kUe=}Y=mufG`hjv3G z!+qFCw%*K%TU~!HZIZ*mFyoNL%2QQl_wugZ;=DDS^GDS4_D|2g=N6gs{mG~4zFnNm zShRZi=nLm-WG8m!-gl`qIJ3{K9^1)0gf(4;WASzyX$c41jY zCOO8*Y9b+w29v8rWSJzFOr9l@z-TZr@y}v*4h}(PuyO_gZXoRna_7dx@640=WjsNy RW?+K&0OTo#&G9^Qm;niDFc$y- diff --git a/.gitignore b/.gitignore index e68d1cf..b119472 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,9 @@ share/python-wheels/ *.egg MANIFEST + +# .DS_Store + # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. diff --git a/test_file.py b/test_file.py deleted file mode 100644 index 7351411..0000000 --- a/test_file.py +++ /dev/null @@ -1,2 +0,0 @@ -def example_function(): - return 42 \ No newline at end of file From 35b38a9e0dca4983a485da935ce13da09ae9f4d8 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:22:39 +0100 Subject: [PATCH 04/25] feat:code complexity toolkit --- .DS_Store | Bin 8196 -> 8196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index 0e5e5f04ff711840ee80a7ccf42f773755355025..09510bb8927f2111201189c89ba61b9cb9b473cb 100644 GIT binary patch delta 107 zcmZp1XmQw}DiBw1oXWt!z`~%%kj{|FP?DSP;*yk;p9B=+$Xfa@ZC}YTM^yO~yz&JZ VhQZ1CxdlKy3=Ay-o0|n1`2jd39Y_EG delta 107 zcmZp1XmQw}DiBw9;0yx;0}F#5LpnnyLrHGFi%U{YeiBfOwbk From b3a7fc6d6c47895adfe951f64f54a1a80ab48a05 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:38:34 +0100 Subject: [PATCH 05/25] feat:code complexity toolkit --- .DS_Store | Bin 8196 -> 8196 bytes .gitignore | 2 +- pyproject.toml | 2 +- ...e_complexity.py => complexity_analyzer.py} | 0 ...plexity.py => test_complexity_analyzer.py} | 9 +++------ 5 files changed, 5 insertions(+), 8 deletions(-) rename src/goose_plugins/toolkits/{code_complexity.py => complexity_analyzer.py} (100%) rename tests/toolkits/{test_code_complexity.py => test_complexity_analyzer.py} (88%) diff --git a/.DS_Store b/.DS_Store index 4a61ec0023288da957e298c63d5dced4e7a59f21..e4a35af07dbf662b48a66065efff254276fa8d2d 100644 GIT binary patch delta 158 zcmZp1XmQw}CJ^hnhk=2Cg+Y%YogtHoOVnOdJuRuc(kVt6pQT11v})DB2ujFD^3};Pu?ja3uWCF RkprsOn8?GtnO)*9I{;$&F2Dc) diff --git a/.gitignore b/.gitignore index b119472..5461955 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,7 @@ share/python-wheels/ MANIFEST -# .DS_Store +.DS_Store # PyInstaller # Usually these files are written by a python script from a template diff --git a/pyproject.toml b/pyproject.toml index b85ff8a..d46d9fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ goose-plugins = "goose_plugins:module_name" [project.entry-points."goose.toolkit"] artify = "goose_plugins.toolkits.artify:VincentVanCode" todo = "goose_plugins.toolkits.todo:TodoToolkit" -code_complexity = "goose_plugins.toolkits.code_complexity:CodeComplexityToolkit" +complexity_analyzer = "goose_plugins.toolkits.complexity_analyzer:CodeComplexityToolkit" [build-system] diff --git a/src/goose_plugins/toolkits/code_complexity.py b/src/goose_plugins/toolkits/complexity_analyzer.py similarity index 100% rename from src/goose_plugins/toolkits/code_complexity.py rename to src/goose_plugins/toolkits/complexity_analyzer.py diff --git a/tests/toolkits/test_code_complexity.py b/tests/toolkits/test_complexity_analyzer.py similarity index 88% rename from tests/toolkits/test_code_complexity.py rename to tests/toolkits/test_complexity_analyzer.py index ce45f8e..413f314 100644 --- a/tests/toolkits/test_code_complexity.py +++ b/tests/toolkits/test_complexity_analyzer.py @@ -1,6 +1,6 @@ import pytest from unittest.mock import MagicMock -from goose_plugins.toolkits.code_complexity import CodeComplexityToolkit +from goose_plugins.toolkits.complexity_analyzer import CodeComplexityToolkit @pytest.fixture @@ -54,7 +54,6 @@ def test_cyclomatic_complexity(toolkit): f"Error calculating cyclomatic complexity: {str(e)}" ) - # Adjust the expected result based on the actual output assert result[0].complexity == 2 @@ -69,8 +68,7 @@ def test_halstead_complexity(toolkit): f"Error calculating Halstead complexity: {str(e)}" ) - # In case no error occurred, verify expected result - assert isinstance(result, dict) # Should return a dictionary + assert isinstance(result, dict) def test_maintainability_index(toolkit): @@ -84,8 +82,7 @@ def test_maintainability_index(toolkit): f"Error calculating maintainability index: {str(e)}" ) - # In case no error occurred, verify expected result - assert isinstance(result, int) # Should return an integer + assert isinstance(result, int) def test_aggregate_results(toolkit): From 46b44ef6700a8633c4300c9c64f99b4f6fd588b5 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:45:17 +0100 Subject: [PATCH 06/25] feat:code complexity toolkit --- .DS_Store | Bin 8196 -> 8196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index e4a35af07dbf662b48a66065efff254276fa8d2d..ba770469c1a54c10260fabd5df45df1fd9999823 100644 GIT binary patch delta 163 zcmZp1XmQw}CJ-C5nSp_Ug+Y%YogtHoOVWw%}ga+n#48HyN^ku8|Z)^z&7WJeK6RtPIzM4C}yaOs=2;?H+yJ=!EYSb} From a3bd1b0a775059aebc0f09b114e92b2c84cc7956 Mon Sep 17 00:00:00 2001 From: evans <58369673+Johnnyevans32@users.noreply.github.com> Date: Sun, 17 Nov 2024 02:46:44 +0100 Subject: [PATCH 07/25] Delete .DS_Store --- .DS_Store | Bin 8196 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ba770469c1a54c10260fabd5df45df1fd9999823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMziSjh6n^7v^xzOu1|hJZg;)e*YqJSwX(4F?UT#lv;ePC4HW6)H6w!aePOuSE z7z;sAECjLgZxH_jOAEi5dAWUadwW`m=zB2pE%V-+_wBc{yEl78WHt|a>qIL=)I_tm z+(I|UtlzeZo#K%TK!bc5?yhg8*=SOU)B#mM6;K6K0aZX1_*)dfJKI{b=DqK|+N%Pp zz<;TLeLlo!78Az8qkD9qb6WslgkfhmUbY9om@#22Ji-GrJ{9Ow4R0}wPse!7aS3DL z(WjH)&4=O2hIc5&Rmc3X3n!C!)Ls=(1*!_zy?aD!v`c%G2mAN?+pnZ=l(pO4Vb;Z1 zGs@0if1iKc2>rc<{z+ea8+a5WmCl^hr9K@{H>gfmm#Ptx!=>X7R~~x~GxLnHl1Y73 zz=sOUrv*NJx=RzvD96z)+J`FpA+H-h>LVq;qSqQ6dww=p_g0e1BPukUTcFKb7k*m`7gM`Y6eVb{wCG zdf=1Jthm*+Ctx!5Ne+YIl!Ne>O3d>IbyqKAYHDDe-=;J;e|r6;^vIe|Pe1s~_de&z z^R0S=dM3uCxUwfJcIMuCGYGx1?wz;K&v9LmFKf?_2i+M75*-dk5r)&6{p(K=|>Y+T{drJ%DRc06L7{$Yr*n8y;v!XrE| P{Se?}&`uTjqYC^4*P}23 From 1add564882b7640241962cadb674f74cb5e436b4 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:47:23 +0100 Subject: [PATCH 08/25] feat:code complexity toolkit --- .DS_Store | Bin 8196 -> 8196 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index ba770469c1a54c10260fabd5df45df1fd9999823..ec341bc79f2711fa9658def9cd2bda905473da98 100644 GIT binary patch delta 113 zcmZp1XmQw}CJ>vf!oa}5!l1{H&XCDalAG`1l9ZF51Qg?7$mkY3SaQq}RXzo;d_jg` caB_Zb0Zs*<882>D6S>C?083^Z@&Et; From aa97bea948ac0413bcec7540c654f18100375812 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:48:02 +0100 Subject: [PATCH 09/25] feat:code complexity toolkit --- .DS_Store | Bin 8196 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ec341bc79f2711fa9658def9cd2bda905473da98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMzl#$=6n#Gv$fefVy|2)n9D{kM}BPZ_D-~MQAEMN!A`Id zuQ>}r@JAtte}-7wSX%hKnYrwnY&Ken=zB2pE%V-+_szGHO=d$xW^=!{PP9TqO*F>k z7P>jcer^>z;YZE^4f1KYv%ZyPqe&%F2UG!7Kow90Q~_1sZ&3i>*=)_4?|tXhUKLOU z{!0b;`4FQqCLRli_R)dP9sz(6hOOaxnJ<8`$HZgd5FVIusX&)%{1L;rbd1M7F7a46 zbm?UL;ludK#-C7(t&aI)8%`#1sJ$wn3RD%~vwK8qv_rd;2j};DY2X`W?RIyVbrIjB z{*yEB^N$-cca-%O`X_zyZs6fnW^(GdF7;`jxl94;Jvxb(iGUsfX|kB#e}%KaD2BkyZ{l;lI(mQO@I z@JVNO-0Iq6Fq!%!hrw{lLHJ80=J|uVs~0gfHL%ZbQ5vkDUVSS)vgXs1_dmaL`ujY& z-l{jKXJSl>JA1riXFgl62ccKiz5V9-SzlM=%i8lJ!uniZO%+fDpg_Z^u)^p6EA{XH zRV!6M75Il0Fj0G}y@>_-vvp;u{PQtJ>!7i5T;b5BpyLo8k33HQFvM8w#}bc)LwI2N OBEZU^ohtB075E8%TQC*? From fce453000cc11414cf6b9343bc2a291a71a97a39 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 02:49:29 +0100 Subject: [PATCH 10/25] feat:code complexity toolkit --- src/goose_plugins/toolkits/todo.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/goose_plugins/toolkits/todo.py b/src/goose_plugins/toolkits/todo.py index a8b9836..573c952 100644 --- a/src/goose_plugins/toolkits/todo.py +++ b/src/goose_plugins/toolkits/todo.py @@ -62,9 +62,7 @@ def mark_as_complete(self, task_number: int) -> str: """ try: self.tasks[task_number - 1]["completed"] = True - self.notifier.log( - f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'" - ) + self.notifier.log(f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'") return f"Marked task {task_number} as complete: '{self.tasks[task_number - 1]['description']}'" except IndexError: self.notifier.log("Invalid task number. Please try again.") @@ -99,9 +97,7 @@ def update_task(self, task_number: int, new_description: str) -> str: try: old_description = self.tasks[task_number - 1]["description"] self.tasks[task_number - 1]["description"] = new_description - self.notifier.log( - f"Updated task {task_number} from '{old_description}' to '{new_description}'" - ) + self.notifier.log(f"Updated task {task_number} from '{old_description}' to '{new_description}'") return f"Updated task {task_number} successfully." except IndexError: self.notifier.log("Invalid task number. Unable to update.") From 9aaadb4aab96bb32fcff5413c1d94d3f1d9f0941 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 03:36:26 +0100 Subject: [PATCH 11/25] feat:code complexity toolkit --- .../toolkits/complexity_analyzer.py | 42 ++++++++++++++++--- test_file.py | 2 + tests/toolkits/test_complexity_analyzer.py | 4 +- 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 test_file.py diff --git a/src/goose_plugins/toolkits/complexity_analyzer.py b/src/goose_plugins/toolkits/complexity_analyzer.py index 87f2f74..2ffab39 100644 --- a/src/goose_plugins/toolkits/complexity_analyzer.py +++ b/src/goose_plugins/toolkits/complexity_analyzer.py @@ -61,6 +61,7 @@ def analyze_complexity(self, directory: str) -> dict: "cyclomatic_complexity" ] += self.cyclomatic_complexity(code) halstead_result = self.halstead_complexity(code) + print("halstead_result", halstead_result) complexity_results["halstead_metrics"] += ( halstead_result["halstead_volume"] if halstead_result else 0 ) @@ -97,9 +98,22 @@ def cyclomatic_complexity(self, code: str) -> int: int: The Cyclomatic Complexity of the code. """ try: - complexity = rc.cc_visit(ast.parse(code)) - return complexity + complexity_list = rc.cc_visit(ast.parse(code)) + total_complexity = 0 + + # Iterate over each item in the complexity list + for item in complexity_list: + if hasattr(item, "complexity"): + # Add complexity of the function or class's top-level complexity + total_complexity += item.complexity + + # For classes, add complexity of methods if any + if hasattr(item, "methods"): + for method in item.methods: + total_complexity += method.complexity + return total_complexity except Exception as e: + print(e) self.notifier.log(f"Error calculating cyclomatic complexity: {str(e)}") return 0 @@ -111,11 +125,26 @@ def halstead_complexity(self, code: str) -> dict: code (str): The Python code as a string to analyze. Returns: - dict: A dictionary of Halstead metrics (e.g., volume, difficulty, effort). + dict: A dictionary containing the Halstead metrics, including 'halstead_volume'. """ + from radon.metrics import h_visit + try: - return rm.halstead_metrics(code) + halstead_report = h_visit(code) + return { + "halstead_volume": halstead_report.total.volume, + "details": { + "vocabulary": halstead_report.total.vocabulary, + "length": halstead_report.total.length, + "calculated_length": halstead_report.total.calculated_length, + "difficulty": halstead_report.total.difficulty, + "effort": halstead_report.total.effort, + "time": halstead_report.total.time, + "bugs": halstead_report.total.bugs, + }, + } except Exception as e: + print(e) self.notifier.log(f"Error calculating Halstead complexity: {str(e)}") return {} @@ -129,9 +158,12 @@ def maintainability_index(self, code: str) -> int: Returns: int: The Maintainability Index of the code. """ + try: - return rm.maintainability_index(code) + mi_score = rm.mi_visit(code, multi=True) + return mi_score except Exception as e: + print(e) self.notifier.log(f"Error calculating maintainability index: {str(e)}") return 0 diff --git a/test_file.py b/test_file.py new file mode 100644 index 0000000..7351411 --- /dev/null +++ b/test_file.py @@ -0,0 +1,2 @@ +def example_function(): + return 42 \ No newline at end of file diff --git a/tests/toolkits/test_complexity_analyzer.py b/tests/toolkits/test_complexity_analyzer.py index 413f314..a01932f 100644 --- a/tests/toolkits/test_complexity_analyzer.py +++ b/tests/toolkits/test_complexity_analyzer.py @@ -54,7 +54,7 @@ def test_cyclomatic_complexity(toolkit): f"Error calculating cyclomatic complexity: {str(e)}" ) - assert result[0].complexity == 2 + assert result == 2 def test_halstead_complexity(toolkit): @@ -82,7 +82,7 @@ def test_maintainability_index(toolkit): f"Error calculating maintainability index: {str(e)}" ) - assert isinstance(result, int) + assert isinstance(result, float) or isinstance(result, int) def test_aggregate_results(toolkit): From bf69a6a6fff84371a9d0399d35b26292cf0c5c51 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 03:36:48 +0100 Subject: [PATCH 12/25] feat:code complexity toolkit --- test_file.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 test_file.py diff --git a/test_file.py b/test_file.py deleted file mode 100644 index 7351411..0000000 --- a/test_file.py +++ /dev/null @@ -1,2 +0,0 @@ -def example_function(): - return 42 \ No newline at end of file From b23f2e3c7391df98cf128493634df9fc5f5eea3f Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 03:46:35 +0100 Subject: [PATCH 13/25] feat:code complexity toolkit --- .../toolkits/complexity_analyzer.py | 28 +++---------------- tests/toolkits/test_complexity_analyzer.py | 17 ----------- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/src/goose_plugins/toolkits/complexity_analyzer.py b/src/goose_plugins/toolkits/complexity_analyzer.py index 2ffab39..5dc0049 100644 --- a/src/goose_plugins/toolkits/complexity_analyzer.py +++ b/src/goose_plugins/toolkits/complexity_analyzer.py @@ -61,7 +61,6 @@ def analyze_complexity(self, directory: str) -> dict: "cyclomatic_complexity" ] += self.cyclomatic_complexity(code) halstead_result = self.halstead_complexity(code) - print("halstead_result", halstead_result) complexity_results["halstead_metrics"] += ( halstead_result["halstead_volume"] if halstead_result else 0 ) @@ -167,28 +166,9 @@ def maintainability_index(self, code: str) -> int: self.notifier.log(f"Error calculating maintainability index: {str(e)}") return 0 - @tool - def aggregate_results(self, results: dict) -> dict: - """Aggregate the complexity results from all analyzed files. - Args: - results (dict): A dictionary containing lists of complexity metrics across multiple files, - including Cyclomatic Complexity, Halstead Metrics, and Maintainability Index. +from unittest.mock import MagicMock - Returns: - dict: A dictionary containing the aggregated averages for each complexity metric. - """ - try: - aggregated_results = { - "avg_cyclomatic_complexity": sum(results["cyclomatic_complexity"]) - / len(results["cyclomatic_complexity"]), - "avg_halstead_complexity": sum( - [h["halstead_volume"] for h in results["halstead_metrics"]] - ) - / len(results["halstead_metrics"]), - "avg_maintainability_index": sum(results["maintainability_index"]) - / len(results["maintainability_index"]), - } - return aggregated_results - except ZeroDivisionError: - return {"error": "No valid results to aggregate."} +toolkit = CodeComplexityToolkit(notifier=MagicMock()) +result = toolkit.analyze_complexity("/Users/jevan/personal/algos") +print("result", result) diff --git a/tests/toolkits/test_complexity_analyzer.py b/tests/toolkits/test_complexity_analyzer.py index a01932f..2122d27 100644 --- a/tests/toolkits/test_complexity_analyzer.py +++ b/tests/toolkits/test_complexity_analyzer.py @@ -83,20 +83,3 @@ def test_maintainability_index(toolkit): ) assert isinstance(result, float) or isinstance(result, int) - - -def test_aggregate_results(toolkit): - results = { - "cyclomatic_complexity": [5, 10], - "halstead_metrics": [{"halstead_volume": 100}, {"halstead_volume": 200}], - "maintainability_index": [70, 60], - } - - aggregated = toolkit.aggregate_results(results) - - assert "avg_cyclomatic_complexity" in aggregated - assert "avg_halstead_complexity" in aggregated - assert "avg_maintainability_index" in aggregated - assert aggregated["avg_cyclomatic_complexity"] == 7.5 - assert aggregated["avg_halstead_complexity"] == 150 - assert aggregated["avg_maintainability_index"] == 65 From 9690e55cbf2e2fd320a93af9ba5689d94b014e67 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 18:05:27 +0100 Subject: [PATCH 14/25] feat:dockerise my app toolkit --- pyproject.toml | 1 + .../toolkits/complexity_analyzer.py | 1 + .../toolkits/dockerize_my_app.py | 114 ++++++++++++++++++ test_file.py | 2 + tests/toolkits/test_dockerize_my_app.py | 30 +++++ 5 files changed, 148 insertions(+) create mode 100644 src/goose_plugins/toolkits/dockerize_my_app.py create mode 100644 test_file.py create mode 100644 tests/toolkits/test_dockerize_my_app.py diff --git a/pyproject.toml b/pyproject.toml index d46d9fb..a57858a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,7 @@ goose-plugins = "goose_plugins:module_name" artify = "goose_plugins.toolkits.artify:VincentVanCode" todo = "goose_plugins.toolkits.todo:TodoToolkit" complexity_analyzer = "goose_plugins.toolkits.complexity_analyzer:CodeComplexityToolkit" +dockerize_my_app = "goose_plugins.toolkits.dockerize_my_app:DockerizationToolkit" [build-system] diff --git a/src/goose_plugins/toolkits/complexity_analyzer.py b/src/goose_plugins/toolkits/complexity_analyzer.py index 5dc0049..6e32cb9 100644 --- a/src/goose_plugins/toolkits/complexity_analyzer.py +++ b/src/goose_plugins/toolkits/complexity_analyzer.py @@ -159,6 +159,7 @@ def maintainability_index(self, code: str) -> int: """ try: + mi_score = rm.mi_visit(code, multi=True) return mi_score except Exception as e: diff --git a/src/goose_plugins/toolkits/dockerize_my_app.py b/src/goose_plugins/toolkits/dockerize_my_app.py new file mode 100644 index 0000000..7e3620f --- /dev/null +++ b/src/goose_plugins/toolkits/dockerize_my_app.py @@ -0,0 +1,114 @@ +import os +from goose.toolkit.base import Toolkit, tool + + +class DockerizationToolkit(Toolkit): + """Dockerizes an application based + on its project type (Node.js, Python, Java).""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @tool + def dockerize(self, project_dir: str, output_dir: str | None = None) -> dict: + """ + Dockerize a project by generating Docker-related files. + + Args: + project_dir (str): Path to the project directory. + output_dir (str, optional): Output directory for Docker files. + Returns: + dict: Status of the operation and output details. + """ + try: + dockerizer = Dockerizer() + result = dockerizer.generate(project_dir, output_dir) + return {"status": "success", "details": result} + except Exception as e: + return {"status": "error", "message": str(e)} + + +class Dockerizer: + def detect_project_type(self, project_dir): + """Detect the project type based on common configuration files.""" + if os.path.exists(os.path.join(project_dir, "package.json")): + return "nodejs" + elif os.path.exists(os.path.join(project_dir, "requirements.txt")): + return "python" + elif os.path.exists(os.path.join(project_dir, "pom.xml")): + return "java" + else: + raise ValueError("Unsupported project type or no recognizable files found.") + + def generate(self, project_dir, output_dir=None): + """Generate Docker-related files.""" + project_type = self.detect_project_type(project_dir) + output_dir = output_dir or project_dir + os.makedirs(output_dir, exist_ok=True) + + # Generate files based on the project type + if project_type == "nodejs": + self._generate_nodejs_files(output_dir) + elif project_type == "python": + self._generate_python_files(output_dir) + elif project_type == "java": + self._generate_java_files(output_dir) + + return {"project_type": project_type, "output_dir": output_dir} + + def _generate_python_files(self, output_dir): + dockerfile_content = """\ +FROM python:3.10-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +CMD ["python", "app.py"] + """ + self._write_file(output_dir, "Dockerfile", dockerfile_content) + + dockerignore_content = """\ +__pycache__/ +*.pyc +.env +.git/ + """ + self._write_file(output_dir, ".dockerignore", dockerignore_content) + + def _generate_nodejs_files(self, output_dir): + dockerfile_content = """\ +FROM node:18-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +CMD ["npm", "start"] + """ + self._write_file(output_dir, "Dockerfile", dockerfile_content) + + dockerignore_content = """\ +node_modules/ +npm-debug.log +.git/ + """ + self._write_file(output_dir, ".dockerignore", dockerignore_content) + + def _generate_java_files(self, output_dir): + dockerfile_content = """\ +FROM openjdk:17-slim +WORKDIR /app +COPY . . +RUN ./mvnw clean package +CMD ["java", "-jar", "target/app.jar"] + """ + self._write_file(output_dir, "Dockerfile", dockerfile_content) + + dockerignore_content = """\ +target/ +.git/ + """ + self._write_file(output_dir, ".dockerignore", dockerignore_content) + + def _write_file(self, directory, filename, content): + with open(os.path.join(directory, filename), "w") as f: + f.write(content) diff --git a/test_file.py b/test_file.py new file mode 100644 index 0000000..7351411 --- /dev/null +++ b/test_file.py @@ -0,0 +1,2 @@ +def example_function(): + return 42 \ No newline at end of file diff --git a/tests/toolkits/test_dockerize_my_app.py b/tests/toolkits/test_dockerize_my_app.py new file mode 100644 index 0000000..63d112f --- /dev/null +++ b/tests/toolkits/test_dockerize_my_app.py @@ -0,0 +1,30 @@ +import os +import pytest +from unittest.mock import MagicMock +from goose_plugins.toolkits.dockerize_my_app import DockerizationToolkit + + +@pytest.fixture +def toolkit(): + return DockerizationToolkit(notifier=MagicMock()) + + +def test_dockerize_nodejs(toolkit, tmp_path): + project_dir = tmp_path / "node_project" + project_dir.mkdir() + print(project_dir) + (project_dir / "package.json").write_text("{}") + + result = toolkit.dockerize(str(project_dir)) + assert result["status"] == "success" + assert os.path.exists(project_dir / "Dockerfile") + + +def test_dockerize_python(toolkit, tmp_path): + project_dir = tmp_path / "python_project" + project_dir.mkdir() + (project_dir / "requirements.txt").write_text("flask") + + result = toolkit.dockerize(str(project_dir)) + assert result["status"] == "success" + assert os.path.exists(project_dir / "Dockerfile") From 864090f931fd7da7eef1aff78deb74e34aabe97d Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 18:44:31 +0100 Subject: [PATCH 15/25] feat:dockerise my app toolkit --- pyproject.toml | 1 - .../toolkits/complexity_analyzer.py | 175 ------------------ test_file.py | 2 - tests/toolkits/test_complexity_analyzer.py | 85 --------- 4 files changed, 263 deletions(-) delete mode 100644 src/goose_plugins/toolkits/complexity_analyzer.py delete mode 100644 test_file.py delete mode 100644 tests/toolkits/test_complexity_analyzer.py diff --git a/pyproject.toml b/pyproject.toml index a57858a..2eb2b36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,6 @@ goose-plugins = "goose_plugins:module_name" [project.entry-points."goose.toolkit"] artify = "goose_plugins.toolkits.artify:VincentVanCode" todo = "goose_plugins.toolkits.todo:TodoToolkit" -complexity_analyzer = "goose_plugins.toolkits.complexity_analyzer:CodeComplexityToolkit" dockerize_my_app = "goose_plugins.toolkits.dockerize_my_app:DockerizationToolkit" diff --git a/src/goose_plugins/toolkits/complexity_analyzer.py b/src/goose_plugins/toolkits/complexity_analyzer.py deleted file mode 100644 index 6e32cb9..0000000 --- a/src/goose_plugins/toolkits/complexity_analyzer.py +++ /dev/null @@ -1,175 +0,0 @@ -import os -import ast -from goose.toolkit.base import Toolkit, tool -import radon.complexity as rc -import radon.metrics as rm - - -class CodeComplexityToolkit(Toolkit): - """A toolkit for analyzing the complexity of Python code in a given directory.""" - - def __init__(self, *args: tuple, **kwargs: dict) -> None: - super().__init__(*args, **kwargs) - - @tool - def get_python_files(self, directory: str) -> list: - """Retrieve all Python files from the specified directory. - - Args: - directory (str): The directory to search for Python files. - - Returns: - list: A list of paths to all Python files in the directory. - """ - return [ - os.path.join(root, file) - for root, _, files in os.walk(directory) - for file in files - if file.endswith(".py") - ] - - @tool - def analyze_complexity(self, directory: str) -> dict: - """Analyze the complexity of Python code in a directory. - - Args: - directory (str): The path to the directory containing Python files to analyze. - - Returns: - dict: A dictionary containing the average complexity metrics (Cyclomatic Complexity, Halstead Metrics, - and Maintainability Index) for all Python files in the directory, or an error message if no - valid Python files are found. - """ - python_files = self.get_python_files(directory) - if not python_files: - return {"error": f"No Python files found in the directory: {directory}"} - - complexity_results = { - "cyclomatic_complexity": 0, - "halstead_metrics": 0, - "maintainability_index": 0, - "file_count": 0, - } - - for file in python_files: - try: - with open(file, "r", encoding="utf-8") as f: - code = f.read() - - # Process each complexity metric and update the results - complexity_results[ - "cyclomatic_complexity" - ] += self.cyclomatic_complexity(code) - halstead_result = self.halstead_complexity(code) - complexity_results["halstead_metrics"] += ( - halstead_result["halstead_volume"] if halstead_result else 0 - ) - complexity_results[ - "maintainability_index" - ] += self.maintainability_index(code) - complexity_results["file_count"] += 1 - - except Exception as e: - complexity_results["error"] = f"Error processing {file}: {str(e)}" - continue - - if complexity_results["file_count"] > 0: - # Average the results - return { - "avg_cyclomatic_complexity": complexity_results["cyclomatic_complexity"] - / complexity_results["file_count"], - "avg_halstead_complexity": complexity_results["halstead_metrics"] - / complexity_results["file_count"], - "avg_maintainability_index": complexity_results["maintainability_index"] - / complexity_results["file_count"], - } - else: - return {"error": "No valid Python files to analyze."} - - @tool - def cyclomatic_complexity(self, code: str) -> int: - """Calculate the Cyclomatic Complexity of a given Python code. - - Args: - code (str): The Python code as a string to analyze. - - Returns: - int: The Cyclomatic Complexity of the code. - """ - try: - complexity_list = rc.cc_visit(ast.parse(code)) - total_complexity = 0 - - # Iterate over each item in the complexity list - for item in complexity_list: - if hasattr(item, "complexity"): - # Add complexity of the function or class's top-level complexity - total_complexity += item.complexity - - # For classes, add complexity of methods if any - if hasattr(item, "methods"): - for method in item.methods: - total_complexity += method.complexity - return total_complexity - except Exception as e: - print(e) - self.notifier.log(f"Error calculating cyclomatic complexity: {str(e)}") - return 0 - - @tool - def halstead_complexity(self, code: str) -> dict: - """Calculate Halstead Complexity metrics of the given Python code. - - Args: - code (str): The Python code as a string to analyze. - - Returns: - dict: A dictionary containing the Halstead metrics, including 'halstead_volume'. - """ - from radon.metrics import h_visit - - try: - halstead_report = h_visit(code) - return { - "halstead_volume": halstead_report.total.volume, - "details": { - "vocabulary": halstead_report.total.vocabulary, - "length": halstead_report.total.length, - "calculated_length": halstead_report.total.calculated_length, - "difficulty": halstead_report.total.difficulty, - "effort": halstead_report.total.effort, - "time": halstead_report.total.time, - "bugs": halstead_report.total.bugs, - }, - } - except Exception as e: - print(e) - self.notifier.log(f"Error calculating Halstead complexity: {str(e)}") - return {} - - @tool - def maintainability_index(self, code: str) -> int: - """Calculate the Maintainability Index of the given Python code. - - Args: - code (str): The Python code as a string to analyze. - - Returns: - int: The Maintainability Index of the code. - """ - - try: - - mi_score = rm.mi_visit(code, multi=True) - return mi_score - except Exception as e: - print(e) - self.notifier.log(f"Error calculating maintainability index: {str(e)}") - return 0 - - -from unittest.mock import MagicMock - -toolkit = CodeComplexityToolkit(notifier=MagicMock()) -result = toolkit.analyze_complexity("/Users/jevan/personal/algos") -print("result", result) diff --git a/test_file.py b/test_file.py deleted file mode 100644 index 7351411..0000000 --- a/test_file.py +++ /dev/null @@ -1,2 +0,0 @@ -def example_function(): - return 42 \ No newline at end of file diff --git a/tests/toolkits/test_complexity_analyzer.py b/tests/toolkits/test_complexity_analyzer.py deleted file mode 100644 index 2122d27..0000000 --- a/tests/toolkits/test_complexity_analyzer.py +++ /dev/null @@ -1,85 +0,0 @@ -import pytest -from unittest.mock import MagicMock -from goose_plugins.toolkits.complexity_analyzer import CodeComplexityToolkit - - -@pytest.fixture -def toolkit(): - toolkit = CodeComplexityToolkit(notifier=MagicMock()) - return toolkit - - -def test_get_python_files(toolkit): - directory = "test_directory" - - # Simulate os.walk to mock the file retrieval process - toolkit.get_python_files = MagicMock( - return_value=["test_file.py", "another_test_file.py"] - ) - - result = toolkit.get_python_files(directory) - - # Check that the mocked method was called with the correct argument - toolkit.get_python_files.assert_called_with(directory) - assert result == ["test_file.py", "another_test_file.py"] - - -def test_analyze_complexity(toolkit): - directory = "test_directory" - - # Mock methods that would be used during complexity analysis - toolkit.get_python_files = MagicMock(return_value=["test_file.py"]) - toolkit.cyclomatic_complexity = MagicMock(return_value=5) - toolkit.halstead_complexity = MagicMock(return_value={"halstead_volume": 100}) - toolkit.maintainability_index = MagicMock(return_value=70) - - # Mock file content reading - with open("test_file.py", "w") as f: - f.write("def example_function():\n return 42") - - result = toolkit.analyze_complexity(directory) - assert "avg_cyclomatic_complexity" in result - assert "avg_halstead_complexity" in result - assert "avg_maintainability_index" in result - - -def test_cyclomatic_complexity(toolkit): - code = "def test_func():\n if True:\n return 1" - - try: - result = toolkit.cyclomatic_complexity(code) - except Exception as e: - result = None - toolkit.notifier.log.assert_called_with( - f"Error calculating cyclomatic complexity: {str(e)}" - ) - - assert result == 2 - - -def test_halstead_complexity(toolkit): - code = "def test_func():\n return 42" - - try: - result = toolkit.halstead_complexity(code) - except Exception as e: - result = None - toolkit.notifier.log.assert_called_with( - f"Error calculating Halstead complexity: {str(e)}" - ) - - assert isinstance(result, dict) - - -def test_maintainability_index(toolkit): - code = "def test_func():\n return 42" - - try: - result = toolkit.maintainability_index(code) - except Exception as e: - result = None - toolkit.notifier.log.assert_called_with( - f"Error calculating maintainability index: {str(e)}" - ) - - assert isinstance(result, float) or isinstance(result, int) From b206e0fc6d4fabfbf443007dda65a71b145859a5 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Sun, 17 Nov 2024 18:45:42 +0100 Subject: [PATCH 16/25] revert previous pr changes --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2eb2b36..e2eaebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,7 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "ai-exchange>=0.8.4", - "goose-ai>=0.9.8", - "radon>=6.0.1", + "goose-ai>=0.9.8" ] author = [{ name = "Block", email = "ai-oss-tools@block.xyz" }] packages = [{ include = "goose_plugins", from = "src" }] From 1f16006f16776539aece81a93347b0496e9e48d6 Mon Sep 17 00:00:00 2001 From: Rizel Scarlett Date: Mon, 18 Nov 2024 11:12:50 -0500 Subject: [PATCH 17/25] add back comma --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e2eaebc..d0fd3ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "ai-exchange>=0.8.4", - "goose-ai>=0.9.8" + "goose-ai>=0.9.8", ] author = [{ name = "Block", email = "ai-oss-tools@block.xyz" }] packages = [{ include = "goose_plugins", from = "src" }] From 63a8101068ca5cf36f69a658d84288c536e349f8 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 17:31:32 +0100 Subject: [PATCH 18/25] fix --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2eb2b36..e2eaebc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,7 @@ readme = "README.md" requires-python = ">=3.10" dependencies = [ "ai-exchange>=0.8.4", - "goose-ai>=0.9.8", - "radon>=6.0.1", + "goose-ai>=0.9.8" ] author = [{ name = "Block", email = "ai-oss-tools@block.xyz" }] packages = [{ include = "goose_plugins", from = "src" }] From 6a4380ed73532d274448082e6c4ad36593482c4a Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 17:33:58 +0100 Subject: [PATCH 19/25] fix --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 03556a6..a57858a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.10" dependencies = [ "ai-exchange>=0.8.4", "goose-ai>=0.9.8", + "radon>=6.0.1", ] author = [{ name = "Block", email = "ai-oss-tools@block.xyz" }] packages = [{ include = "goose_plugins", from = "src" }] From eb22403c2669d5c338d66f2997b957e7cf766ea5 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 17:38:36 +0100 Subject: [PATCH 20/25] fix --- src/goose_plugins/toolkits/dockerize_my_app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/goose_plugins/toolkits/dockerize_my_app.py b/src/goose_plugins/toolkits/dockerize_my_app.py index 7e3620f..fbe96d0 100644 --- a/src/goose_plugins/toolkits/dockerize_my_app.py +++ b/src/goose_plugins/toolkits/dockerize_my_app.py @@ -29,7 +29,7 @@ def dockerize(self, project_dir: str, output_dir: str | None = None) -> dict: class Dockerizer: - def detect_project_type(self, project_dir): + def detect_project_type(self, project_dir: str) -> str: """Detect the project type based on common configuration files.""" if os.path.exists(os.path.join(project_dir, "package.json")): return "nodejs" @@ -40,7 +40,7 @@ def detect_project_type(self, project_dir): else: raise ValueError("Unsupported project type or no recognizable files found.") - def generate(self, project_dir, output_dir=None): + def generate(self, project_dir: str, output_dir: str | None = None) -> dict: """Generate Docker-related files.""" project_type = self.detect_project_type(project_dir) output_dir = output_dir or project_dir @@ -56,7 +56,7 @@ def generate(self, project_dir, output_dir=None): return {"project_type": project_type, "output_dir": output_dir} - def _generate_python_files(self, output_dir): + def _generate_python_files(self, output_dir: str) -> None: dockerfile_content = """\ FROM python:3.10-slim WORKDIR /app @@ -75,7 +75,7 @@ def _generate_python_files(self, output_dir): """ self._write_file(output_dir, ".dockerignore", dockerignore_content) - def _generate_nodejs_files(self, output_dir): + def _generate_nodejs_files(self, output_dir: str) -> None: dockerfile_content = """\ FROM node:18-alpine WORKDIR /app @@ -93,7 +93,7 @@ def _generate_nodejs_files(self, output_dir): """ self._write_file(output_dir, ".dockerignore", dockerignore_content) - def _generate_java_files(self, output_dir): + def _generate_java_files(self, output_dir: str) -> None: dockerfile_content = """\ FROM openjdk:17-slim WORKDIR /app @@ -109,6 +109,6 @@ def _generate_java_files(self, output_dir): """ self._write_file(output_dir, ".dockerignore", dockerignore_content) - def _write_file(self, directory, filename, content): + def _write_file(self, directory: str, filename: str, content: str) -> None: with open(os.path.join(directory, filename), "w") as f: f.write(content) From 0ae1f8b20d3c2748b4bb97ccc4e0b2f4604d3890 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 17:51:26 +0100 Subject: [PATCH 21/25] fix --- src/goose_plugins/toolkits/dockerize_my_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/goose_plugins/toolkits/dockerize_my_app.py b/src/goose_plugins/toolkits/dockerize_my_app.py index fbe96d0..4f52475 100644 --- a/src/goose_plugins/toolkits/dockerize_my_app.py +++ b/src/goose_plugins/toolkits/dockerize_my_app.py @@ -6,7 +6,7 @@ class DockerizationToolkit(Toolkit): """Dockerizes an application based on its project type (Node.js, Python, Java).""" - def __init__(self, *args, **kwargs): + def __init__(self, *args: tuple, **kwargs: dict) -> None: super().__init__(*args, **kwargs) @tool From de4ef7f495e332a02bca7cf14003fff78dc6c946 Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 18:29:37 +0100 Subject: [PATCH 22/25] fix --- .../toolkits/complexity_analyzer.py | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/goose_plugins/toolkits/complexity_analyzer.py b/src/goose_plugins/toolkits/complexity_analyzer.py index 027f52e..891406f 100644 --- a/src/goose_plugins/toolkits/complexity_analyzer.py +++ b/src/goose_plugins/toolkits/complexity_analyzer.py @@ -1,8 +1,8 @@ import os import ast from goose.toolkit.base import Toolkit, tool -import radon.complexity as rc -import radon.metrics as rm +from radon.complexity import cc_visit +from radon.metrics import h_visit, mi_visit class CodeComplexityToolkit(Toolkit): @@ -22,10 +22,7 @@ def get_python_files(self, directory: str) -> list: list: A list of paths to all Python files in the directory. """ return [ - os.path.join(root, file) - for root, _, files in os.walk(directory) - for file in files - if file.endswith(".py") + os.path.join(root, file) for root, _, files in os.walk(directory) for file in files if file.endswith(".py") ] @tool @@ -57,16 +54,10 @@ def analyze_complexity(self, directory: str) -> dict: code = f.read() # Process each complexity metric and update the results - complexity_results[ - "cyclomatic_complexity" - ] += self.cyclomatic_complexity(code) + complexity_results["cyclomatic_complexity"] += self.cyclomatic_complexity(code) halstead_result = self.halstead_complexity(code) - complexity_results["halstead_metrics"] += ( - halstead_result["halstead_volume"] if halstead_result else 0 - ) - complexity_results[ - "maintainability_index" - ] += self.maintainability_index(code) + complexity_results["halstead_metrics"] += halstead_result["halstead_volume"] if halstead_result else 0 + complexity_results["maintainability_index"] += self.maintainability_index(code) complexity_results["file_count"] += 1 except Exception as e: @@ -78,8 +69,7 @@ def analyze_complexity(self, directory: str) -> dict: return { "avg_cyclomatic_complexity": complexity_results["cyclomatic_complexity"] / complexity_results["file_count"], - "avg_halstead_complexity": complexity_results["halstead_metrics"] - / complexity_results["file_count"], + "avg_halstead_complexity": complexity_results["halstead_metrics"] / complexity_results["file_count"], "avg_maintainability_index": complexity_results["maintainability_index"] / complexity_results["file_count"], } @@ -97,7 +87,7 @@ def cyclomatic_complexity(self, code: str) -> int: int: The Cyclomatic Complexity of the code. """ try: - complexity_list = rc.cc_visit(ast.parse(code)) + complexity_list = cc_visit(ast.pare(code)) total_complexity = 0 # Iterate over each item in the complexity list @@ -126,7 +116,6 @@ def halstead_complexity(self, code: str) -> dict: Returns: dict: A dictionary containing the Halstead metrics, including 'halstead_volume'. """ - from radon.metrics import h_visit try: halstead_report = h_visit(code) @@ -159,7 +148,7 @@ def maintainability_index(self, code: str) -> int: """ try: - mi_score = rm.mi_visit(code, multi=True) + mi_score = mi_visit(code, multi=True) return mi_score except Exception as e: print(e) From 999f2390513449b67dc4933a5821f0b727eae8ad Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 18:31:39 +0100 Subject: [PATCH 23/25] fix --- .../toolkits/complexity_analyzer.py | 22 ++++++++++++++----- test_file.py | 2 ++ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 test_file.py diff --git a/src/goose_plugins/toolkits/complexity_analyzer.py b/src/goose_plugins/toolkits/complexity_analyzer.py index 891406f..bc796f6 100644 --- a/src/goose_plugins/toolkits/complexity_analyzer.py +++ b/src/goose_plugins/toolkits/complexity_analyzer.py @@ -22,7 +22,10 @@ def get_python_files(self, directory: str) -> list: list: A list of paths to all Python files in the directory. """ return [ - os.path.join(root, file) for root, _, files in os.walk(directory) for file in files if file.endswith(".py") + os.path.join(root, file) + for root, _, files in os.walk(directory) + for file in files + if file.endswith(".py") ] @tool @@ -54,10 +57,16 @@ def analyze_complexity(self, directory: str) -> dict: code = f.read() # Process each complexity metric and update the results - complexity_results["cyclomatic_complexity"] += self.cyclomatic_complexity(code) + complexity_results[ + "cyclomatic_complexity" + ] += self.cyclomatic_complexity(code) halstead_result = self.halstead_complexity(code) - complexity_results["halstead_metrics"] += halstead_result["halstead_volume"] if halstead_result else 0 - complexity_results["maintainability_index"] += self.maintainability_index(code) + complexity_results["halstead_metrics"] += ( + halstead_result["halstead_volume"] if halstead_result else 0 + ) + complexity_results[ + "maintainability_index" + ] += self.maintainability_index(code) complexity_results["file_count"] += 1 except Exception as e: @@ -69,7 +78,8 @@ def analyze_complexity(self, directory: str) -> dict: return { "avg_cyclomatic_complexity": complexity_results["cyclomatic_complexity"] / complexity_results["file_count"], - "avg_halstead_complexity": complexity_results["halstead_metrics"] / complexity_results["file_count"], + "avg_halstead_complexity": complexity_results["halstead_metrics"] + / complexity_results["file_count"], "avg_maintainability_index": complexity_results["maintainability_index"] / complexity_results["file_count"], } @@ -87,7 +97,7 @@ def cyclomatic_complexity(self, code: str) -> int: int: The Cyclomatic Complexity of the code. """ try: - complexity_list = cc_visit(ast.pare(code)) + complexity_list = cc_visit(ast.parse(code)) total_complexity = 0 # Iterate over each item in the complexity list diff --git a/test_file.py b/test_file.py new file mode 100644 index 0000000..7351411 --- /dev/null +++ b/test_file.py @@ -0,0 +1,2 @@ +def example_function(): + return 42 \ No newline at end of file From cb0c33bf9f3b3620a9011adb1cdafda8eab3065e Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 18:33:00 +0100 Subject: [PATCH 24/25] fix --- test_file.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 test_file.py diff --git a/test_file.py b/test_file.py deleted file mode 100644 index 7351411..0000000 --- a/test_file.py +++ /dev/null @@ -1,2 +0,0 @@ -def example_function(): - return 42 \ No newline at end of file From ce19d748b67ab2478360995c351c302fda4c6a8c Mon Sep 17 00:00:00 2001 From: johnny evans Date: Mon, 18 Nov 2024 18:33:56 +0100 Subject: [PATCH 25/25] fix --- .../toolkits/complexity_analyzer.py | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/goose_plugins/toolkits/complexity_analyzer.py b/src/goose_plugins/toolkits/complexity_analyzer.py index bc796f6..8755a80 100644 --- a/src/goose_plugins/toolkits/complexity_analyzer.py +++ b/src/goose_plugins/toolkits/complexity_analyzer.py @@ -22,10 +22,7 @@ def get_python_files(self, directory: str) -> list: list: A list of paths to all Python files in the directory. """ return [ - os.path.join(root, file) - for root, _, files in os.walk(directory) - for file in files - if file.endswith(".py") + os.path.join(root, file) for root, _, files in os.walk(directory) for file in files if file.endswith(".py") ] @tool @@ -57,16 +54,10 @@ def analyze_complexity(self, directory: str) -> dict: code = f.read() # Process each complexity metric and update the results - complexity_results[ - "cyclomatic_complexity" - ] += self.cyclomatic_complexity(code) + complexity_results["cyclomatic_complexity"] += self.cyclomatic_complexity(code) halstead_result = self.halstead_complexity(code) - complexity_results["halstead_metrics"] += ( - halstead_result["halstead_volume"] if halstead_result else 0 - ) - complexity_results[ - "maintainability_index" - ] += self.maintainability_index(code) + complexity_results["halstead_metrics"] += halstead_result["halstead_volume"] if halstead_result else 0 + complexity_results["maintainability_index"] += self.maintainability_index(code) complexity_results["file_count"] += 1 except Exception as e: @@ -78,8 +69,7 @@ def analyze_complexity(self, directory: str) -> dict: return { "avg_cyclomatic_complexity": complexity_results["cyclomatic_complexity"] / complexity_results["file_count"], - "avg_halstead_complexity": complexity_results["halstead_metrics"] - / complexity_results["file_count"], + "avg_halstead_complexity": complexity_results["halstead_metrics"] / complexity_results["file_count"], "avg_maintainability_index": complexity_results["maintainability_index"] / complexity_results["file_count"], }