Skip to content

Commit 223924a

Browse files
authored
Merge pull request #6 from monch1962/logging-utils-refactoring
refactor: modularize logging-utils.js (790 lines) into 5 modules
2 parents 64f31cb + b6929ab commit 223924a

16 files changed

Lines changed: 3673 additions & 1340 deletions
Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Python Command Executor Module for PythonCommandRunner
4+
*
5+
* Command execution methods: runTests, runLinter, runFormatter, runTypeChecker, manageDependencies, runSetup
6+
*/
7+
8+
const path = require('path');
9+
const fs = require('fs');
10+
const { LoggingUtils } = require('../../lib');
11+
12+
class PythonCommandExecutor {
13+
constructor(projectPath, pythonConfig, initializer, projectAnalyzer, coreExecutor) {
14+
this.projectPath = projectPath;
15+
this.pythonConfig = pythonConfig;
16+
this.initializer = initializer;
17+
this.projectAnalyzer = projectAnalyzer;
18+
this.coreExecutor = coreExecutor;
19+
}
20+
21+
/**
22+
* Run tests with configured test runner
23+
*/
24+
async runTests(options = {}) {
25+
await this.initializer.initialize();
26+
27+
const testRunner = this.pythonConfig.testRunner || 'pytest';
28+
await this.initializer.checkTool(testRunner);
29+
30+
// Log test information
31+
const projectInfo = this.projectAnalyzer.getPythonProjectInfo();
32+
if (projectInfo) {
33+
LoggingUtils.debug(`Python files: ${projectInfo.pythonFiles}`);
34+
}
35+
36+
const args = [];
37+
38+
// Add coverage if requested
39+
if (options.coverage) {
40+
if (testRunner === 'pytest') {
41+
args.push('--cov=.', '--cov-report=term', '--cov-report=html');
42+
LoggingUtils.info('📊 Coverage reporting enabled');
43+
}
44+
}
45+
46+
// Add verbose flag
47+
if (options.verbose) {
48+
args.push('-v');
49+
LoggingUtils.debug('Verbose mode enabled');
50+
}
51+
52+
// Add specific test file
53+
if (options.file) {
54+
args.push(options.file);
55+
LoggingUtils.debug(`Testing specific file: ${options.file}`);
56+
}
57+
58+
// Add test name pattern
59+
if (options.test) {
60+
if (testRunner === 'pytest') {
61+
args.push('-k', options.test);
62+
LoggingUtils.debug(`Test pattern: ${options.test}`);
63+
} else if (testRunner === 'unittest') {
64+
args.push(options.test);
65+
LoggingUtils.debug(`Test pattern: ${options.test}`);
66+
}
67+
}
68+
69+
// Log test configuration
70+
LoggingUtils.info(`Running tests with ${testRunner}...`);
71+
72+
// Execute test runner
73+
if (testRunner === 'pytest') {
74+
return this.coreExecutor.executeCommand('pytest', args);
75+
} else if (testRunner === 'unittest') {
76+
return this.coreExecutor.executePythonModule('unittest', args);
77+
} else {
78+
throw new Error(`Unsupported test runner: ${testRunner}`);
79+
}
80+
}
81+
82+
/**
83+
* Run linter with configured tool
84+
*/
85+
async runLinter(options = {}) {
86+
await this.initializer.initialize();
87+
88+
const linter = this.pythonConfig.linter || 'ruff';
89+
await this.initializer.checkTool(linter);
90+
91+
// Log linter information
92+
LoggingUtils.info(`Running ${linter}...`);
93+
if (options.fix) {
94+
LoggingUtils.debug('Fix mode enabled');
95+
}
96+
97+
const args = [];
98+
99+
// Check or fix mode
100+
if (options.fix) {
101+
if (linter === 'ruff') {
102+
args.push('check', '--fix');
103+
} else if (linter === 'flake8') {
104+
// flake8 doesn't have fix mode
105+
args.push('.');
106+
LoggingUtils.warn("flake8 doesn't support auto-fix mode");
107+
} else if (linter === 'pylint') {
108+
args.push('.');
109+
LoggingUtils.warn("pylint doesn't support auto-fix mode");
110+
}
111+
} else {
112+
if (linter === 'ruff') {
113+
args.push('check');
114+
} else {
115+
args.push('.');
116+
}
117+
}
118+
119+
// Add specific file
120+
if (options.file) {
121+
args.push(options.file);
122+
LoggingUtils.debug(`Linting specific file: ${options.file}`);
123+
}
124+
125+
// Add exclude patterns
126+
if (options.exclude) {
127+
if (linter === 'ruff') {
128+
args.push('--exclude', options.exclude);
129+
} else if (linter === 'flake8') {
130+
args.push('--exclude', options.exclude);
131+
}
132+
}
133+
134+
// Execute linter
135+
return this.coreExecutor.executeCommand(linter, args);
136+
}
137+
138+
/**
139+
* Run formatter with configured tool
140+
*/
141+
async runFormatter(options = {}) {
142+
await this.initializer.initialize();
143+
144+
const formatter = this.pythonConfig.formatter || 'black';
145+
await this.initializer.checkTool(formatter);
146+
147+
// Log formatter information
148+
LoggingUtils.info(`Running ${formatter}...`);
149+
if (options.check) {
150+
LoggingUtils.debug('Check mode (no changes)');
151+
}
152+
153+
const args = [];
154+
155+
// Check or format mode
156+
if (options.check) {
157+
if (formatter === 'black') {
158+
args.push('--check');
159+
} else if (formatter === 'isort') {
160+
args.push('--check-only');
161+
} else if (formatter === 'yapf') {
162+
args.push('--diff');
163+
}
164+
}
165+
166+
// Add specific file or directory
167+
if (options.file) {
168+
args.push(options.file);
169+
LoggingUtils.debug(`Formatting specific file: ${options.file}`);
170+
} else {
171+
args.push('.');
172+
}
173+
174+
// Add line length if specified
175+
if (options.lineLength) {
176+
if (formatter === 'black') {
177+
args.push('--line-length', options.lineLength.toString());
178+
} else if (formatter === 'yapf') {
179+
args.push('--style', `{based_on_style: pep8, column_limit: ${options.lineLength}}`);
180+
}
181+
}
182+
183+
// Execute formatter
184+
return this.coreExecutor.executeCommand(formatter, args);
185+
}
186+
187+
/**
188+
* Run type checker with configured tool
189+
*/
190+
async runTypeChecker(options = {}) {
191+
await this.initializer.initialize();
192+
193+
const typeChecker = this.pythonConfig.typeChecker || 'mypy';
194+
await this.initializer.checkTool(typeChecker);
195+
196+
// Log type checker information
197+
LoggingUtils.info(`Running ${typeChecker}...`);
198+
199+
const args = [];
200+
201+
// Add specific file or directory
202+
if (options.file) {
203+
args.push(options.file);
204+
LoggingUtils.debug(`Type checking specific file: ${options.file}`);
205+
} else {
206+
args.push('.');
207+
}
208+
209+
// Add strict mode
210+
if (options.strict) {
211+
if (typeChecker === 'mypy') {
212+
args.push('--strict');
213+
} else if (typeChecker === 'pyright') {
214+
args.push('--lib');
215+
}
216+
}
217+
218+
// Add config file if specified
219+
if (options.config) {
220+
if (typeChecker === 'mypy') {
221+
args.push('--config-file', options.config);
222+
} else if (typeChecker === 'pyright') {
223+
args.push('--config', options.config);
224+
}
225+
}
226+
227+
// Execute type checker
228+
return this.coreExecutor.executeCommand(typeChecker, args);
229+
}
230+
231+
/**
232+
* Manage Python dependencies
233+
*/
234+
async manageDependencies(action, packages = [], options = {}) {
235+
await this.initializer.initialize();
236+
237+
const packageManager = this.pythonConfig.packageManager || 'pip';
238+
await this.initializer.checkTool(packageManager);
239+
240+
// Log dependency management information
241+
LoggingUtils.info(`Managing dependencies with ${packageManager}...`);
242+
243+
const args = [];
244+
245+
// Handle different actions
246+
switch (action) {
247+
case 'install':
248+
args.push('install');
249+
if (packages.length === 0) {
250+
// Install from requirements file
251+
if (this.projectAnalyzer.hasFile('requirements.txt')) {
252+
args.push('-r', 'requirements.txt');
253+
LoggingUtils.debug('Installing from requirements.txt');
254+
} else if (this.projectAnalyzer.hasFile('pyproject.toml')) {
255+
if (packageManager === 'poetry') {
256+
args.push('--no-root');
257+
} else if (packageManager === 'pip') {
258+
args.push('.');
259+
}
260+
}
261+
} else {
262+
// Install specific packages
263+
args.push(...packages);
264+
LoggingUtils.debug(`Installing packages: ${packages.join(', ')}`);
265+
}
266+
break;
267+
268+
case 'uninstall':
269+
args.push('uninstall', ...packages);
270+
LoggingUtils.debug(`Uninstalling packages: ${packages.join(', ')}`);
271+
break;
272+
273+
case 'update':
274+
args.push('update');
275+
if (packages.length > 0) {
276+
args.push(...packages);
277+
LoggingUtils.debug(`Updating packages: ${packages.join(', ')}`);
278+
} else {
279+
LoggingUtils.debug('Updating all packages');
280+
}
281+
break;
282+
283+
case 'list':
284+
args.push('list');
285+
break;
286+
287+
case 'show':
288+
if (packages.length > 0) {
289+
args.push('show', ...packages);
290+
LoggingUtils.debug(`Showing info for: ${packages.join(', ')}`);
291+
} else {
292+
throw new Error('Package name required for show action');
293+
}
294+
break;
295+
296+
default:
297+
throw new Error(`Unknown dependency action: ${action}`);
298+
}
299+
300+
// Add additional options
301+
if (options.dev && packageManager === 'poetry') {
302+
args.push('--dev');
303+
}
304+
305+
if (options.noCache && packageManager === 'pip') {
306+
args.push('--no-cache-dir');
307+
}
308+
309+
// Execute package manager
310+
if (packageManager === 'pip') {
311+
return this.coreExecutor.executePythonModule('pip', args);
312+
} else {
313+
return this.coreExecutor.executeCommand(packageManager, args);
314+
}
315+
}
316+
317+
/**
318+
* Run project setup
319+
*/
320+
async runSetup(options = {}) {
321+
await this.initializer.initialize();
322+
323+
LoggingUtils.info('Setting up Python project...');
324+
325+
const setupTasks = [];
326+
327+
// Check Python installation
328+
try {
329+
const python = this.projectAnalyzer.getPythonExecutable();
330+
setupTasks.push(`✓ Python executable: ${python}`);
331+
} catch (error) {
332+
setupTasks.push(`✗ Python not found: ${error.message}`);
333+
throw error;
334+
}
335+
336+
// Check virtual environment
337+
const venvPath = path.join(this.projectPath, 'venv');
338+
if (!fs.existsSync(venvPath) && options.createVenv) {
339+
setupTasks.push('Creating virtual environment...');
340+
await this.coreExecutor.executePythonModule('venv', ['venv']);
341+
setupTasks.push('✓ Virtual environment created');
342+
} else if (fs.existsSync(venvPath)) {
343+
setupTasks.push('✓ Virtual environment found');
344+
}
345+
346+
// Install dependencies
347+
if (options.installDeps) {
348+
setupTasks.push('Installing dependencies...');
349+
await this.manageDependencies('install', [], {});
350+
setupTasks.push('✓ Dependencies installed');
351+
}
352+
353+
// Run initial tests
354+
if (options.runTests) {
355+
setupTasks.push('Running initial tests...');
356+
try {
357+
await this.runTests({ verbose: false });
358+
setupTasks.push('✓ Tests passed');
359+
} catch (error) {
360+
setupTasks.push(`⚠ Tests failed: ${error.message}`);
361+
if (!options.force) {
362+
throw error;
363+
}
364+
}
365+
}
366+
367+
// Log setup summary
368+
LoggingUtils.info('Setup completed:');
369+
setupTasks.forEach((task) => {
370+
if (task.startsWith('✓')) {
371+
LoggingUtils.success(task);
372+
} else if (task.startsWith('✗')) {
373+
LoggingUtils.error(task);
374+
} else if (task.startsWith('⚠')) {
375+
LoggingUtils.warn(task);
376+
} else {
377+
LoggingUtils.info(task);
378+
}
379+
});
380+
381+
return { success: true, tasks: setupTasks };
382+
}
383+
}
384+
385+
module.exports = PythonCommandExecutor;

0 commit comments

Comments
 (0)