UNPKG

tusktsk

Version:

TuskTsk - The Freedom Configuration Language. Query databases, use any syntax, never bow to any king!

371 lines (306 loc) 10.1 kB
/** * Utility Commands for TuskLang CLI * ================================== * Implements utility operations */ const fs = require('fs').promises; const path = require('path'); const TuskLang = require('../../index.js'); // Parse command async function parse(file) { try { console.log(`🔄 Parsing TSK file: ${file}`); // Check if file exists await fs.access(file); // Read file content const content = await fs.readFile(file, 'utf8'); // Parse with TuskLang const tusk = new TuskLang(); const parsed = tusk.parse(content); console.log('✅ TSK file parsed successfully'); console.log('📍 Parsed structure:'); // Display parsed structure displayParsedStructure(parsed); return { success: true, parsed }; } catch (error) { console.error('❌ Parsing failed:', error.message); return { success: false, error: error.message }; } } // Validate command async function validate(file) { try { console.log(`🔍 Validating TSK file: ${file}`); // Check if file exists await fs.access(file); // Read file content const content = await fs.readFile(file, 'utf8'); // Validate with TuskLang const tusk = new TuskLang(); const validation = validateTSKContent(content); console.log('✅ TSK file validation completed'); if (validation.valid) { console.log('✅ File is valid TuskLang syntax'); console.log(`📍 Total lines: ${validation.lines}`); console.log(`📍 Sections: ${validation.sections}`); console.log(`📍 Variables: ${validation.variables}`); console.log(`📍 Functions: ${validation.functions}`); } else { console.log('❌ File has syntax errors:'); validation.errors.forEach((error, index) => { console.log(` ${index + 1}. Line ${error.line}: ${error.message}`); }); } return { success: true, validation }; } catch (error) { console.error('❌ Validation failed:', error.message); return { success: false, error: error.message }; } } // Convert command async function convert(options) { try { console.log('🔄 Converting between formats...'); if (!options.input || !options.output) { throw new Error('Both --input and --output options are required'); } // Check if input file exists await fs.access(options.input); // Read input file const content = await fs.readFile(options.input, 'utf8'); const inputExt = path.extname(options.input).toLowerCase(); const outputExt = path.extname(options.output).toLowerCase(); console.log(`📍 Converting from ${inputExt} to ${outputExt}`); let converted; if (inputExt === '.tsk' && outputExt === '.json') { // TSK to JSON const tusk = new TuskLang(); const parsed = tusk.parse(content); converted = JSON.stringify(parsed, null, 2); } else if (inputExt === '.json' && outputExt === '.tsk') { // JSON to TSK const parsed = JSON.parse(content); converted = TuskLang.stringify(parsed); } else if (inputExt === '.tsk' && outputExt === '.yaml') { // TSK to YAML const tusk = new TuskLang(); const parsed = tusk.parse(content); converted = convertToYAML(parsed); } else if (inputExt === '.yaml' && outputExt === '.tsk') { // YAML to TSK const yaml = require('js-yaml'); const parsed = yaml.load(content); converted = TuskLang.stringify(parsed); } else { throw new Error(`Unsupported conversion: ${inputExt} to ${outputExt}`); } // Write output file await fs.writeFile(options.output, converted); console.log('✅ Conversion completed successfully'); console.log(`📍 Output file: ${options.output}`); return { success: true, input: options.input, output: options.output }; } catch (error) { console.error('❌ Conversion failed:', error.message); return { success: false, error: error.message }; } } // Get command async function get(file, keyPath) { try { console.log(`📍 Getting value from TSK file: ${file}`); console.log(`📍 Key path: ${keyPath}`); // Check if file exists await fs.access(file); // Read and parse file const content = await fs.readFile(file, 'utf8'); const tusk = new TuskLang(); const parsed = tusk.parse(content); // Get value by path const value = getValueByPath(parsed, keyPath); if (value !== undefined) { console.log(`✅ Value: ${JSON.stringify(value)}`); return { success: true, key: keyPath, value }; } else { console.log(`❌ Key not found: ${keyPath}`); return { success: false, key: keyPath, found: false }; } } catch (error) { console.error('❌ Get operation failed:', error.message); return { success: false, error: error.message }; } } // Set command async function set(file, keyPath, value) { try { console.log(`📍 Setting value in TSK file: ${file}`); console.log(`📍 Key path: ${keyPath}`); console.log(`📍 Value: ${value}`); // Check if file exists await fs.access(file); // Read and parse file const content = await fs.readFile(file, 'utf8'); const tusk = new TuskLang(); const parsed = tusk.parse(content); // Set value by path const updated = setValueByPath(parsed, keyPath, parseValue(value)); // Convert back to TSK format const updatedContent = TuskLang.stringify(updated); // Write back to file await fs.writeFile(file, updatedContent); console.log('✅ Value set successfully'); return { success: true, key: keyPath, value: parseValue(value) }; } catch (error) { console.error('❌ Set operation failed:', error.message); return { success: false, error: error.message }; } } // Helper functions function displayParsedStructure(obj, indent = 0) { const spaces = ' '.repeat(indent); for (const [key, value] of Object.entries(obj)) { if (typeof value === 'object' && value !== null && !Array.isArray(value)) { console.log(`${spaces}${key}: {`); displayParsedStructure(value, indent + 1); console.log(`${spaces}}`); } else { const displayValue = Array.isArray(value) ? `[${value.join(', ')}]` : JSON.stringify(value); console.log(`${spaces}${key}: ${displayValue}`); } } } function validateTSKContent(content) { const validation = { valid: true, lines: content.split('\n').length, sections: 0, variables: 0, functions: 0, errors: [] }; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNumber = i + 1; // Skip empty lines and comments if (!line || line.startsWith('#')) { continue; } // Check for section headers if (line.startsWith('[') && line.endsWith(']')) { validation.sections++; continue; } // Check for variable definitions if (line.startsWith('$')) { validation.variables++; continue; } // Check for function calls if (line.includes('@')) { validation.functions++; continue; } // Check for key-value pairs if (line.includes(':')) { const [key, ...valueParts] = line.split(':'); const value = valueParts.join(':').trim(); // Validate key if (!key.trim()) { validation.valid = false; validation.errors.push({ line: lineNumber, message: 'Empty key in key-value pair' }); } // Validate value if (!value && !line.endsWith(':')) { validation.valid = false; validation.errors.push({ line: lineNumber, message: 'Empty value in key-value pair' }); } } else if (line && !line.startsWith('[') && !line.startsWith('$') && !line.startsWith('#')) { // Line doesn't match any valid pattern validation.valid = false; validation.errors.push({ line: lineNumber, message: 'Invalid syntax' }); } } return validation; } function convertToYAML(obj, indent = 0) { const spaces = ' '.repeat(indent); let yaml = ''; for (const [key, value] of Object.entries(obj)) { if (typeof value === 'object' && value !== null && !Array.isArray(value)) { yaml += `${spaces}${key}:\n`; yaml += convertToYAML(value, indent + 1); } else if (Array.isArray(value)) { yaml += `${spaces}${key}:\n`; value.forEach(item => { yaml += `${spaces} - ${JSON.stringify(item)}\n`; }); } else { yaml += `${spaces}${key}: ${JSON.stringify(value)}\n`; } } return yaml; } function getValueByPath(obj, path) { const keys = path.split('.'); let current = obj; for (const key of keys) { if (current && typeof current === 'object' && key in current) { current = current[key]; } else { return undefined; } } return current; } function setValueByPath(obj, path, value) { const keys = path.split('.'); const result = { ...obj }; let current = result; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!(key in current) || typeof current[key] !== 'object') { current[key] = {}; } current = current[key]; } current[keys[keys.length - 1]] = value; return result; } function parseValue(value) { // Try to parse as different types if (value === 'true' || value === 'false') { return value === 'true'; } if (!isNaN(value) && value !== '') { return Number(value); } if (value.startsWith('"') && value.endsWith('"')) { return value.slice(1, -1); } if (value.startsWith("'") && value.endsWith("'")) { return value.slice(1, -1); } // Try to parse as JSON try { return JSON.parse(value); } catch { // Return as string return value; } } module.exports = { parse, validate, convert, get, set };