Skip to content

Commit

Permalink
feat(build): #1 add support for git repositories
Browse files Browse the repository at this point in the history
- Change version to `0.2.0`
- Add support to analyze git repositories from
GitHub and GitLab.
- Calculate the cognitive complexity of the files
in the repository.
  • Loading branch information
rohaquinlop committed Feb 25, 2024
1 parent 74ec5ce commit bd18315
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 18 deletions.
58 changes: 56 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "complexipy"
version = "1.0.0"
version = "0.2.0"
edition = "2021"
authors = ["Robin Quintero <rohaquinlop301@gmail.com>"]
license = "MIT"
Expand All @@ -22,3 +22,4 @@ log = "0.4.20"
pyo3 = "0.19.0"
rayon = "1.8.1"
rustpython-parser = { git = "https://github.com/RustPython/Parser.git", rev = "9ce55aefdeb35e2f706ce0b02d5a2dfe6295fc57" }
tempfile = "3.10.0"
42 changes: 33 additions & 9 deletions complexipy/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pathlib import Path
from complexipy import rust
import os
import re
from rich.console import Console
from rich.table import Table
import time
Expand All @@ -11,27 +12,39 @@
root_dir = Path(__file__).resolve().parent.parent
app = typer.Typer(name="complexipy")
console = Console()
version = "1.0.0"
version = "0.2.0"


@app.command()
def main(
path: str,
max_complexity: int = typer.Option(15, "--max-complexity", "-c", help="The maximum complexity allowed, set this value as 0 to set it as unlimited."),
output: bool = typer.Option(False, "--output", "-o", help="Output the results to a XML file."),
max_complexity: int = typer.Option(
15,
"--max-complexity",
"-c",
help="The maximum complexity allowed, set this value as 0 to set it as unlimited.",
),
output: bool = typer.Option(
False, "--output", "-o", help="Output the results to a XML file."
),
):
has_success = True
is_dir = Path(path).is_dir()
_url_pattern = (
r"^(https:\/\/|http:\/\/|www\.|git@)(github|gitlab)\.com(\/[\w.-]+){2,}$"
)
is_url = bool(re.match(_url_pattern, path))
invocation_path = os.getcwd()

console.rule(f"complexipy {version} :octopus:")
with console.status("Analyzing the complexity of the code...", spinner="dots"):
start_time = time.time()
files = rust.main(path, is_dir, max_complexity)
files = rust.main(path, is_dir, is_url, max_complexity)
execution_time = time.time() - start_time
console.print("Analysis completed! :tada:")
console.rule("Analysis completed! :tada:")

# Output to XML
if output:
invocation_path = os.getcwd()
xml_file = ET.Element("complexity")
for file in files:
file_xml = ET.SubElement(xml_file, "file")
Expand All @@ -49,21 +62,32 @@ def main(
console.print(f"Results saved to {invocation_path}/complexipy.xml")

# Summary
table = Table(title="Summary", show_header=True, header_style="bold magenta", show_lines=True)
table = Table(
title="Summary", show_header=True, header_style="bold magenta", show_lines=True
)
table.add_column("Path")
table.add_column("File")
table.add_column("Complexity")
for file in files:
if file.complexity > max_complexity and max_complexity != 0:
table.add_row(f"{file.path}", f"[green]{file.file_name}[/green]", f"[red]{file.complexity}[/red]")
table.add_row(
f"{file.path}",
f"[green]{file.file_name}[/green]",
f"[red]{file.complexity}[/red]",
)
has_success = False
else:
table.add_row(f"{file.path}", f"[green]{file.file_name}[/green]", f"[blue]{file.complexity}[/blue]")
table.add_row(
f"{file.path}",
f"[green]{file.file_name}[/green]",
f"[blue]{file.complexity}[/blue]",
)
console.print(table)
console.print(f"{len(files)} files analyzed in {execution_time:.4f} seconds")

if not has_success:
raise typer.Exit(code=1)


if __name__ == "__main__":
app()
45 changes: 39 additions & 6 deletions src/cognitive_complexity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,48 @@ use rustpython_parser::{
ast::{self, Stmt},
Parse,
};
use std::env;
use std::path;
use utils::{count_bool_ops, has_recursive_calls, is_decorator};
use std::process;
use tempfile::tempdir;
use utils::{count_bool_ops, get_repo_name, has_recursive_calls, is_decorator};

// Main function
#[pyfunction]
pub fn main(path: &str, is_dir: bool, max_complexity: usize) -> PyResult<Vec<FileComplexity>> {
pub fn main(
path: &str,
is_dir: bool,
is_url: bool,
max_complexity: usize,
) -> PyResult<Vec<FileComplexity>> {
let mut ans: Vec<FileComplexity> = Vec::new();
if is_dir {

if is_url {
let dir = tempdir()?;
let repo_name = get_repo_name(path);

env::set_current_dir(&dir)?;

let _output = process::Command::new("git")
.args(&["clone", path])
.output()
.expect("failed to execute process");

let repo_path = dir.path().join(&repo_name).to_str().unwrap().to_string();

match evaluate_dir(&repo_path, max_complexity) {
Ok(files_complexity) => ans = files_complexity,
Err(e) => return Err(e),
}

dir.close()?;
} else if is_dir {
match evaluate_dir(path, max_complexity) {
Ok(files_complexity) => ans = files_complexity,
Err(e) => return Err(e),
}
} else {
match file_cognitive_complexity(path, max_complexity) {
match file_cognitive_complexity(path, path, max_complexity) {
Ok(file_complexity) => ans.push(file_complexity),
Err(e) => return Err(e),
}
Expand All @@ -35,6 +63,8 @@ pub fn main(path: &str, is_dir: bool, max_complexity: usize) -> PyResult<Vec<Fil
pub fn evaluate_dir(path: &str, max_complexity: usize) -> PyResult<Vec<FileComplexity>> {
let mut files_paths: Vec<String> = Vec::new();

let parent_dir = path::Path::new(path).parent().unwrap().to_str().unwrap();

// Get all the python files in the directory
for entry in Walk::new(path) {
let entry = entry.unwrap();
Expand All @@ -48,7 +78,7 @@ pub fn evaluate_dir(path: &str, max_complexity: usize) -> PyResult<Vec<FileCompl
let files_complexity_result: Result<Vec<FileComplexity>, PyErr> = files_paths
.par_iter()
.map(
|file_path| match file_cognitive_complexity(file_path, max_complexity) {
|file_path| match file_cognitive_complexity(file_path, parent_dir, max_complexity) {
Ok(file_complexity) => Ok(file_complexity),
Err(e) => Err(e),
},
Expand All @@ -65,6 +95,7 @@ pub fn evaluate_dir(path: &str, max_complexity: usize) -> PyResult<Vec<FileCompl
#[pyfunction]
pub fn file_cognitive_complexity(
file_path: &str,
base_path: &str,
_max_complexity: usize,
) -> PyResult<FileComplexity> {
let code = std::fs::read_to_string(file_path)?;
Expand All @@ -74,14 +105,16 @@ pub fn file_cognitive_complexity(
let path = path::Path::new(file_path);
let file_name = path.file_name().unwrap().to_str().unwrap();

let relative_path = path.strip_prefix(base_path).unwrap().to_str().unwrap();

for node in ast.iter() {
complexity += statement_cognitive_complexity(node.clone(), 0)?;
}

println!("{}", file_name);

Ok(FileComplexity {
path: file_path.to_string(),
path: relative_path.to_string(),
file_name: file_name.to_string(),
complexity: complexity,
})
Expand Down
14 changes: 14 additions & 0 deletions src/cognitive_complexity/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
use rustpython_parser::ast::{self, Stmt};

pub fn get_repo_name(url: &str) -> String {
let url = url.trim_end_matches('/');

let repo_name = url.split('/').last().unwrap();

let repo_name = if repo_name.ends_with(".git") {
&repo_name[..repo_name.len() - 4]
} else {
repo_name
};

repo_name.to_string()
}

pub fn has_recursive_calls(statement: Stmt) -> bool {
let mut ans = false;

Expand Down

0 comments on commit bd18315

Please sign in to comment.