Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dockerize my app #72

Merged
merged 29 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
369c1b1
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
fa0972a
Delete .DS_Store
Johnnyevans32 Nov 17, 2024
09e50d6
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
35b38a9
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
5dcaf78
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
b3a7fc6
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
46b44ef
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
a3bd1b0
Delete .DS_Store
Johnnyevans32 Nov 17, 2024
1add564
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
aa97bea
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
f5768d6
Merge branch 'main' of https://github.com/Johnnyevans32/goose-plugins
Johnnyevans32 Nov 17, 2024
fce4530
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
9aaadb4
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
bf69a6a
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
b23f2e3
feat:code complexity toolkit
Johnnyevans32 Nov 17, 2024
9690e55
feat:dockerise my app toolkit
Johnnyevans32 Nov 17, 2024
864090f
feat:dockerise my app toolkit
Johnnyevans32 Nov 17, 2024
b206e0f
revert previous pr changes
Johnnyevans32 Nov 17, 2024
1f16006
add back comma
blackgirlbytes Nov 18, 2024
63a8101
fix
Johnnyevans32 Nov 18, 2024
7fbd0d1
fix
Johnnyevans32 Nov 18, 2024
86fbc00
Merge branch 'dockerize_my_app' of https://github.com/Johnnyevans32/g…
Johnnyevans32 Nov 18, 2024
6a4380e
fix
Johnnyevans32 Nov 18, 2024
eb22403
fix
Johnnyevans32 Nov 18, 2024
0ae1f8b
fix
Johnnyevans32 Nov 18, 2024
de4ef7f
fix
Johnnyevans32 Nov 18, 2024
999f239
fix
Johnnyevans32 Nov 18, 2024
cb0c33b
fix
Johnnyevans32 Nov 18, 2024
ce19d74
fix
Johnnyevans32 Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ share/python-wheels/
*.egg
MANIFEST


.DS_Store
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm should we remove this since it wasnt in the original gitignore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed now @blackgirlbytes


# 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.
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "[email protected]" }]
packages = [{ include = "goose_plugins", from = "src" }]
Expand All @@ -20,6 +21,8 @@ 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"


[build-system]
Expand Down
175 changes: 175 additions & 0 deletions src/goose_plugins/toolkits/complexity_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
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)
114 changes: 114 additions & 0 deletions src/goose_plugins/toolkits/dockerize_my_app.py
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions test_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def example_function():
return 42
Loading