codeaudit 0.5.3__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.
codeaudit/__about__.py ADDED
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present Maikel Mardjan <mike@bm-support.org>
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+ __version__ = "0.5.3"
codeaudit/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present Maikel Mardjan <mike@bm-support.org>
2
+ #
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+ from . __about__ import __version__
@@ -0,0 +1,54 @@
1
+ """
2
+ License GPLv3 or higher.
3
+
4
+ (C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
5
+
6
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9
+
10
+ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+
12
+ Altair Plotting functions for codeaudit
13
+ """
14
+
15
+ import altair as alt
16
+ import pandas as pd
17
+
18
+
19
+ def make_chart(y_field, df):
20
+ """Function to create a single bar chart with red and grey bars."""
21
+
22
+ # Calculate the median (or use any other threshold if needed)
23
+ threshold = df[y_field].median()
24
+
25
+ # Add a column for color condition
26
+ df = df.copy()
27
+ df['color'] = df[y_field].apply(lambda val: 'red' if val > threshold else 'grey')
28
+
29
+ chart = alt.Chart(df).mark_bar().encode(
30
+ x=alt.X('FileName:N', sort=None, title='File Name'),
31
+ y=alt.Y(f'{y_field}:Q', title=y_field),
32
+ color=alt.Color('color:N', scale=alt.Scale(domain=['red', 'grey'], range=['#d62728', '#7f7f7f']), legend=None),
33
+ tooltip=['FileName', y_field]
34
+ ).properties(
35
+ width=400,
36
+ height=400,
37
+ title=y_field
38
+ )
39
+ return chart
40
+
41
+ def multi_bar_chart(df):
42
+ """Creates a multi bar chart for all relevant columns"""
43
+
44
+ # List of metrics to chart
45
+ metrics = [
46
+ "Number_Of_Lines", "AST_Nodes", "Modules",
47
+ "Functions", "Comment_Lines", "Complexity_Score"
48
+ ]
49
+ rows = [alt.hconcat(*[make_chart(metric,df) for metric in metrics[i:i+2]]) for i in range(0, len(metrics), 2)]
50
+
51
+ # Stack the rows vertically
52
+ multi_chart = alt.vconcat(*rows)
53
+
54
+ return multi_chart
@@ -0,0 +1,103 @@
1
+ """
2
+ License GPLv3 or higher.
3
+
4
+ (C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
5
+
6
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9
+
10
+ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+
12
+
13
+ Checking imported Python modules functions for codeaudit
14
+ """
15
+
16
+ import ast
17
+ import sys
18
+ import json
19
+ import urllib.request
20
+
21
+
22
+ def get_imported_modules(source_code):
23
+ tree = ast.parse(source_code)
24
+ imported_modules = []
25
+
26
+ for node in ast.walk(tree):
27
+ if isinstance(node, ast.Import):
28
+ for alias in node.names:
29
+ # e.g., import os -> os
30
+ imported_modules.append(alias.name)
31
+ elif isinstance(node, ast.ImportFrom):
32
+ # e.g., from os import path -> os
33
+ module_name = node.module
34
+ if module_name:
35
+ imported_modules.append(module_name)
36
+ imported_modules = list(set(imported_modules)) # to make the list with unique values only!
37
+ # distinguish imported modules vs Standard Library
38
+ standard_modules = get_standard_library_modules()
39
+ core_modules = []
40
+ external_modules = []
41
+ for module in imported_modules:
42
+ top_level_module_name = module.split('.')[0]
43
+ if top_level_module_name in standard_modules:
44
+ core_modules.append(module)
45
+ else:
46
+ external_modules.append(module)
47
+ result = {'core_modules' : sorted(core_modules) ,
48
+ 'imported_modules': sorted(external_modules)}
49
+ return result
50
+
51
+
52
+ def get_standard_library_modules():
53
+ """works only Python 3.10+ or higher!"""
54
+ names = []
55
+ if hasattr(sys, 'stdlib_module_names'):
56
+ core_modules = sorted(list(sys.stdlib_module_names))
57
+ for module_name in core_modules:
58
+ if not module_name.startswith('_'):
59
+ names.append(module_name)
60
+ return names
61
+
62
+
63
+ def query_osv(package_name, ecosystem="PyPI"):
64
+ """MVP version to check imported module on vulnerabilities on osv db """
65
+ url = "https://api.osv.dev/v1/query"
66
+ headers = {
67
+ "Content-Type": "application/json"
68
+ }
69
+ data = {
70
+ "version": '', #no version needed for this tool
71
+ "package": {
72
+ "name": package_name,
73
+ "ecosystem": ecosystem
74
+ }
75
+ }
76
+
77
+ request = urllib.request.Request(
78
+ url,
79
+ data=json.dumps(data).encode("utf-8"),
80
+ headers=headers,
81
+ method="POST"
82
+ )
83
+
84
+ with urllib.request.urlopen(request) as response:
85
+ return json.loads(response.read().decode("utf-8"))
86
+
87
+
88
+ def extract_vuln_info(data):
89
+ results = []
90
+ for vuln in data.get("vulns", []):
91
+ info = {
92
+ "id": vuln.get("id"),
93
+ "details": vuln.get("details"),
94
+ "aliases": vuln.get("aliases", [])
95
+ }
96
+ results.append(info)
97
+ return results
98
+
99
+ def check_module_on_vuln(module):
100
+ """Retrieves vuln info for external modules using osv-db"""
101
+ result = query_osv(module)
102
+ vulnerability_info = extract_vuln_info(result)
103
+ return vulnerability_info
codeaudit/codeaudit.py ADDED
@@ -0,0 +1,79 @@
1
+ """
2
+ License GPLv3 or higher.
3
+
4
+ (C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
5
+
6
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9
+
10
+ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+
12
+
13
+ CLI functions for codeaudit
14
+ """
15
+ import fire # for working CLI with this PoC-thing (The Google way)
16
+ import sys
17
+ from codeaudit import __version__
18
+ from codeaudit.reporting import overview_report ,report_module_information ,file_scan_report , directory_scan_report , report_implemented_tests
19
+
20
+ codeaudit_ascii_art=r"""
21
+ --------------------------------------------------
22
+ _____ _ _ _ _
23
+ / ____| | | | (_) |
24
+ | | ___ __| | ___ __ _ _ _ __| |_| |_
25
+ | | / _ \ / _` |/ _ \/ _` | | | |/ _` | | __|
26
+ | |___| (_) | (_| | __/ (_| | |_| | (_| | | |_
27
+ \_____\___/ \__,_|\___|\__,_|\__,_|\__,_|_|\__|
28
+ --------------------------------------------------
29
+ """
30
+
31
+
32
+ def display_version():
33
+ """Prints the module version. Use [-v] [--v] [-version] or [--version]."""
34
+ print(f"version: {__version__}")
35
+
36
+
37
+ def display_help():
38
+ """Shows detailed help for using codeaudit tool."""
39
+ print(codeaudit_ascii_art)
40
+ print("Codeaudit - Modern Python source code analyzer based on distrust.\n")
41
+ print("Commands to evaluate Python source code:")
42
+ print('Usage: codeaudit COMMAND [PATH or FILE] [OUTPUTFILE] \n')
43
+ print('Depending on the command, a directory or file name must be specified. The output is a static HTML file to be examined in a browser. Specifying a name for the output file is optional.\n')
44
+ print('Commands:')
45
+ commands = ["overview", "modulescan", "filescan", "directoryscan","checks","version"] # commands on CLI
46
+ functions = [overview_report, report_module_information, file_scan_report, directory_scan_report, report_implemented_tests,display_version] # Related functions relevant for help
47
+ for command, function in zip(commands, functions):
48
+ docstring = function.__doc__.strip().split('\n')[0] or ""
49
+ summary = docstring.split("\n", 1)[0]
50
+ print(f" {command:<20} {summary}")
51
+ print("\nUse the Codeaudit documentation to check the security of Python programs and make your Python programs more secure!\nCheck https://simplifysecurity.nocomplexity.com/ \n")
52
+
53
+
54
+ def main():
55
+ if len(sys.argv) > 1 and sys.argv[1] in ("-v", "--v", "--version", "-version"):
56
+ display_version()
57
+ elif len(sys.argv) > 1 and sys.argv[1] in ("-help", "-?", "--help", "-h"):
58
+ display_help()
59
+ elif len(sys.argv) == 1:
60
+ display_help()
61
+ else:
62
+ fire.Fire(
63
+ {
64
+ "overview": overview_report,
65
+ "modulescan": report_module_information,
66
+ "filescan" : file_scan_report,
67
+ "directoryscan" : directory_scan_report,
68
+ "checks" : report_implemented_tests,
69
+ "-help": display_help,
70
+ }
71
+ )
72
+
73
+
74
+ if __name__ == "__main__":
75
+ main()
76
+
77
+
78
+
79
+
@@ -0,0 +1,107 @@
1
+ """
2
+ License GPLv3 or higher.
3
+
4
+ (C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
5
+
6
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9
+
10
+ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+
12
+ Simple Cyclomatic Complexity check for Python source files
13
+ See the docs for in-depth and why this is a simple way , but good enough!
14
+ """
15
+
16
+ import ast
17
+ import warnings
18
+
19
+ class ComplexityVisitor(ast.NodeVisitor):
20
+ def __init__(self):
21
+ self.complexity = 1 # Start with 1 for the entry point
22
+
23
+ def visit_If(self, node):
24
+ self.complexity += 1
25
+ self.generic_visit(node)
26
+
27
+ def visit_For(self, node):
28
+ self.complexity += 1
29
+ self.generic_visit(node)
30
+
31
+ def visit_While(self, node):
32
+ self.complexity += 1
33
+ self.generic_visit(node)
34
+
35
+ def visit_Try(self, node):
36
+ self.complexity += 1 # For the try block
37
+ self.generic_visit(node)
38
+
39
+ def visit_ExceptHandler(self, node):
40
+ self.complexity += 1 # For each except clause
41
+ self.generic_visit(node)
42
+
43
+ def visit_With(self, node):
44
+ self.complexity += 1 # For 'with' statements
45
+ self.generic_visit(node)
46
+
47
+ def visit_BoolOp(self, node):
48
+ # Count 'and' and 'or' operators
49
+ # Each 'and' or 'or' introduces a new predicate
50
+ self.complexity += (len(node.values) - 1)
51
+ self.generic_visit(node)
52
+
53
+ def visit_Match(self, node):
54
+ # A 'match' statement itself adds to complexity
55
+ self.complexity += 1
56
+ self.generic_visit(node)
57
+
58
+ def visit_MatchCase(self, node):
59
+ # Each 'case' in a match statement is a distinct path
60
+ # Note: The 'visit_Match' already adds 1. Each subsequent 'MatchCase'
61
+ # For simplicity and aligning with common CC interpretations where each distinct
62
+ # path adds 1, we'll increment for each MatchCase.
63
+ # So count each case as a separate branch point from the 'match' entry.
64
+ self.complexity += 1
65
+ self.generic_visit(node)
66
+
67
+ def visit_Assert(self, node):
68
+ # Assert statements introduce a potential exit point (branch)
69
+ self.complexity += 1
70
+ self.generic_visit(node)
71
+
72
+
73
+ def calculate_complexity(code):
74
+ with warnings.catch_warnings():
75
+ warnings.simplefilter("ignore", category=SyntaxWarning)
76
+ tree = ast.parse(code)
77
+
78
+ visitor = ComplexityVisitor()
79
+ visitor.visit(tree)
80
+ return visitor.complexity
81
+
82
+
83
+ def count_static_warnings_in_file(file_path):
84
+ """
85
+ Parses a Python source file using AST and counts the number of warnings raised (e.g., SyntaxWarning).
86
+
87
+ Args:
88
+ file_path (str): Path to the Python source file.
89
+
90
+ Returns:
91
+ int: Number of static warnings detected during parsing.
92
+ Returns -1 if the file cannot be read or parsed.
93
+ """
94
+ try:
95
+ with open(file_path, "r", encoding="utf-8") as f:
96
+ source = f.read()
97
+
98
+ with warnings.catch_warnings(record=True) as caught_warnings:
99
+ warnings.simplefilter("always") # Capture all warnings
100
+ ast.parse(source, filename=file_path)
101
+
102
+ result = { 'warnings' : len(caught_warnings)}
103
+
104
+ return result
105
+
106
+ except (SyntaxError, UnicodeDecodeError, ValueError):
107
+ return -1
@@ -0,0 +1,59 @@
1
+ name,construct,severity,info
2
+ Check on assert,assert,Low,Assertions are for debugging and development. Misuse can lead to security vulnerabilities.
3
+ Binding All Interfaces,s.bind,Medium,Network sockets require additional measurements.
4
+ Check for chmod,os.chmod,High,Operating System calls can have a security impact and should be inspected in detail.
5
+ Directory Creation,os.makedirs,Low,Operating System calls can have a security impact and should be inspected in detail.
6
+ Directory Creation,os.mkdir,Low,Operating System calls can have a security impact and should be inspected in detail.
7
+ Directory Creation,os.mkfifo,Low,Operating System calls can have a security impact and should be inspected in detail.
8
+ Directory Creation,os.mknod,Low,Operating System calls can have a security impact and should be inspected in detail.
9
+ Directory Creation,os.makedev,Low,Operating System calls can have a security impact and should be inspected in detail.
10
+ OS System call - Fork a child process,os.fork,Low,"On macOS use of this function is unsafe when mixed with using higher-level system APIs, and that includes using urllib.request."
11
+ Check on eval usuage,eval,High,This function can executes arbitrary code.
12
+ Check on input statement,input,Low,Use of input requires strict sanitizing and validation.
13
+ Exception Handling,pass,Low,Too broad exception handling risk when not used correctly.
14
+ Exception Handling- Continue statement,continue,Low,Too broad exception handling risk when not used correctly.
15
+ Built-in Functions: Check for exec usuage.,exec,High,This built-in function can execute code you do not want and/or aware of. So check and validate if it is used correct.
16
+ Built-in Functions: Check on compile usuage.,compile,High,It is possible to crash the Python interpreter when using this function.
17
+ Hash Check - md5,hashlib.md5,High,Use of insecure hashing algorithms detected.
18
+ Hash Check -sha1,hashlib.sha1,High,Use of insecure hashing algorithms detected.
19
+ Logging - configuration ,logging.config,Low,Potential security issues can arise with parsing objects and incorrect sanitizing.
20
+ Pickle use,pickle.loads,High,unpickling will import any class or function that it finds in the pickle data
21
+ Pickle use,pickle.load,High,unpickling will import any class or function that it finds in the pickle data
22
+ OS - direct calls,os.system,High,Operating System calls can have a security impact and should be inspected in detail.
23
+ OS - execl,os.execl,High,Operating System calls can have a security impact and should be inspected in detail.
24
+ OS - execle,os.execle,High,Operating System calls can have a security impact and should be inspected in detail.
25
+ OS - execlp,os.execlp,High,Operating System calls can have a security impact and should be inspected in detail.
26
+ OS - execlpe,os.execlpe,High,Operating System calls can have a security impact and should be inspected in detail.
27
+ OS - execv,os.execv,High,Operating System calls can have a security impact and should be inspected in detail.
28
+ OS - execve,os.execve,High,Operating System calls can have a security impact and should be inspected in detail.
29
+ OS - execvp,os.execvp,High,Operating System calls can have a security impact and should be inspected in detail.
30
+ OS - execvpe,os.execvpe,High,Operating System calls can have a security impact and should be inspected in detail.
31
+ OS - popen,os.popen,High,Operating System calls can have a security impact and should be inspected in detail.
32
+ OS Access,os.access,High,Operating System calls can have a security impact and should be inspected in detail.
33
+ OS Interfaces,os.write,Low,os.write can cause availability issues if not done correct.
34
+ OS Interfaces,os.writev,Low,os.writev can cause availability issues if not done correct.
35
+ OS Interfaces,os.forkpty,Low,Use of forkpty can be unsafe when used on MacOS.
36
+ OS Interface,os.read,Low,"When files can be read from , they can be transfered to some place you do not want."
37
+ Marshal,marshal.loads,High,The marshal module is not intended to be secure against erroneous or maliciously constructed data.
38
+ Marshal,marshal.load,High,The marshal module is not intended to be secure against erroneous or maliciously constructed data.
39
+ Subprocesses - call,subprocess.call,High,Use of the subprocess module calls should be analyzed in-depth.
40
+ Subprocesses - check_call,subprocess.check_call,High,Use of the subprocess module calls should be analyzed in-depth.
41
+ Subprocesses - Popen,subprocess.Popen,Medium,Use of the subprocess module calls should be analyzed in-depth.
42
+ Subprocesses - run,subprocess.run,Medium,Use of the subprocess module calls should be analyzed in-depth.
43
+ Tarfile,tarfile.TarFile,High,Extracting files within a program should never be trusted by default. This issue is detected when the zipfile and/or tarfile module with an extraction method is used.
44
+ Encodings,base64,Low,"Base encoding visually hides otherwise easily recognized information such as passwords, but does not provide any computational confidentiality."
45
+ XML - client,xmlrpc.client,High,xmlrpc is vulnerable to the “decompression bomb” attack.
46
+ XML - server,xmlrpc.server.SimpleXMLRPCServer,High,xmlrpc.server is vulnerable to the “decompression bomb” attack.
47
+ Random numbers generation module,random.random,Low,The pseudo-random generators of this module should not be used for security purposes.
48
+ Random numbers generation module,random.seed,Low,The pseudo-random generators of this module should not be used for security purposes.
49
+ Shelve module,shelve.open,High,Only loading a shelve from a trusted source is secure. So check if this is the case.
50
+ Multiprocessing ,connection.recv,High,Connection.recv() uses pickle
51
+ Multiprocessing ,multiprocessing.connection.Connection,High,Connection.recv() uses pickle
52
+ Zipfile,zipfile.ZipFile,High,Extracting files within a program should never be trusted by default. This issue is detected when the zipfile and/or tarfile module with an extraction method is used.
53
+ shutil,shutil.unpack_archive,Medium,Extracting files within a program should not be trusted by default.
54
+ shutil,shutil.copy,Medium,Information can be transfered without permission.
55
+ shutil,shutil.copy2,Medium,Information can be transfered without permission.
56
+ shutil,shutil.copytree,Medium,Information can be transfered without permission.
57
+ shutil,shutil.chown,Medium,Programs should not change access rights on files they do not own.
58
+ HTTP servers: Check on usuage.,http.server.BaseHTTPRequestHandler,High,Insecure for production use.
59
+ HTTP servers: Check on usuage.,http.server.HTTPServer,High,Insecure for production use.
@@ -0,0 +1,114 @@
1
+ """
2
+ License GPLv3 or higher.
3
+
4
+ (C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
5
+
6
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9
+
10
+ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+
12
+ Helper functions for files
13
+ """
14
+
15
+ import os
16
+ import sys
17
+ from pathlib import Path
18
+ import ast
19
+ import warnings
20
+
21
+ def read_in_source_file(file_path):
22
+ # Ensure file_path is a Path object
23
+ file_path = Path(file_path)
24
+
25
+ if file_path.is_dir():
26
+ print(
27
+ "Error: The given path is a directory.\nUse the function: codeaudit directoryscan\nUse codeaudit -h for help"
28
+ )
29
+ sys.exit(1)
30
+
31
+ if file_path.suffix != ".py":
32
+ print("Error: The given file is not a Python (.py) file.")
33
+ sys.exit(1)
34
+
35
+ try:
36
+ with file_path.open("r", encoding="utf-8") as f:
37
+ return f.read()
38
+ except Exception as e:
39
+ print(f"Failed to read file: {e}")
40
+ sys.exit(1)
41
+
42
+
43
+ def collect_python_source_files(directory):
44
+ """
45
+ Collects all Python source files (.py) from a directory, including subdirectories,
46
+ while skipping directories that are hidden (start with '.') or underscore-prefixed,
47
+ or are in a predefined exclusion list.
48
+
49
+ Args:
50
+ directory (str): The path to the directory to search.
51
+
52
+ Returns:
53
+ list: A list of absolute paths to valid Python source files.
54
+ """
55
+ EXCLUDE_DIRS = {"docs", "docker", "dist", "tests"}
56
+ python_files = []
57
+
58
+ for root, dirs, files in os.walk(directory):
59
+ # Filter out unwanted directories
60
+ dirs[:] = [
61
+ d for d in dirs
62
+ if not (
63
+ d.startswith(".") or d.startswith("_") or d in EXCLUDE_DIRS
64
+ )
65
+ ]
66
+
67
+ for file in files:
68
+ if file.endswith(".py") and not file.startswith("."):
69
+ full_path = os.path.join(root, file)
70
+ if os.path.isfile(full_path):
71
+ python_files.append(os.path.abspath(full_path))
72
+ #check if the file can be parsed using the AST
73
+ final_file_list = []
74
+ for python_file in python_files:
75
+ if is_ast_parseable(python_file):
76
+ final_file_list.append(python_file)
77
+ else:
78
+ print(f'Error: {python_file} will be skipped due to syntax error while parsing into AST.')
79
+ return final_file_list
80
+
81
+
82
+ def get_filename_from_path(file_path):
83
+ """
84
+ Extracts the file name (including extension) from a full path string.
85
+
86
+ Args:
87
+ file_path (str): The full path to the file.
88
+
89
+ Returns:
90
+ str: The file name.
91
+ """
92
+ return os.path.basename(file_path)
93
+
94
+
95
+ def is_ast_parseable(file_path):
96
+ """
97
+ Checks whether a Python file can be parsed using the AST module.
98
+
99
+ Args:
100
+ file_path (str): Path to the Python source file.
101
+
102
+ Returns:
103
+ bool: True if the file can be parsed without syntax errors, False otherwise.
104
+ """
105
+ try:
106
+ with open(file_path, "r", encoding="utf-8") as f:
107
+ source = f.read()
108
+
109
+ with warnings.catch_warnings():
110
+ warnings.simplefilter("ignore", category=SyntaxWarning)
111
+ ast.parse(source, filename=file_path)
112
+ return True
113
+ except (SyntaxError, UnicodeDecodeError, ValueError) as e:
114
+ return False
@@ -0,0 +1,90 @@
1
+ """
2
+ License GPLv3 or higher.
3
+
4
+ (C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
5
+
6
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
9
+
10
+ You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
11
+
12
+ HTML helper functions for codeaudit
13
+ """
14
+ import json
15
+ from html import escape
16
+
17
+
18
+ def dict_to_html(data):
19
+ """Creates simple HTML from a dict with values that are list:
20
+ Example {'core_modules': ['os', 'hashlib', 'socket', 'logging.config', 'tarfile'],
21
+ 'imported_modules': ['linkaudit', 'pandas']}
22
+ """
23
+ html_output = ""
24
+
25
+ if not isinstance(data, dict):
26
+ html_output += "<p>None</p>\n"
27
+ return
28
+
29
+ for key, items in data.items():
30
+ html_output += f"<h3>{key.capitalize()}</h3>\n<ul>\n"
31
+ try:
32
+ for item in items:
33
+ html_output += f" <li>{item}</li>\n"
34
+ except Exception:
35
+ html_output += " <li>None</li>\n"
36
+ html_output += "</ul>\n"
37
+ return html_output
38
+
39
+
40
+ def json_to_html(json_input):
41
+ """
42
+ Takes a Python dictionary or JSON string and returns an HTML page
43
+ that displays the JSON in a formatted and readable way.
44
+ """
45
+ # Parse JSON string if needed
46
+ # Parse if input is a JSON string
47
+ if isinstance(json_input, str):
48
+ json_data = json.loads(json_input)
49
+ else:
50
+ json_data = json_input
51
+
52
+ # Pretty-printed JSON string
53
+ pretty_json = json.dumps(json_data, indent=2)
54
+
55
+ # Escape HTML characters for safe embedding inside <code>
56
+ escaped_json = escape(pretty_json)
57
+
58
+ html_output = f'<div class="json-display">{escaped_json}</div>'
59
+ return html_output
60
+
61
+
62
+ def dict_list_to_html_table(data):
63
+ """
64
+ Converts a list of dictionaries to an HTML table string.
65
+ :param data: List[Dict] - List of dicts with the same keys.
66
+ :return: str - HTML table as string.
67
+ """
68
+ if not data:
69
+ return "<p><em>No data available</em></p>"
70
+
71
+ # Get column headers from the first dictionary
72
+ headers = data[0].keys()
73
+
74
+ # Start the HTML table
75
+ html = '<table border="1" cellpadding="5" cellspacing="0">\n'
76
+ html += ' <thead>\n <tr>\n'
77
+ for header in headers:
78
+ html += f' <th>{header}</th>\n'
79
+ html += ' </tr>\n </thead>\n <tbody>\n'
80
+
81
+ # Add rows
82
+ for row in data:
83
+ html += ' <tr>\n'
84
+ for header in headers:
85
+ html += f' <td>{row.get(header, "")}</td>\n'
86
+ html += ' </tr>\n'
87
+
88
+ html += ' </tbody>\n</table>'
89
+
90
+ return html