-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
288 lines (222 loc) · 11.2 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
import argparse
import logging
import os
import re
from src.config_loader import load_config
from src.export.exporter import export_data, export_implementation_smells, export_design_smells, export_architecture_smells
from src.log_config import setup_logging
from src.smells import get_detector
from src.smells.smell_detector import ImplementationSmellDetector, DesignSmellDetector, ArchitectureSmellDetector
from src.sourcemodel.ast_parser import ASTParser
from src.sourcemodel.sm_project import SMProject
def get_root_path(input_path):
"""Get the root path of the input file or directory."""
return input_path if os.path.isdir(input_path) else os.path.dirname(input_path)
def get_project_name(input_path):
"""Extract the project name from the input path."""
return os.path.basename(input_path) if os.path.isdir(input_path) else \
os.path.splitext(os.path.basename(input_path))[0]
config = load_config()
skip_directories = set(config['skip_directories']['names'])
skip_patterns = [re.compile(pattern) for pattern in config['skip_directories']['patterns']]
def should_skip(path):
# Normalize and lower the path for comparison
path = path.replace('\\', '/').lower()
# Check if any part of the path is in the skip directories
for part in path.split('/'):
if part in skip_directories:
return True
# Check if the path matches any of the skip patterns
for pattern in skip_patterns:
if pattern.search(path):
return True
return False
def process_file(file_path, project, project_root, parser=None):
"""Process an individual Python file."""
if should_skip(file_path):
logging.info(f"Skipping file: {file_path}")
return None
parser = parser or ASTParser(project)
try:
return parser.parse(file_path, project_root)
except Exception as e:
logging.error(f"Error in processing file {file_path}: {e}", exc_info=True)
return None
def process_directory(directory_path, project, project_root):
modules = []
for root, dirs, files in os.walk(directory_path):
# Modify dirs in place to skip unwanted directories
dirs[:] = [d for d in dirs if not should_skip(os.path.join(root, d))]
for file in files:
file_path = os.path.join(root, file)
if file.endswith('.py') and not should_skip(file_path):
logging.info(f"Parsing file: {file_path}")
print(f"Parsing file: {file_path}")
module = process_file(file_path, project, project_root)
if module:
modules.append(module)
return modules
def analyze_modules(modules):
"""Analyze parsed modules to gather metrics and identify smells."""
aggregated_metrics = {'module': [], 'class': [], 'method': [], 'function': []}
for module in modules:
logging.info(f"Analyzing module: {module.name}")
print(f"Analyzing module: {module.name}")
file_metrics = module.analyze()
if file_metrics:
aggregated_metrics['module'].append(file_metrics.get('module_metrics', {}))
aggregated_metrics['class'].extend(file_metrics.get('class_metrics', []))
aggregated_metrics['method'].extend(file_metrics.get('method_metrics', []))
aggregated_metrics['function'].extend(file_metrics.get('function_metrics', []))
return aggregated_metrics
# def detect_smells(module, config):
# """Detect code smells within a module based on the provided configuration."""
# implementation_smells = []
# design_smells = []
# for smell_name, settings in config['Smells'].items():
# if not settings.get('enable', False):
# continue # Skip if the smell is not enabled
# detector = get_detector(smell_name)
# if detector is None:
# logging.warning(f"Detector not found for smell: {smell_name}")
# continue
# try:
# smells = detector.detect(module, settings)
# if not isinstance(smells, list):
# logging.error(f"Expected list from detector but got {type(smells)} for {smell_name}")
# continue
# if isinstance(detector, ImplementationSmellDetector):
# implementation_smells.extend(smells)
# elif isinstance(detector, DesignSmellDetector):
# design_smells.extend(smells)
# elif isinstance(detector, ArchitectureSmellDetector):###################### architecture
# architecture_smells.extend(smells)
# except Exception as e:
# logging.error(f"Exception during detection of {smell_name}: {e}", exc_info=True)
# # Combine the collected smells into a single dictionary before returning
# detected_smells = {
# 'implementation': implementation_smells,
# 'design': design_smells,
# 'architecture': architecture_smells
# }
# return detected_smells
def detect_smells(package_details, config):
"""Detect code smells within each package based on the provided configuration."""
implementation_smells = []
design_smells = []
architecture_smells = []
for smell_name, settings in config['Smells'].items():
if not settings.get('enable', False):
continue # Skip if the smell is not enabled
detector = get_detector(smell_name)
if detector is None:
logging.warning(f"Detector not found for smell: {smell_name}")
continue
try:
if isinstance(detector, ArchitectureSmellDetector):
arch_smells = detector.detect(package_details, settings)
architecture_smells.extend(arch_smells)
else:
for package_name, modules in package_details.items():
for module in modules:
smells = detector.detect(module, settings)
if not isinstance(smells, list):
logging.error(f"Expected list from detector but got {type(smells)} for {smell_name}")
continue
if isinstance(detector, ImplementationSmellDetector):
implementation_smells.extend(smells)
elif isinstance(detector, DesignSmellDetector):
design_smells.extend(smells)
except Exception as e:
logging.error(f"Exception during detection of {smell_name}: {e}", exc_info=True)
# Combine the collected smells into a single dictionary before returning
detected_smells = {
'implementation': implementation_smells,
'design': design_smells,
'architecture': architecture_smells
}
return detected_smells
def configure_environment(args):
"""Set up the environment before analysis starts, including logging and path checks."""
log_directory = args.log_dir if args.log_dir else args.output_dir
setup_logging(log_directory)
if not os.path.exists(args.input):
logging.error(f"Input path does not exist: {args.input}")
return False # Indicates that the environment setup was not successful
if not os.path.exists(args.output_dir):
os.makedirs(args.output_dir, exist_ok=True)
return True # Indicates successful environment setup
# def detect_arch_smells(package_details, config):
# architecture_smells = []
# for smell_name, settings in config['Smells'].items():
# if not settings.get('enable', False):
# continue
# detector = get_detector()
def perform_analysis(args):
"""Conduct the main analysis workflow."""
try:
project_root = get_root_path(args.input)
project_name = get_project_name(args.input)
project = SMProject(project_name)
config = load_config(user_path=args.config)
modules = process_directory(args.input, project, project_root) if os.path.isdir(args.input) else [
process_file(args.input, project, project_root)]
package_details = {}
for module in modules:
if module.package_name in package_details:
package_details[module.package_name].append(module)
else:
package_details[module.package_name] = [module]
all_metrics = analyze_modules(modules)
print("AST Parsing and Metrics calculation done!")
all_smells = {'implementation': [], 'design': [], 'architecture': []}
smells = detect_smells(package_details, config)
for smell_type in all_smells:
all_smells[smell_type].extend(smells[smell_type])
# for module in modules:
# smells = detect_smells(module, config)
# for smell_type in all_smells:
# all_smells[smell_type].extend(smells[smell_type])
print("Analysis Completed!!")
return all_metrics, all_smells, project_name
except Exception as e:
logging.error(f"An error occurred during analysis: {e}")
raise # Re-raising the exception after logging
def export_results(all_metrics, all_smells, project_name, args):
"""Export the analysis results based on user-specified formats."""
try:
if all_metrics:
export_data(all_metrics['module'], args.output_dir, f"{project_name}_module_metrics", args.format)
export_data(all_metrics['class'], args.output_dir, f"{project_name}_class_metrics", args.format)
export_data(all_metrics['method'], args.output_dir, f"{project_name}_method_metrics", args.format)
export_data(all_metrics['function'], args.output_dir, f"{project_name}_function_metrics", args.format)
if all_smells['implementation']:
export_implementation_smells(all_smells['implementation'], args.output_dir, project_name, args.format)
if all_smells['design']:
export_design_smells(all_smells['design'], args.output_dir, project_name, args.format)
if all_smells['architecture']: ########################## architecture conditional statement
export_architecture_smells(all_smells['architecture'], args.output_dir, project_name, args.format)
else:
logging.info("No data available for export.")
except Exception as e:
logging.error(f"Failed to export results: {e}")
raise # Re-raising the exception after logging
def main(args):
"""Entry point of the application."""
if configure_environment(args):
try:
all_metrics, all_smells, project_name = perform_analysis(args)
export_results(all_metrics, all_smells, project_name, args)
except Exception as e:
logging.error(f"Analysis failed: {e}")
print(f"Analysis failed. Check logs for details.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Your program description here.")
# Add command-line arguments here
parser.add_argument('-i', '--input', type=str, help='Input file or directory path')
parser.add_argument('-o', '--output-dir', type=str, help='Output directory path')
parser.add_argument('-f', '--format', type=str, help='Output format (e.g., csv)')
parser.add_argument('--config', type=str, help='Configuration file path')
parser.add_argument('--log-dir', type=str, help='Log directory path')
args = parser.parse_args()
main(args)