Coverage for src / codeaudit / filehelpfunctions.py: 49%
53 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-09 09:33 +0200
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-09 09:33 +0200
1"""
2License GPLv3 or higher.
4(C) 2025 Created by Maikel Mardjan - https://nocomplexity.com/
6This 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.
8This 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.
10You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
12Helper functions for files
13"""
15import ast
16import os
17import sys
18import warnings
19from pathlib import Path
22def read_in_source_file(file_path):
23 # Ensure file_path is a Path object
24 file_path = Path(file_path)
26 if file_path.is_dir():
27 print(
28 "Error: The given path is a directory.\nUse 'codeaudit filescan' to security audit Python files in a directory or PyPI package.\nThe 'codeaudit modulescan' command works per file only, not on a directory.\nUse codeaudit -h for help"
29 )
30 sys.exit(1)
32 if file_path.suffix.lower() != ".py":
33 print("Error: The given file is not a Python (.py) file.")
34 sys.exit(1)
36 try:
37 with file_path.open("r", encoding="utf-8") as f:
38 return f.read()
39 except Exception as e:
40 print(f"Failed to read file: {e}")
41 sys.exit(1)
44def collect_python_source_files(directory):
45 """
46 Collects all Python source files (.py) from a directory, including subdirectories,
47 while skipping directories that are hidden (start with '.') or underscore-prefixed,
48 or are in a predefined exclusion list.
50 Args:
51 directory (str): The path to the directory to search.
53 Returns:
54 list: A list of absolute paths to valid Python source files.
55 """
56 EXCLUDE_DIRS = {"docs", "docker", "dist", "tests"}
57 python_files = []
59 for root, dirs, files in os.walk(directory):
60 # Filter out unwanted directories
61 dirs[:] = [
62 d
63 for d in dirs
64 if not (d.startswith(".") or d.startswith("_") or d in EXCLUDE_DIRS)
65 ]
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_parsable(python_file):
76 final_file_list.append(python_file)
77 else:
78 print(
79 f"Error: {python_file} will be skipped due to syntax error while parsing into AST."
80 )
81 return final_file_list
84def get_filename_from_path(file_path):
85 """
86 Extracts the file name (including extension) from a full path string.
88 Args:
89 file_path (str): The full path to the file.
91 Returns:
92 str: The file name.
93 """
94 # return os.path.basename(file_path)
95 return Path(file_path).name
98def is_ast_parsable(file_path):
99 """
100 Checks whether a Python file can be parsed using the AST module.
102 Args:
103 file_path (str): Path to the Python source file.
105 Returns:
106 bool: True if the file can be parsed without syntax errors, False otherwise.
107 """
108 try:
109 with open(file_path, "r", encoding="utf-8") as f:
110 source = f.read()
112 with warnings.catch_warnings():
113 warnings.simplefilter("ignore", category=SyntaxWarning)
114 ast.parse(source, filename=file_path)
115 return True
116 except (SyntaxError, UnicodeDecodeError, ValueError) as e:
117 return False
120def has_python_files(input_path):
121 """
122 Check whether a directory contains at least one Python file.
124 Args:
125 input_path (str | Path): Path to a directory.
127 Returns:
128 bool: True if the directory contains at least one Python file, False otherwise.
129 """
130 file_path = Path(input_path)
132 if not file_path.is_dir():
133 return False
135 files_to_check = collect_python_source_files(file_path)
136 return len(files_to_check) > 0