UNPKG

python2igcse

Version:

Convert Python code to IGCSE Pseudocode format

462 lines 19.1 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.CLI = void 0; // CLI for Python to IGCSE Pseudocode Converter const commander_1 = require("commander"); const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const converter_1 = require("./converter"); const types_1 = require("./types"); /** * CLI Application */ class CLI { constructor() { this.program = new commander_1.Command(); this.setupCommands(); } /** * Setup commands */ setupCommands() { this.program .name('python2igcse') .description('Convert Python code to IGCSE Pseudocode') .version(types_1.VERSION); // Main conversion command this.program .command('convert') .description('Convert Python file(s) to IGCSE Pseudocode') .argument('<input>', 'Input Python file or directory') .option('-o, --output <path>', 'Output file or directory') .option('-f, --format <format>', 'Output format (plain|markdown)', 'plain') .option('--indent-size <size>', 'Indentation size', '3') .option('--indent-type <type>', 'Indentation type (spaces|tabs)', 'spaces') .option('--line-ending <type>', 'Line ending type (lf|crlf)', 'lf') .option('--max-line-length <length>', 'Maximum line length', '80') .option('--no-beautify', 'Disable code beautification') .option('--strict', 'Enable strict mode') .option('--no-comments', 'Exclude comments from output') .option('--line-numbers', 'Include line numbers') .option('--no-preserve-whitespace', 'Do not preserve whitespace') .option('--lowercase-keywords', 'Use lowercase keywords') .option('--no-space-operators', 'No spaces around operators') .option('--no-space-commas', 'No spaces after commas') .option('--max-errors <count>', 'Maximum number of errors', '10') .option('--timeout <ms>', 'Conversion timeout in milliseconds', '30000') .option('--watch', 'Watch for file changes') .option('--verbose', 'Verbose output') .action(this.handleConvert.bind(this)); // Batch conversion command this.program .command('batch') .description('Convert multiple Python files') .argument('<pattern>', 'File pattern (glob)') .option('-o, --output-dir <dir>', 'Output directory', './output') .option('-f, --format <format>', 'Output format (plain|markdown)', 'plain') .option('--config <file>', 'Configuration file') .option('--parallel <count>', 'Number of parallel conversions', '4') .option('--verbose', 'Verbose output') .action(this.handleBatch.bind(this)); // Validation command this.program .command('validate') .description('Validate Python code for IGCSE conversion') .argument('<input>', 'Input Python file') .option('--strict', 'Enable strict validation') .option('--verbose', 'Verbose output') .action(this.handleValidate.bind(this)); // Statistics command this.program .command('stats') .description('Show conversion statistics') .argument('<input>', 'Input Python file') .option('--detailed', 'Show detailed statistics') .action(this.handleStats.bind(this)); // Configuration command this.program .command('config') .description('Manage configuration') .option('--init', 'Initialize configuration file') .option('--show', 'Show current configuration') .option('--set <key=value>', 'Set configuration value') .action(this.handleConfig.bind(this)); } /** * Handle convert command */ async handleConvert(input, options) { try { if (options.verbose) { console.log(`Converting: ${input}`); console.log(`Options:`, options); } const conversionOptions = this.buildConversionOptions(options); const inputStat = await fs.stat(input); if (inputStat.isFile()) { await this.convertSingleFile(input, options.output, conversionOptions, options.verbose); } else if (inputStat.isDirectory()) { await this.convertDirectory(input, options.output, conversionOptions, options.verbose); } else { throw new Error(`Invalid input: ${input}`); } if (options.watch) { await this.watchFiles(input, options.output, conversionOptions, options.verbose); } } catch (error) { console.error('Error:', error instanceof Error ? error.message : error); process.exit(1); } } /** * Handle batch conversion command */ async handleBatch(pattern, options) { try { const glob = await Promise.resolve().then(() => __importStar(require('glob'))); const files = await glob.glob(pattern); if (files.length === 0) { console.log('No files found matching pattern:', pattern); return; } console.log(`Found ${files.length} files to convert`); const conversionOptions = await this.loadConfig(options.config); const outputDir = options.outputDir; await fs.mkdir(outputDir, { recursive: true }); const parallelCount = parseInt(options.parallel); const chunks = this.chunkArray(files, parallelCount); for (const chunk of chunks) { await Promise.all(chunk.map((file) => this.convertSingleFile(file, path.join(outputDir, this.getOutputFileName(file, conversionOptions.outputFormat || 'plain')), conversionOptions, options.verbose))); } console.log(`Converted ${files.length} files to ${outputDir}`); } catch (error) { console.error('Error:', error instanceof Error ? error.message : error); process.exit(1); } } /** * Handle validation command */ async handleValidate(input, options) { try { const code = await fs.readFile(input, 'utf-8'); const conversionOptions = { ...this.buildConversionOptions(options), strictMode: options.strict, }; const result = await (0, converter_1.convertPythonToIGCSE)(code, conversionOptions); console.log(`Validation Results for: ${input}`); const hasErrors = result.parseResult.errors.length > 0; console.log(`Success: ${!hasErrors}`); if (result.parseResult.errors.length > 0) { console.log('\nErrors:'); result.parseResult.errors.forEach((error, index) => { console.log(` ${index + 1}. ${error.message}${error.line ? ` (Line ${error.line})` : ''}`); }); } if (result.parseResult.warnings.length > 0) { console.log('\nWarnings:'); result.parseResult.warnings.forEach((warning, index) => { console.log(` ${index + 1}. ${warning.message}${warning.line ? ` (Line ${warning.line})` : ''}`); }); } if (options.verbose && !hasErrors) { console.log('\nGenerated Code Preview:'); console.log(result.code.split('\n').slice(0, 10).join('\n')); if (result.code.split('\n').length > 10) { console.log('...'); } } } catch (error) { console.error('Error:', error instanceof Error ? error.message : error); process.exit(1); } } /** * Handle statistics command */ async handleStats(input, options) { try { const code = await fs.readFile(input, 'utf-8'); const result = await (0, converter_1.convertPythonToIGCSE)(code); console.log(`Statistics for: ${input}`); console.log(`Parse Time: ${result.stats.parseTime}ms`); console.log(`Emit Time: ${result.stats.emitTime}ms`); console.log(`Conversion Time: ${result.stats.conversionTime}ms`); console.log(`Input Lines: ${result.stats.inputLines}`); console.log(`Output Lines: ${result.stats.outputLines}`); console.log(`Error Count: ${result.stats.errorCount}`); console.log(`Warning Count: ${result.stats.warningCount}`); if (options.detailed) { console.log('\nDetailed Analysis:'); console.log(`Parse Errors: ${result.parseResult.errors.length}`); console.log(`Parse Warnings: ${result.parseResult.warnings.length}`); console.log(`Emit Errors: ${result.emitResult.errors?.length || 0}`); console.log(`Emit Warnings: ${result.emitResult.warnings?.length || 0}`); } } catch (error) { console.error('Error:', error instanceof Error ? error.message : error); process.exit(1); } } /** * Handle configuration command */ async handleConfig(options) { const configPath = path.join(process.cwd(), '.python2igcse.json'); try { if (options.init) { const defaultConfig = { outputFormat: 'plain', indentSize: 3, indentType: 'spaces', beautify: true, includeComments: true, uppercaseKeywords: true, }; await fs.writeFile(configPath, JSON.stringify(defaultConfig, null, 2)); console.log(`Configuration file created: ${configPath}`); return; } if (options.show) { try { const config = await fs.readFile(configPath, 'utf-8'); console.log('Current configuration:'); console.log(config); } catch { console.log('No configuration file found'); } return; } if (options.set) { const [key, value] = options.set.split('='); if (!key || value === undefined) { throw new Error('Invalid format. Use: --set key=value'); } let config = {}; try { const configContent = await fs.readFile(configPath, 'utf-8'); config = JSON.parse(configContent); } catch { // Create new file if it doesn't exist } // Type conversion for values let parsedValue = value; if (value === 'true') parsedValue = true; else if (value === 'false') parsedValue = false; else if (!isNaN(Number(value))) parsedValue = Number(value); config[key] = parsedValue; await fs.writeFile(configPath, JSON.stringify(config, null, 2)); console.log(`Configuration updated: ${key} = ${parsedValue}`); return; } // Display help by default console.log('Use --init to create a configuration file'); console.log('Use --show to display current configuration'); console.log('Use --set key=value to update configuration'); } catch (error) { console.error('Error:', error instanceof Error ? error.message : error); process.exit(1); } } /** * Convert single file */ async convertSingleFile(inputPath, outputPath, options, verbose) { const code = await fs.readFile(inputPath, 'utf-8'); const result = await (0, converter_1.convertPythonToIGCSE)(code, options); const hasErrors = result.parseResult.errors.length > 0; if (hasErrors) { console.error(`Failed to convert ${inputPath}:`); result.parseResult.errors.forEach((error) => { console.error(` ${error.message}${error.line ? ` (Line ${error.line})` : ''}`); }); return; } if (outputPath) { await fs.mkdir(path.dirname(outputPath), { recursive: true }); await fs.writeFile(outputPath, result.code); if (verbose) { console.log(`Converted: ${inputPath} -> ${outputPath}`); } } else { console.log(result.code); } } /** * Convert directory */ async convertDirectory(inputDir, outputDir, options, verbose) { const files = await this.findPythonFiles(inputDir); const output = outputDir || path.join(inputDir, 'igcse-output'); await fs.mkdir(output, { recursive: true }); for (const file of files) { const relativePath = path.relative(inputDir, file); const outputPath = path.join(output, this.getOutputFileName(relativePath, options.outputFormat || 'plain')); await this.convertSingleFile(file, outputPath, options, verbose); } console.log(`Converted ${files.length} files to ${output}`); } /** * Watch files */ async watchFiles(inputPath, outputPath, options, verbose) { const chokidar = await Promise.resolve().then(() => __importStar(require('chokidar'))); console.log(`Watching for changes in: ${inputPath}`); const watcher = chokidar.watch(inputPath, { ignored: /node_modules/, persistent: true, }); watcher.on('change', async (filePath) => { if (path.extname(filePath) === '.py') { console.log(`File changed: ${filePath}`); try { const output = outputPath || this.getOutputFileName(filePath, options.outputFormat || 'plain'); await this.convertSingleFile(filePath, output, options, verbose); } catch (error) { console.error('Conversion error:', error); } } }); // Exit with Ctrl+C process.on('SIGINT', () => { console.log('\nStopping file watcher...'); watcher.close(); process.exit(0); }); } /** * Find Python files */ async findPythonFiles(dir) { const files = []; const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory() && !entry.name.startsWith('.')) { files.push(...(await this.findPythonFiles(fullPath))); } else if (entry.isFile() && entry.name.endsWith('.py')) { files.push(fullPath); } } return files; } /** * Generate output file name */ getOutputFileName(inputPath, format) { const ext = format === 'markdown' ? '.md' : '.txt'; return inputPath.replace(/\.py$/, ext); } /** * Build conversion options */ buildConversionOptions(cliOptions) { return { outputFormat: cliOptions.format || 'plain', indentSize: parseInt(cliOptions.indentSize) || 3, indentType: cliOptions.indentType || 'spaces', lineEnding: cliOptions.lineEnding === 'crlf' ? '\r\n' : '\n', maxLineLength: parseInt(cliOptions.maxLineLength) || 80, beautify: !cliOptions.noBeautify, strictMode: cliOptions.strict || false, includeComments: !cliOptions.noComments, includeLineNumbers: cliOptions.lineNumbers || false, preserveWhitespace: !cliOptions.noPreserveWhitespace, uppercaseKeywords: !cliOptions.lowercaseKeywords, spaceAroundOperators: !cliOptions.noSpaceOperators, spaceAfterCommas: !cliOptions.noSpaceCommas, maxErrors: parseInt(cliOptions.maxErrors) || 10, timeout: parseInt(cliOptions.timeout) || 30000, }; } /** * Load configuration file */ async loadConfig(configPath) { const defaultOptions = this.buildConversionOptions({}); if (!configPath) { configPath = path.join(process.cwd(), '.python2igcse.json'); } try { const configContent = await fs.readFile(configPath, 'utf-8'); const config = JSON.parse(configContent); return { ...defaultOptions, ...config }; } catch { return defaultOptions; } } /** * Split array into chunks */ chunkArray(array, chunkSize) { const chunks = []; for (let i = 0; i < array.length; i += chunkSize) { chunks.push(array.slice(i, i + chunkSize)); } return chunks; } /** * Run CLI */ run() { this.program.parse(); } } exports.CLI = CLI; // Run CLI if (require.main === module) { const cli = new CLI(); cli.run(); } //# sourceMappingURL=cli.js.map