diff --git a/Cargo.lock b/Cargo.lock index 19e2a99..3da97ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,13 +65,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "complexipy" -version = "0.5.0" +version = "1.1.0" dependencies = [ "csv", "ignore", "indicatif", "pyo3", "rayon", + "regex", "rustpython-parser", "tempfile", ] @@ -661,11 +662,23 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -674,9 +687,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-hash" diff --git a/Cargo.toml b/Cargo.toml index cad76cd..f1219ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "complexipy" -version = "0.5.0" +version = "1.1.0" edition = "2021" authors = ["Robin Quintero "] license = "MIT" @@ -22,5 +22,6 @@ ignore = "0.4.22" indicatif = "0.17.8" pyo3 = "0.19.0" rayon = "1.8.1" +regex = "1.11.1" rustpython-parser = { git = "https://github.com/RustPython/Parser.git", rev = "9ce55aefdeb35e2f706ce0b02d5a2dfe6295fc57" } tempfile = "3.10.0" diff --git a/README.md b/README.md index 739c38d..0f83063 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,6 @@ complexipy path/to/file.py -c 20 # Use the -c option to set the maximum c complexipy path/to/directory -c 0 # Set the maximum cognitive complexity to 0 to disable the exit with error complexipy path/to/directory -o # Use the -o option to output the results to a CSV file, default is False complexipy path/to/directory -d low # Use the -d option to set detail level, default is "normal". If set to "low" will show only files with complexity greater than the maximum complexity -complexipy path/to/directory -l file # Use the -l option to set the level of measurement, default is "function". If set to "file" will measure the complexity of the file and will validate the maximum complexity according to the file complexity. complexipy path/to/directory -q # Use the -q option to disable the output to the console, default is False. complexipy path/to/directory -s desc # Use the -s option to set the sort order, default is "asc". If set to "desc" will sort the results in descending order. If set to "asc" will sort the results in ascending order. If set to "name" will sort the results by name. ``` @@ -85,13 +84,6 @@ complexipy path/to/directory -s desc # Use the -s option to set the sort orde - `-d` or `--details`: Set the detail level, default is "normal". If set to "low" will show only files or functions with complexity greater than the maximum complexity. -- `-l` or `--level` Set the level of measurement, default is "function". If set - to "file" will measure the complexity of the file and will validate the maximum - complexity according to the file complexity. If set to "function" will measure - the complexity of the functions and will validate the maximum complexity - according to the function complexity. This option is useful if you want to set - a maximum complexity according for each file or for each function in the file - (or files). - `-q` or `--quiet`: Disable the output to the console, default is False. - `-s` or `--sort`: Set the sort order, default is "asc". If set to "desc" will sort the results in descending order. If set to "asc" will sort the results in @@ -135,7 +127,7 @@ The output of the command `complexipy path/to/file.py` will be: ```txt -───────────────────────────── 🐙 complexipy 0.4.0 ────────────────────────────── +───────────────────────────── 🐙 complexipy 1.1.0 ────────────────────────────── Summary ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ Path ┃ File ┃ Function ┃ Complexity ┃ @@ -144,8 +136,8 @@ The output of the command ├───────────────────┼───────────────────┼─────────────┼────────────┤ │ test_decorator.py │ test_decorator.py │ b_decorator │ 1 │ └───────────────────┴───────────────────┴─────────────┴────────────┘ -🧠 Total Cognitive Complexity in ./tests/src/test_decorator.py: 1 -1 file analyzed in 0.0032 seconds +🧠 Total Cognitive Complexity: 1 +1 file analyzed in 0.0092 seconds ────────────────────────── 🎉 Analysis completed! 🎉 ─────────────────────────── ``` diff --git a/complexipy/main.py b/complexipy/main.py index d04a6fe..760ae78 100644 --- a/complexipy/main.py +++ b/complexipy/main.py @@ -1,12 +1,10 @@ from .types import ( DetailTypes, - Level, Sort, ) from .utils import ( output_summary, - has_success_file_level, - has_success_function_level, + has_success_functions, ) from complexipy import ( rust, @@ -16,7 +14,6 @@ from pathlib import ( Path, ) -import re from rich.console import ( Console, ) @@ -26,13 +23,13 @@ root_dir = Path(__file__).resolve().parent.parent app = typer.Typer(name="complexipy") console = Console() -version = "0.5.0" +version = "1.1.0" @app.command() def main( - path: str = typer.Argument( - help="Path to the directory or file to analyze, it can be a local path or a git repository URL.", + paths: list[str] = typer.Argument( + help="Paths to the directories or files to analyze, it can be a local paths or a git repository URL.", ), max_complexity: int = typer.Option( 15, @@ -49,12 +46,6 @@ def main( "-d", help="Specify how detailed should be output, it can be 'low' or 'normal'. Default is 'normal'.", ), - level: Level = typer.Option( - Level.function.value, - "--level", - "-l", - help="Specify the level of measurement, it can be 'function' or 'file'. Default is 'function'.", - ), quiet: bool = typer.Option( False, "--quiet", "-q", help="Suppress the output to the console." ), @@ -65,39 +56,26 @@ def main( help="Sort the output by complexity, it can be 'asc', 'desc' or 'name'. Default is 'asc'.", ), ): - 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() - file_level = level == Level.file - console.rule(f":octopus: complexipy {version}") start_time = time.time() - files: list[FileComplexity] = rust.main(path, is_dir, is_url, file_level) + files_complexities: list[FileComplexity] = rust.main(paths) execution_time = time.time() - start_time output_csv_path = f"{invocation_path}/complexipy.csv" - if output and file_level: - rust.output_csv_file_level(output_csv_path, files, sort.value) - console.print(f"Results saved in {output_csv_path}") - if output and not file_level: - rust.output_csv_function_level(output_csv_path, files, sort.value) - console.print(f"Results saved in {output_csv_path}") + if output: + rust.output_csv(output_csv_path, files_complexities, sort.value) + console.print(f"Results saved at {output_csv_path}") - # Summary if not quiet: has_success = output_summary( - console, file_level, files, max_complexity, details, path, sort + console, max_complexity, details, paths, sort ) - if quiet and not file_level: - has_success = has_success_function_level(files, max_complexity) - if quiet and file_level: - has_success = has_success_file_level(files, max_complexity) + if quiet: + has_success = has_success_functions(files_complexities, max_complexity) console.print( - f"{len(files)} file{'s' if len(files)> 1 else ''} analyzed in {execution_time:.4f} seconds" + f"{len(files_complexities)} file{'s' if len(files_complexities)> 1 else ''} analyzed in {execution_time:.4f} seconds" ) console.rule(":tada: Analysis completed! :tada:") @@ -107,18 +85,16 @@ def main( def code_complexity( code: str, - file_level: bool = True, ) -> CodeComplexity: - return rust.code_complexity(code, file_level) + return rust.code_complexity(code) -def file_complexity(file_path: str, file_level: bool = True) -> FileComplexity: +def file_complexity(file_path: str) -> FileComplexity: path = Path(file_path) base_path = path.parent return rust.file_complexity( file_path=path.resolve().as_posix(), base_path=base_path.resolve().as_posix(), - _file_level=file_level, ) diff --git a/complexipy/types.py b/complexipy/types.py index 7bf114e..18d148b 100644 --- a/complexipy/types.py +++ b/complexipy/types.py @@ -6,11 +6,6 @@ class DetailTypes(Enum): normal = "normal" # Show all files with their complexity -class Level(Enum): - function = "function" - file = "file" - - class Sort(Enum): asc = "asc" desc = "desc" diff --git a/complexipy/utils.py b/complexipy/utils.py index 7c19af3..895a229 100644 --- a/complexipy/utils.py +++ b/complexipy/utils.py @@ -17,69 +17,27 @@ def output_summary( console: Console, - file_level: bool, files: list[FileComplexity], max_complexity: int, details: DetailTypes, - path: str, sort: Sort, ) -> bool: - if file_level: - table, has_success, total_complexity = create_table_file_level( - files, max_complexity, details, sort - ) - else: - table, has_success, total_complexity = create_table_function_level( - files, max_complexity, details, sort - ) + table, has_success, total_complexity = create_table( + files, max_complexity, details, sort + ) if details == DetailTypes.low and table.row_count < 1: - console.print(f"No {'file' if file_level else 'function'}{'s' if len(files) > 1 else ''} were found with complexity greater than {max_complexity}.") + console.print( + f"No function{'s' if len(files) > 1 else ''} were found with complexity greater than {max_complexity}." + ) else: console.print(Align.center(table)) - console.print(f":brain: Total Cognitive Complexity in {path}: {total_complexity}") + console.print(f":brain: Total Cognitive Complexity: {total_complexity}") return has_success -def create_table_file_level( - files: list[FileComplexity], max_complexity: int, details: DetailTypes, sort: Sort -) -> tuple[Table, bool, int]: - has_success = 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") - total_complexity = 0 - - if sort != Sort.name: - files.sort(key=lambda x: x.complexity) - - if sort == Sort.desc: - files.reverse() - - for file in files: - total_complexity += file.complexity - 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]", - ) - has_success = False - elif details != DetailTypes.low or max_complexity == 0: - table.add_row( - f"{file.path}", - f"[green]{file.file_name}[/green]", - f"[blue]{file.complexity}[/blue]", - ) - return table, has_success, total_complexity - - -def create_table_function_level( +def create_table( files: list[FileComplexity], complexity: int, details: DetailTypes, @@ -90,7 +48,10 @@ def create_table_function_level( total_complexity = 0 table = Table( - title="Summary", show_header=True, header_style="bold magenta", show_lines=True + title="Summary", + show_header=True, + header_style="bold magenta", + show_lines=True, ) table.add_column("Path") table.add_column("File") @@ -98,7 +59,6 @@ def create_table_function_level( table.add_column("Complexity") for file in files: - total_complexity += file.complexity for function in file.functions: total_complexity += function.complexity all_functions.append((file.path, file.file_name, function)) @@ -128,14 +88,7 @@ def create_table_function_level( return table, has_success, total_complexity -def has_success_file_level(files: list[FileComplexity], max_complexity: int) -> bool: - for file in files: - if file.complexity > max_complexity and max_complexity != 0: - return False - return True - - -def has_success_function_level(files: list[FileComplexity], complexity: int) -> bool: +def has_success_functions(files: list[FileComplexity], complexity: int) -> bool: for file in files: for function in file.functions: if function.complexity > complexity and complexity != 0: diff --git a/docs/index.md b/docs/index.md index 5feb442..5b6c7dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -69,7 +69,6 @@ complexipy path/to/file.py -c 20 # Use the -c option to set the maximum c complexipy path/to/directory -c 0 # Set the maximum cognitive complexity to 0 to disable the exit with error complexipy path/to/directory -o # Use the -o option to output the results to a CSV file, default is False complexipy path/to/directory -d low # Use the -d option to set detail level, default is "normal". If set to "low" will show only files with complexity greater than the maximum complexity -complexipy path/to/directory -l file # Use the -l option to set the level of measurement, default is "function". If set to "file" will measure the complexity of the file and will validate the maximum complexity according to the file complexity. complexipy path/to/directory -q # Use the -q option to disable the output to the console, default is False. complexipy path/to/directory -s desc # Use the -s option to set the sort order, default is "asc". If set to "desc" will sort the results in descending order. If set to "asc" will sort the results in ascending order. If set to "name" will sort the results by name. ``` @@ -85,13 +84,6 @@ complexipy path/to/directory -s desc # Use the -s option to set the sort orde - `-d` or `--details`: Set the detail level, default is "normal". If set to "low" will show only files or functions with complexity greater than the maximum complexity. -- `-l` or `--level` Set the level of measurement, default is "function". If set - to "file" will measure the complexity of the file and will validate the maximum - complexity according to the file complexity. If set to "function" will measure - the complexity of the functions and will validate the maximum complexity - according to the function complexity. This option is useful if you want to set - a maximum complexity according for each file or for each function in the file - (or files). - `-q` or `--quiet`: Disable the output to the console, default is False. - `-s` or `--sort`: Set the sort order, default is "asc". If set to "desc" will sort the results in descending order. If set to "asc" will sort the results in @@ -131,7 +123,7 @@ The cognitive complexity of the file is 1, and the output of the command `complexipy path/to/file.py` will be: ```txt -───────────────────────────── 🐙 complexipy 0.4.0 ────────────────────────────── +───────────────────────────── 🐙 complexipy 1.1.0 ────────────────────────────── Summary ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ Path ┃ File ┃ Function ┃ Complexity ┃ @@ -140,8 +132,8 @@ The cognitive complexity of the file is 1, and the output of the command ├───────────────────┼───────────────────┼─────────────┼────────────┤ │ test_decorator.py │ test_decorator.py │ b_decorator │ 1 │ └───────────────────┴───────────────────┴─────────────┴────────────┘ -🧠 Total Cognitive Complexity in ./tests/src/test_decorator.py: 1 -1 file analyzed in 0.0032 seconds +🧠 Total Cognitive Complexity: 1 +1 file analyzed in 0.0092 seconds ────────────────────────── 🎉 Analysis completed! 🎉 ─────────────────────────── ``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b46f25d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,38 @@ +babel==2.16.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +colorama==0.4.6 +ghp-import==2.1.0 +idna==3.10 +iniconfig==2.0.0 +Jinja2==3.1.4 +Markdown==3.7 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +mdurl==0.1.2 +mergedeep==1.3.4 +mkdocs==1.6.1 +mkdocs-get-deps==0.2.0 +mkdocs-material==9.5.42 +mkdocs-material-extensions==1.3.1 +packaging==24.1 +paginate==0.5.7 +pathspec==0.12.1 +platformdirs==4.3.6 +pluggy==1.5.0 +Pygments==2.18.0 +pymdown-extensions==10.11.2 +pytest==8.3.3 +python-dateutil==2.9.0.post0 +PyYAML==6.0.2 +pyyaml_env_tag==0.1 +regex==2024.9.11 +requests==2.32.3 +rich==13.9.2 +shellingham==1.5.4 +six==1.16.0 +typer==0.12.5 +typing_extensions==4.12.2 +urllib3==2.2.3 +watchdog==5.0.3 diff --git a/src/cognitive_complexity/mod.rs b/src/cognitive_complexity/mod.rs index e3fc587..b9e8762 100644 --- a/src/cognitive_complexity/mod.rs +++ b/src/cognitive_complexity/mod.rs @@ -1,17 +1,19 @@ pub mod utils; -use crate::classes::{FileComplexity, FunctionComplexity, CodeComplexity}; +use crate::classes::{CodeComplexity, FileComplexity, FunctionComplexity}; use ignore::Walk; use indicatif::ProgressBar; use indicatif::ProgressStyle; -use pyo3::prelude::*; use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; use rayon::prelude::*; +use regex::Regex; use rustpython_parser::{ ast::{self, Stmt}, Parse, }; use std::env; +use std::fs::metadata; use std::path; use std::process; use std::sync::{Arc, Mutex}; @@ -21,12 +23,43 @@ use utils::{count_bool_ops, get_repo_name, is_decorator}; // Main function #[pyfunction] -pub fn main( - path: &str, - is_dir: bool, - is_url: bool, - file_level: bool, -) -> PyResult> { +pub fn main(paths: Vec<&str>) -> PyResult> { + let re = Regex::new(r"^(https:\/\/|http:\/\/|www\.|git@)(github|gitlab)\.com(\/[\w.-]+){2,}$") + .unwrap(); + + let all_files_paths: Vec<(&str, bool, bool)> = paths + .par_iter() + .map(|&path| { + let is_url = re.is_match(path); + + if is_url { + return (path, false, true); + } else if metadata(path).unwrap().is_dir() { + return (path, true, false); + } else { + return (path, false, false); + } + }) + .collect(); + + let all_files_processed: Vec, PyErr>> = all_files_paths + .iter() + .map(|(path, is_dir, is_url)| process_path(path, *is_dir, *is_url)) + .collect(); + + if all_files_processed.iter().all(|x| !x.is_err()) { + let ans: Vec = all_files_processed + .iter() + .flat_map(|file_complexities| file_complexities.as_ref().unwrap().iter().cloned()) + .collect(); + return Ok(ans); + } else { + return Err(PyValueError::new_err("Failed to process the paths")); + } +} + +#[pyfunction] +pub fn process_path(path: &str, is_dir: bool, is_url: bool) -> PyResult> { let mut ans: Vec = Vec::new(); if is_url { @@ -62,21 +95,21 @@ pub fn main( let repo_path = dir.path().join(&repo_name).to_str().unwrap().to_string(); - match evaluate_dir(&repo_path, file_level) { + match evaluate_dir(&repo_path) { Ok(files_complexity) => ans = files_complexity, Err(e) => return Err(e), } dir.close()?; } else if is_dir { - match evaluate_dir(path, file_level) { + match evaluate_dir(path) { Ok(files_complexity) => ans = files_complexity, Err(e) => return Err(e), } } else { let parent_dir = path::Path::new(path).parent().unwrap().to_str().unwrap(); - match file_complexity(path, parent_dir, file_level) { + match file_complexity(path, parent_dir) { Ok(file_complexity) => ans.push(file_complexity), Err(e) => return Err(e), } @@ -89,10 +122,7 @@ pub fn main( Ok(ans) } -fn evaluate_dir( - path: &str, - file_level: bool, -) -> PyResult> { +fn evaluate_dir(path: &str) -> PyResult> { let mut files_paths: Vec = Vec::new(); let parent_dir = path::Path::new(path).parent().unwrap().to_str().unwrap(); @@ -121,7 +151,7 @@ fn evaluate_dir( .par_iter() .map(|file_path| { pb.inc(1); - match file_complexity(file_path, parent_dir, file_level) { + match file_complexity(file_path, parent_dir) { Ok(file_complexity) => Ok(file_complexity), Err(e) => Err(e), } @@ -138,65 +168,52 @@ fn evaluate_dir( /// Calculate the cognitive complexity of a python file. #[pyfunction] -pub fn file_complexity( - file_path: &str, - base_path: &str, - _file_level: bool, -) -> PyResult { +pub fn file_complexity(file_path: &str, base_path: &str) -> PyResult { 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(); let code = std::fs::read_to_string(file_path)?; - let code_complexity = match code_complexity(&code, _file_level) { + let code_complexity = match code_complexity(&code) { Ok(v) => v, - Err(e) => return Err( - PyValueError:: - new_err(format!("Failed to compute code_complexity; error: {}", e))), + Err(e) => { + return Err(PyValueError::new_err(format!( + "Failed to compute code_complexity; error: {}", + e + ))) + } }; Ok(FileComplexity { path: relative_path.to_string(), file_name: file_name.to_string(), complexity: code_complexity.complexity, - functions: code_complexity.functions, + functions: code_complexity.functions, }) } /// Calculate the cognitive complexity of a string of python code. #[pyfunction] -pub fn code_complexity( - code: &str, - _file_level: bool, -) -> PyResult { +pub fn code_complexity(code: &str) -> PyResult { let ast = match ast::Suite::parse(&code, "") { Ok(v) => v, - Err(e) => return Err( - PyValueError:: - new_err(format!("Failed to parse this code; error: {}", e))), + Err(e) => { + return Err(PyValueError::new_err(format!( + "Failed to parse this code; error: {}", + e + ))) + } }; - let mut complexity: u64 = 0; - let mut functions: Vec = Vec::new(); - - if _file_level { - for node in ast.iter() { - complexity += statement_cognitive_complexity(node.clone(), 0)?; - } - } else { - let (f, c) = function_level_cognitive_complexity(&ast)?; - functions = f; - complexity = c; - } + let (functions, complexity) = function_level_cognitive_complexity(&ast)?; Ok(CodeComplexity { functions, - complexity, + complexity, }) } - fn function_level_cognitive_complexity( ast: &Vec, ) -> PyResult<(Vec, u64)> { @@ -246,6 +263,10 @@ fn function_level_cognitive_complexity( } } + for function in functions.iter() { + complexity += function.complexity; + } + Ok((functions, complexity)) } diff --git a/src/cognitive_complexity/utils.rs b/src/cognitive_complexity/utils.rs index 75e26a8..50057bb 100644 --- a/src/cognitive_complexity/utils.rs +++ b/src/cognitive_complexity/utils.rs @@ -4,48 +4,7 @@ use pyo3::prelude::*; use rustpython_parser::ast::{self, Stmt}; #[pyfunction] -pub fn output_csv_file_level( - invocation_path: &str, - files_complexity: Vec, - sort: &str, -) { - let mut writer = Writer::from_path(invocation_path).unwrap(); - - writer - .write_record(&["Path", "File Name", "Cognitive Complexity"]) - .unwrap(); - - if sort != "name" { - let mut files_complexity = files_complexity; - - files_complexity.sort_by_key(|f| f.complexity); - - if sort == "desc" { - files_complexity.reverse(); - } - - for file in files_complexity.into_iter() { - writer - .write_record(&[&file.path, &file.file_name, &file.complexity.to_string()]) - .unwrap(); - } - } else { - for file in &files_complexity { - writer - .write_record(&[&file.path, &file.file_name, &file.complexity.to_string()]) - .unwrap(); - } - } - - writer.flush().unwrap(); -} - -#[pyfunction] -pub fn output_csv_function_level( - invocation_path: &str, - functions_complexity: Vec, - sort: &str, -) { +pub fn output_csv(invocation_path: &str, functions_complexity: Vec, sort: &str) { let mut writer = Writer::from_path(invocation_path).unwrap(); writer diff --git a/src/lib.rs b/src/lib.rs index cad406e..9b8429c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,9 @@ mod classes; mod cognitive_complexity; -use classes::{FileComplexity, FunctionComplexity, CodeComplexity}; +use classes::{CodeComplexity, FileComplexity, FunctionComplexity}; +use cognitive_complexity::utils::output_csv; use cognitive_complexity::{code_complexity, file_complexity, main}; -use cognitive_complexity::utils::{output_csv_file_level, output_csv_function_level}; use pyo3::prelude::*; /// A Python module implemented in Rust. @@ -12,8 +12,7 @@ fn rust(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(main, m)?)?; m.add_function(wrap_pyfunction!(file_complexity, m)?)?; m.add_function(wrap_pyfunction!(code_complexity, m)?)?; - m.add_function(wrap_pyfunction!(output_csv_file_level, m)?)?; - m.add_function(wrap_pyfunction!(output_csv_function_level, m)?)?; + m.add_function(wrap_pyfunction!(output_csv, m)?)?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/tests/main.py b/tests/main.py index c64061a..5c3e2e6 100644 --- a/tests/main.py +++ b/tests/main.py @@ -13,97 +13,97 @@ class TestFiles(unittest.TestCase): def test_path(self): path = self.local_path / "src" - files = rust.main(path.resolve().as_posix(), True, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(41, total_complexity) def test(self): path = self.local_path / "src/test.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(9, total_complexity) def test_break_continue(self): path = self.local_path / "src/test_break_continue.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(3, total_complexity) def test_class(self): path = self.local_path / "src/test_class.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(1, total_complexity) def test_decorator(self): path = self.local_path / "src/test_decorator.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(1, total_complexity) def test_for(self): path = self.local_path / "src/test_for.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(5, total_complexity) def test_for_assign(self): path = self.local_path / "src/test_for_assign.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(1, total_complexity) def test_if(self): path = self.local_path / "src/test_if.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(3, total_complexity) def test_main(self): path = self.local_path / "src/test_main.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(0, total_complexity) def test_match(self): path = self.local_path / "src/test_match.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(0, total_complexity) def test_multiple_func(self): path = self.local_path / "src/test_multiple_func.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(0, total_complexity) def test_nested_func(self): path = self.local_path / "src/test_nested_func.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(2, total_complexity) def test_recursive(self): path = self.local_path / "src/test_recursive.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(0, total_complexity) def test_ternary_op(self): path = self.local_path / "src/test_ternary_op.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(1, total_complexity) def test_try(self): path = self.local_path / "src/test_try.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(3, total_complexity) def test_try_nested(self): path = self.local_path / "src/test_try_nested.py" - files = rust.main(path.resolve().as_posix(), False, False, True) + files = rust.main([path.resolve().as_posix()]) total_complexity = sum([file.complexity for file in files]) self.assertEqual(12, total_complexity) diff --git a/tests/src/test_if.py b/tests/src/test_if.py index 49693c8..fd3eefa 100644 --- a/tests/src/test_if.py +++ b/tests/src/test_if.py @@ -1,4 +1,5 @@ -if "1" == "1": - print(1) -if "2" == "2" and "3" == "3": - print(1) +def if_function(): + if "1" == "1": + print(1) + if "2" == "2" and "3" == "3": + print(1) diff --git a/tests/src/test_ternary_op.py b/tests/src/test_ternary_op.py index 7f56cb2..bd86894 100644 --- a/tests/src/test_ternary_op.py +++ b/tests/src/test_ternary_op.py @@ -1 +1,2 @@ -a = 1 if 1 > 2 else 2 \ No newline at end of file +def test_ternary_op(): + _a = 1 if 1 > 2 else 2