metripy 0.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of metripy might be problematic. Click here for more details.

Files changed (66) hide show
  1. metripy-0.2.5.dist-info/METADATA +112 -0
  2. metripy-0.2.5.dist-info/RECORD +66 -0
  3. metripy-0.2.5.dist-info/WHEEL +5 -0
  4. metripy-0.2.5.dist-info/entry_points.txt +2 -0
  5. metripy-0.2.5.dist-info/licenses/LICENSE +21 -0
  6. metripy-0.2.5.dist-info/top_level.txt +1 -0
  7. src/Application/Analyzer.py +105 -0
  8. src/Application/Application.py +54 -0
  9. src/Application/Config/Config.py +13 -0
  10. src/Application/Config/File/ConfigFileReaderFactory.py +22 -0
  11. src/Application/Config/File/ConfigFileReaderInterface.py +14 -0
  12. src/Application/Config/File/JsonConfigFileReader.py +81 -0
  13. src/Application/Config/GitConfig.py +10 -0
  14. src/Application/Config/Parser.py +30 -0
  15. src/Application/Config/ProjectConfig.py +27 -0
  16. src/Application/Config/ReportConfig.py +10 -0
  17. src/Application/__init__.py +0 -0
  18. src/Component/Debug/Debugger.py +20 -0
  19. src/Component/File/Finder.py +37 -0
  20. src/Component/Output/CliOutput.py +49 -0
  21. src/Component/Output/ProgressBar.py +27 -0
  22. src/Dependency/Composer/Composer.py +30 -0
  23. src/Dependency/Composer/Packegist.py +55 -0
  24. src/Dependency/Dependency.py +30 -0
  25. src/Dependency/Npm/Npm.py +30 -0
  26. src/Dependency/Npm/NpmOrg.py +47 -0
  27. src/Dependency/Pip/Pip.py +69 -0
  28. src/Dependency/Pip/PyPi.py +49 -0
  29. src/Git/GitAnalyzer.py +86 -0
  30. src/LangAnalyzer/AbstractLangAnalyzer.py +65 -0
  31. src/LangAnalyzer/Generic/HalSteadAnalyzer.py +58 -0
  32. src/LangAnalyzer/Generic/__init__.py +0 -0
  33. src/LangAnalyzer/Php/PhpAnalyzer.py +193 -0
  34. src/LangAnalyzer/Php/PhpBasicAstParser.py +56 -0
  35. src/LangAnalyzer/Php/PhpBasicLocAnalyzer.py +174 -0
  36. src/LangAnalyzer/Php/PhpHalSteadAnalyzer.py +44 -0
  37. src/LangAnalyzer/Python/PythonAnalyzer.py +129 -0
  38. src/LangAnalyzer/Typescript/TypescriptAnalyzer.py +210 -0
  39. src/LangAnalyzer/Typescript/TypescriptAstParser.py +68 -0
  40. src/LangAnalyzer/Typescript/TypescriptBasicComplexityAnalyzer.py +114 -0
  41. src/LangAnalyzer/Typescript/TypescriptBasicLocAnalyzer.py +69 -0
  42. src/LangAnalyzer/Typescript/TypescriptHalSteadAnalyzer.py +55 -0
  43. src/LangAnalyzer/__init__.py +0 -0
  44. src/Metric/Code/AggregatedMetrics.py +42 -0
  45. src/Metric/Code/FileMetrics.py +33 -0
  46. src/Metric/Code/ModuleMetrics.py +32 -0
  47. src/Metric/Code/SegmentedMetrics.py +65 -0
  48. src/Metric/FileTree/FileTree.py +15 -0
  49. src/Metric/FileTree/FileTreeParser.py +42 -0
  50. src/Metric/Git/GitCodeHotspot.py +37 -0
  51. src/Metric/Git/GitContributor.py +37 -0
  52. src/Metric/Git/GitKnowledgeSilo.py +27 -0
  53. src/Metric/Git/GitMetrics.py +148 -0
  54. src/Metric/ProjectMetrics.py +55 -0
  55. src/Report/Csv/Reporter.py +12 -0
  56. src/Report/Html/Reporter.py +210 -0
  57. src/Report/Json/AbstractJsonReporter.py +10 -0
  58. src/Report/Json/GitJsonReporter.py +21 -0
  59. src/Report/Json/JsonReporter.py +12 -0
  60. src/Report/ReporterFactory.py +22 -0
  61. src/Report/ReporterInterface.py +17 -0
  62. src/Tree/ClassNode.py +32 -0
  63. src/Tree/FunctionNode.py +49 -0
  64. src/Tree/ModuleNode.py +42 -0
  65. src/__init__.py +0 -0
  66. src/codemetrics.py +15 -0
@@ -0,0 +1,49 @@
1
+ import re
2
+ import sys
3
+ from typing import Self
4
+
5
+
6
+ class CliOutput:
7
+ def __init__(self):
8
+ self.quiet_mode = False
9
+
10
+ def set_quiet_mode(self, quiet_mode: bool):
11
+ self.quiet_mode = quiet_mode
12
+
13
+ def writeln(self, message: str) -> Self:
14
+ self.write(str(message), end="\n")
15
+
16
+ return self
17
+
18
+ def write(self, message: str, end="") -> Self:
19
+ if matches := re.search(r"<([a-z]+)>(.*?)</([a-z]+)>", message):
20
+ type = matches.group(1)
21
+ message = matches.group(2)
22
+ color = {
23
+ "error": "\033[31m",
24
+ "warning": "\033[33m",
25
+ "success": "\033[32m",
26
+ "info": "\033[34m",
27
+ "debug": "\033[95m",
28
+ }
29
+ message = color[type] + message + "\033[0m"
30
+
31
+ if not self.quiet_mode:
32
+ print(message, end=end)
33
+
34
+ return self
35
+
36
+ def err(self, message: str) -> Self:
37
+ sys.stderr.write(message)
38
+
39
+ return self
40
+
41
+ def clearln(self) -> Self:
42
+ if self.has_ansi():
43
+ self.write("\x0d")
44
+ self.write("\x1b[2K")
45
+
46
+ return self
47
+
48
+ def has_ansi(self) -> bool:
49
+ return sys.stdout.isatty()
@@ -0,0 +1,27 @@
1
+ from Component.Output.CliOutput import CliOutput
2
+
3
+
4
+ class ProgressBar:
5
+ def __init__(self, output: CliOutput, total: int):
6
+ self.output = output
7
+ self.total = total
8
+ self.current = 0
9
+
10
+ def start(self):
11
+ self.current = 0
12
+
13
+ def advance(self):
14
+ self.current += 1
15
+
16
+ if self.output.has_ansi():
17
+ percent = round(self.current / self.total * 100)
18
+ self.output.write("\x0d")
19
+ self.output.write("\x1b[2K")
20
+ self.output.write(f"... {percent}% ...")
21
+ else:
22
+ self.output.write(".")
23
+
24
+ def clear(self):
25
+ if self.output.has_ansi():
26
+ self.output.write("\x0d")
27
+ self.output.write("\x1b[2K")
@@ -0,0 +1,30 @@
1
+ import json
2
+ import os
3
+
4
+ from Dependency.Composer.Packegist import Packegist
5
+ from Dependency.Dependency import Dependency
6
+
7
+
8
+ class Composer:
9
+ def get_composer_dependencies(self, composer_json_path: str):
10
+ requirements = self.get_composer_json_requirements(composer_json_path)
11
+
12
+ packegist = Packegist()
13
+ packages = []
14
+ for dependency in requirements:
15
+ package = packegist.get_info(dependency)
16
+ packages.append(package)
17
+
18
+ return [item for item in packages if item is not None]
19
+
20
+ def get_composer_json_requirements(self, composer_json_path) -> list:
21
+ requirements = []
22
+ with open(os.path.join(composer_json_path, "composer.json"), "r") as file:
23
+ composer_json = json.load(file)
24
+ require = composer_json.get("require", None)
25
+ if require is None:
26
+ return []
27
+ for name, version in require.items():
28
+ requirements.append(Dependency(name, version))
29
+
30
+ return requirements
@@ -0,0 +1,55 @@
1
+ import re
2
+
3
+ import requests
4
+ from packaging import version
5
+
6
+ from Dependency.Dependency import Dependency
7
+
8
+
9
+ class Packegist:
10
+ def get_info(self, dependency: Dependency) -> Dependency | None:
11
+ if "/" not in dependency.name:
12
+ return None
13
+ [user, name] = dependency.name.split("/", 2)
14
+ uri = f"https://packagist.org/packages/{user}/{name}.json"
15
+
16
+ x = requests.get(uri)
17
+ d = x.json()
18
+
19
+ package_info = d.get("package", None)
20
+ if package_info is None:
21
+ print(f"package of {dependency.name} has no package info")
22
+ return dependency
23
+
24
+ dependency.type = package_info["type"]
25
+ dependency.description = package_info["description"]
26
+ dependency.repository = package_info["repository"]
27
+ dependency.github_stars = package_info["github_stars"]
28
+ dependency.downloads_total = package_info["downloads"]["total"]
29
+ dependency.downloads_monthly = package_info["downloads"]["monthly"]
30
+ dependency.downloads_daily = package_info["downloads"]["daily"]
31
+
32
+ latest = version.parse("0.0.0")
33
+ versions = package_info["versions"]
34
+ for ver_str, datas in versions.items():
35
+ # Strip leading 'v' if present
36
+ if ver_str.startswith("v"):
37
+ ver_str = ver_str[1:]
38
+
39
+ # Skip non-semver strings
40
+ if not re.match(r"^[\d.]+$", ver_str):
41
+ continue
42
+
43
+ current_version = version.parse(ver_str)
44
+ if current_version > latest:
45
+ latest = current_version
46
+ dependency.latest = ver_str
47
+ dependency.license = datas.get("license", [])
48
+ dependency.homepage = datas.get("homepage")
49
+ dependency.zip = datas.get("dist", {}).get("url")
50
+
51
+ if dependency.version == dependency.latest:
52
+ dependency.status = "latest"
53
+ else:
54
+ dependency.status = "outdated"
55
+ return dependency
@@ -0,0 +1,30 @@
1
+ class Dependency:
2
+ def __init__(self, name: str, version: str):
3
+ self.name = name
4
+ self.version = version
5
+ self.latest: str = ""
6
+ self.status: str = "unknown"
7
+ self.type: str = ""
8
+ self.description: str = ""
9
+ self.repository: str = ""
10
+ self.github_stars: int = 0
11
+ self.downloads_total: int = 0
12
+ self.downloads_monthly: int = 0
13
+ self.downloads_daily: int = 0
14
+ self.license: list[str] = []
15
+ self.homepage: str = ""
16
+ self.zip: str = ""
17
+
18
+ def to_dict(self) -> dict:
19
+ return {
20
+ "name": self.name,
21
+ "version": self.version,
22
+ "latest": self.latest,
23
+ "status": self.status,
24
+ "type": self.type,
25
+ "description": self.description,
26
+ "repository": self.repository,
27
+ "github_stars": self.github_stars,
28
+ "downloads_monthly": self.downloads_monthly,
29
+ "licenses": ",".join(self.license),
30
+ }
@@ -0,0 +1,30 @@
1
+ import json
2
+ import os
3
+
4
+ from Dependency.Dependency import Dependency
5
+ from Dependency.Npm.NpmOrg import NpmOrg
6
+
7
+
8
+ class Npm:
9
+ def get_dependencies(self, path: str) -> list[Dependency]:
10
+ requirements = self._get_requirements(path)
11
+
12
+ npm_org = NpmOrg()
13
+ packages = []
14
+ for dependency in requirements:
15
+ package = npm_org.get_info(dependency)
16
+ packages.append(package)
17
+
18
+ return [item for item in packages if item is not None]
19
+
20
+ def _get_requirements(self, path: str) -> list[Dependency]:
21
+ requirements = []
22
+ with open(os.path.join(path, "package.json"), "r") as file:
23
+ package_json = json.load(file)
24
+ dependencies = package_json.get("dependencies", None)
25
+ if dependencies is None:
26
+ return None
27
+ for name, version in dependencies.items():
28
+ requirements.append(Dependency(name, version))
29
+
30
+ return requirements
@@ -0,0 +1,47 @@
1
+ import requests
2
+
3
+ from Dependency.Dependency import Dependency
4
+
5
+
6
+ class NpmOrg:
7
+ def get_info(self, dependency: Dependency) -> Dependency | None:
8
+ if not dependency.name:
9
+ return None
10
+
11
+ uri = f"https://registry.npmjs.org/{dependency.name}"
12
+ response = requests.get(uri)
13
+
14
+ if response.status_code != 200:
15
+ print(f"Package {dependency.name} not found on npm.org")
16
+ return None
17
+
18
+ data = response.json()
19
+
20
+ # Basic metadata
21
+ dependency.type = "npm"
22
+ dependency.description = data.get("description", "")
23
+ dependency.repository = data.get("repository", {}).get("url", "")
24
+ dependency.homepage = data.get("homepage", "")
25
+ dependency.license = [data.get("license")] if data.get("license") else []
26
+
27
+ # Version info
28
+ latest_version = data.get("dist-tags", {}).get("latest", "")
29
+ dependency.latest = latest_version
30
+
31
+ # npm doesn't provide download stats in the registry API
32
+ dependency.github_stars = "??"
33
+ dependency.downloads_total = "??"
34
+ dependency.downloads_monthly = "??"
35
+ dependency.downloads_daily = "??"
36
+
37
+ # Determine status
38
+ if dependency.version == dependency.latest:
39
+ dependency.status = "latest"
40
+ else:
41
+ dependency.status = "outdated"
42
+
43
+ # build zip url
44
+ if latest_version:
45
+ dependency.zip = f"https://registry.npmjs.org/{dependency.name}/-/{dependency.name}-{latest_version}.tgz"
46
+
47
+ return dependency
@@ -0,0 +1,69 @@
1
+ import os
2
+ import re
3
+
4
+ import toml
5
+
6
+ from Dependency.Dependency import Dependency
7
+ from Dependency.Pip.PyPi import PyPi
8
+
9
+
10
+ class Pip:
11
+ def get_dependencies(self, path: str) -> list[Dependency]:
12
+ try:
13
+ requirements = self.get_from_requirements_txt(path)
14
+ except FileNotFoundError:
15
+ requirements = self.get_from_pyproject_toml(path)
16
+
17
+ pypi = PyPi()
18
+ packages = []
19
+ for dependency in requirements:
20
+ package = pypi.get_info(dependency)
21
+ packages.append(package)
22
+
23
+ return [item for item in packages if item is not None]
24
+
25
+ def get_from_requirements_txt(self, path: str) -> list[Dependency]:
26
+ requirements = []
27
+
28
+ pattern = re.compile(r"([a-zA-Z0-9_\-]+)([<>=!~]+[^\s]+)?")
29
+ with open(os.path.join(path, "requirements.txt"), "r") as file:
30
+ lines = file.readlines()
31
+ for line in lines:
32
+
33
+ line = line.strip()
34
+ if line and not line.startswith("#"):
35
+ match = pattern.match(line)
36
+ if match:
37
+ name = match.group(1)
38
+ version = match.group(2) if match.group(2) else None
39
+ requirements.append(Dependency(name, version))
40
+ return requirements
41
+
42
+ def get_from_pyproject_toml(self, path: str) -> list[Dependency]:
43
+ dependencies = []
44
+
45
+ with open(os.path.join(path, "pyproject.toml"), "r") as f:
46
+ data = toml.load(f)
47
+
48
+ # For PEP 621 / setuptools projects
49
+ if "project" in data:
50
+ deps = data["project"].get("dependencies", [])
51
+ for dep in deps:
52
+ # dep is a string like "requests>=2.32.5"
53
+ # You can split it if needed
54
+ if "==" in dep:
55
+ name, version = dep.split("==")
56
+ elif ">=" in dep:
57
+ name, version = dep.split(">=")
58
+ else:
59
+ name, version = dep, None
60
+ dependencies.append(
61
+ Dependency(name.strip(), version.strip() if version else None)
62
+ )
63
+
64
+ return dependencies
65
+
66
+
67
+ if __name__ == "__main__":
68
+ pip = Pip()
69
+ pip.get_dependencies("./")
@@ -0,0 +1,49 @@
1
+ import requests
2
+
3
+ from Dependency.Dependency import Dependency
4
+
5
+
6
+ class PyPi:
7
+ def get_info(self, dependency: Dependency) -> Dependency | None:
8
+ uri = f"https://pypi.org/pypi/{dependency.name}/json"
9
+ x = requests.get(uri)
10
+ data = x.json()
11
+
12
+ info = data.get("info", {})
13
+ releases = data.get("releases", {})
14
+
15
+ if not info:
16
+ print(f"Package '{dependency.name}' has no info section")
17
+ return dependency
18
+
19
+ dependency.description = info.get("summary")
20
+ dependency.repository = info.get("project_url") or info.get("home_page")
21
+ if info.get("license"):
22
+ dependency.license = [info.get("license")]
23
+ else:
24
+ dependency.license = []
25
+ dependency.homepage = info.get("home_page")
26
+
27
+ # PyPI doesn't provide GitHub stars or download counts directly
28
+ dependency.github_stars = "??"
29
+ dependency.downloads_total = "??"
30
+ dependency.downloads_monthly = "??"
31
+ dependency.downloads_daily = "??"
32
+
33
+ # Determine latest version
34
+ latest_version = info.get("version")
35
+ dependency.latest = latest_version
36
+
37
+ # Compare with current version
38
+ if dependency.version == latest_version:
39
+ dependency.status = "latest"
40
+ else:
41
+ dependency.status = "outdated"
42
+
43
+ # Get distribution URL (e.g., wheel or sdist)
44
+ if latest_version in releases:
45
+ release_files = releases[latest_version]
46
+ if release_files:
47
+ dependency.zip = release_files[0].get("url")
48
+
49
+ return dependency
src/Git/GitAnalyzer.py ADDED
@@ -0,0 +1,86 @@
1
+ from collections import defaultdict
2
+ from datetime import datetime
3
+
4
+ from git import Repo
5
+
6
+ from Application.Config.GitConfig import GitConfig
7
+ from Metric.Git.GitMetrics import GitMetrics
8
+
9
+
10
+ class GitAnalyzer:
11
+ def __init__(self, git_config: GitConfig):
12
+ print(git_config.repo)
13
+ print(git_config.branch)
14
+ self.repo = Repo(git_config.repo)
15
+ self.branch_name = git_config.branch
16
+
17
+ def analyze(self) -> GitMetrics:
18
+ """Main analysis method with comprehensive output"""
19
+
20
+ # Calculate first day of this month last year
21
+ now = datetime.now()
22
+ first_of_month_last_year = datetime(now.year - 1, now.month, 1)
23
+ # first_of_month_last_year = datetime(now.year, now.month, 1)
24
+ after_date = first_of_month_last_year.strftime("%Y-%m-%d")
25
+ print(f"analyzing from {after_date}")
26
+
27
+ return self.get_metrics(after_date)
28
+
29
+ def _is_source_file(self, file_path: str) -> bool:
30
+ """Check if file is a source code file we want to analyze"""
31
+ source_extensions = {
32
+ ".py",
33
+ ".js",
34
+ ".ts",
35
+ ".tsx",
36
+ ".php",
37
+ ".java",
38
+ ".cpp",
39
+ ".c",
40
+ ".h",
41
+ ".rb",
42
+ ".go",
43
+ ".rs",
44
+ }
45
+ return any(file_path.endswith(ext) for ext in source_extensions)
46
+
47
+ def get_metrics(self, after: str) -> GitMetrics:
48
+ commits_per_month = {}
49
+ chrun_per_month = defaultdict(lambda: {"added": 0, "removed": 0})
50
+ file_contributors = defaultdict(lambda: {"contributors": set(), "commits": 0})
51
+ contributor_stats = defaultdict(
52
+ lambda: {"commits": 0, "lines_added": 0, "lines_removed": 0}
53
+ )
54
+
55
+ for commit in self.repo.iter_commits(
56
+ self.branch_name, no_merges=True, after=after
57
+ ):
58
+ month = commit.committed_datetime.strftime("%Y-%m")
59
+ author = commit.author.name
60
+
61
+ if month not in commits_per_month.keys():
62
+ commits_per_month[month] = 0
63
+ commits_per_month[month] += 1
64
+
65
+ stats = commit.stats.total
66
+ insertions = stats.get("insertions", 0)
67
+ deletions = stats.get("deletions", 0)
68
+ chrun_per_month[month]["added"] += insertions
69
+ chrun_per_month[month]["removed"] += deletions
70
+
71
+ contributor_stats[author]["commits"] += 1
72
+ contributor_stats[author]["lines_added"] += insertions
73
+ contributor_stats[author]["lines_removed"] += deletions
74
+
75
+ for file_path in commit.stats.files:
76
+ if self._is_source_file(file_path):
77
+ file_contributors[file_path]["contributors"].add(author)
78
+ file_contributors[file_path]["commits"] += 1
79
+
80
+ return GitMetrics(
81
+ analysis_start_date=after,
82
+ commit_stats_per_month=commits_per_month,
83
+ churn_per_month=chrun_per_month,
84
+ contributor_stats=contributor_stats,
85
+ file_contributors=file_contributors,
86
+ )
@@ -0,0 +1,65 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from Metric.Code.FileMetrics import FileMetrics
4
+ from Tree.ModuleNode import ModuleNode
5
+
6
+
7
+ class AbstractLangAnalyzer(ABC):
8
+ def __init__(self):
9
+ self.files: list[str] = []
10
+ self.modules: dict[str, ModuleNode] = {}
11
+
12
+ @abstractmethod
13
+ def set_files(self, files: list[str]) -> None:
14
+ raise NotImplementedError
15
+
16
+ @abstractmethod
17
+ def is_needed(self) -> bool:
18
+ pass
19
+
20
+ def before_run(self) -> None:
21
+ # build cache
22
+ pass
23
+
24
+ def after_run(self) -> None:
25
+ # clear cache
26
+ pass
27
+
28
+ @abstractmethod
29
+ def get_lang_name(self) -> str:
30
+ raise NotImplementedError
31
+
32
+ @abstractmethod
33
+ def run(self) -> None:
34
+ raise NotImplementedError
35
+
36
+ @abstractmethod
37
+ def get_metrics(self) -> list[FileMetrics]:
38
+ metrics = []
39
+ for module in self.modules.values():
40
+ full_name = module.full_name
41
+
42
+ if len(module.functions) > 0:
43
+ avgCcPerFunction = sum(
44
+ function.complexity for function in module.functions
45
+ ) / len(module.functions)
46
+ avgLocPerFunction = (
47
+ module.lloc - module.comments - len(module.functions)
48
+ ) / len(module.functions)
49
+ else:
50
+ avgCcPerFunction = 0
51
+ avgLocPerFunction = 0
52
+ maintainabilityIndex = module.maintainability_index
53
+
54
+ file_metric = FileMetrics(
55
+ full_name=full_name,
56
+ loc=module.loc,
57
+ avgCcPerFunction=avgCcPerFunction,
58
+ maintainabilityIndex=maintainabilityIndex,
59
+ avgLocPerFunction=avgLocPerFunction,
60
+ class_nodes=module.classes,
61
+ function_nodes=module.functions,
62
+ )
63
+ metrics.append(file_metric)
64
+
65
+ return metrics
@@ -0,0 +1,58 @@
1
+ import math
2
+ import re
3
+ from collections import Counter
4
+
5
+
6
+ class HalSteadAnalyzer:
7
+ def __init__(self, operators: set):
8
+ self.operators: set = operators
9
+
10
+ def calculate_halstead_metrics(self, code: str):
11
+ # Tokenize the code
12
+ tokens = re.findall(r"\b\w+\b|[^\s\w]", code)
13
+
14
+ # Count operators and operands
15
+ operator_counts = Counter()
16
+ operand_counts = Counter()
17
+
18
+ for token in tokens:
19
+ if token in self.operators:
20
+ operator_counts[token] += 1
21
+ elif re.match(r"\b\w+\b", token):
22
+ operand_counts[token] += 1
23
+
24
+ # Halstead metrics
25
+ n1 = len(operator_counts) # distinct operators
26
+ n2 = len(operand_counts) # distinct operands
27
+ N1 = sum(operator_counts.values()) # total operators
28
+ N2 = sum(operand_counts.values()) # total operands
29
+
30
+ vocabulary = n1 + n2
31
+ length = N1 + N2
32
+ volume = length * math.log2(vocabulary) if vocabulary > 0 else 0
33
+ difficulty = (n1 / 2) * (N2 / n2) if n2 > 0 else 0
34
+ effort = difficulty * volume
35
+
36
+ calculated_length = 0
37
+ if n1 > 0:
38
+ calculated_length += n1 * math.log2(n1)
39
+ if n2 > 0:
40
+ calculated_length += n2 * math.log2(n2)
41
+
42
+ bugs = (effort ** (2 / 3)) / 3000 if effort > 0 else 0
43
+ time = effort / 18 if effort > 0 else 0
44
+
45
+ return {
46
+ "n1": n1, # distinct operators
47
+ "n2": n2, # distinct operands
48
+ "N1": N1, # total operators
49
+ "N2": N2, # total operands
50
+ "vocabulary": vocabulary,
51
+ "length": length,
52
+ "volume": volume,
53
+ "difficulty": difficulty,
54
+ "effort": effort,
55
+ "calculated_length": calculated_length,
56
+ "bugs": bugs,
57
+ "time": time,
58
+ }
File without changes