UNPKG

@syntropysoft/praetorian

Version:

Praetorian CLI – A universal multi-environment configuration validator for DevSecOps teams. Validate, compare, and secure YAML/ENV files with ease.

308 lines 8.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseIniValue = exports.parseIniContent = exports.IniFileAdapter = void 0; const AbstractFileAdapter_1 = require("../base/AbstractFileAdapter"); /** * INI File Adapter - Functional Programming * * Single Responsibility: Parse INI configuration files only * Pure functions, no state, no side effects */ class IniFileAdapter extends AbstractFileAdapter_1.AbstractFileAdapter { canHandle(filePath) { // Guard clause: no file path if (!filePath || typeof filePath !== 'string') { return false; } return filePath.endsWith('.ini') || filePath.endsWith('.cfg') || filePath.endsWith('.conf'); } async read(filePath) { // Guard clause: no file path if (!filePath || typeof filePath !== 'string') { throw new Error('File path is required'); } this.validateFileExists(filePath); try { const content = await this.readFileContent(filePath); return (0, exports.parseIniContent)(content); } catch (error) { throw new Error(`Failed to parse INI file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } getFormat() { return 'ini'; } getSupportedExtensions() { return ['.ini', '.cfg', '.conf']; } } exports.IniFileAdapter = IniFileAdapter; /** * Pure function to parse INI content */ const parseIniContent = (content) => { // Guard clause: no content if (!content || typeof content !== 'string') { return {}; } const lines = splitIntoLines(content); const processedLines = filterCommentsAndEmpty(lines); const sections = parseSections(processedLines); return sections.reduce((result, section) => { if (section.name) { result[section.name] = section.properties; } return result; }, {}); }; exports.parseIniContent = parseIniContent; /** * Pure function to split content into lines */ const splitIntoLines = (content) => { // Guard clause: empty content if (!content) { return []; } return content.split('\n'); }; /** * Pure function to filter comments and empty lines */ const filterCommentsAndEmpty = (lines) => { // Guard clause: no lines if (!lines || lines.length === 0) { return []; } return lines .map(line => line.trim()) .filter(line => line && !isComment(line)); }; /** * Pure function to check if line is a comment */ const isComment = (line) => { // Guard clause: no line if (!line) { return false; } return line.startsWith(';') || line.startsWith('#'); }; /** * Pure function to parse sections from lines */ const parseSections = (lines) => { // Guard clause: no lines if (!lines || lines.length === 0) { return []; } let currentSection = null; const result = {}; for (const line of lines) { // Guard clause: skip empty lines and comments if (isEmptyOrComment(line)) { continue; } // Guard clause: check if it's a valid section header if (isValidSectionHeader(line)) { currentSection = extractSectionName(line); if (!result[currentSection]) { result[currentSection] = {}; } continue; } // Guard clause: invalid section header - reset current section if (isInvalidSectionHeader(line)) { currentSection = null; continue; } // Guard clause: only process key-value if we have a valid current section if (!hasValidCurrentSection(currentSection)) { continue; } const keyValue = extractKeyValuePair(line); if (hasValidKeyValue(keyValue) && currentSection) { result[currentSection][keyValue.key] = (0, exports.parseIniValue)(keyValue.value); } } return Object.entries(result).map(([name, properties]) => ({ name, properties })); }; /** * Pure function to check if line is empty or comment */ const isEmptyOrComment = (line) => { if (!line || line.trim() === '') return true; if (line.trim().startsWith(';')) return true; if (line.trim().startsWith('#')) return true; return false; }; /** * Pure function to check if line is a valid section header */ const isValidSectionHeader = (line) => { if (!line) return false; if (!line.startsWith('[') || !line.endsWith(']')) return false; if (line.length < 3) return false; // [x] return true; }; /** * Pure function to check if line is an invalid section header */ const isInvalidSectionHeader = (line) => { if (!line) return false; if (line.startsWith('[') || line.endsWith(']')) return true; return false; }; /** * Pure function to check if we have a valid current section */ const hasValidCurrentSection = (currentSection) => { return currentSection !== null; }; /** * Pure function to check if key-value pair is valid */ const hasValidKeyValue = (keyValue) => { return keyValue !== null && keyValue.key.length > 0; }; /** * Pure function to check if line is a section header */ const isSectionHeader = (line) => { // Guard clause: no line if (!line) { return false; } return line.startsWith('[') && line.endsWith(']'); }; /** * Pure function to extract section name from header */ const extractSectionName = (line) => { // Guard clause: no line or invalid format if (!line || !isSectionHeader(line)) { return ''; } return line.slice(1, -1).trim(); }; /** * Pure function to extract key-value pair from line */ const extractKeyValuePair = (line) => { // Guard clause: no line if (!line) { return null; } const equalIndex = line.indexOf('='); // Guard clause: no equals sign or at start of line if (equalIndex <= 0) { return null; } const key = line.substring(0, equalIndex).trim(); const value = line.substring(equalIndex + 1).trim(); // Guard clause: no key if (!key) { return null; } return { key, value }; }; /** * Pure function to parse an INI value */ const parseIniValue = (value) => { // Guard clause: no value if (!value || value === null || value === undefined) { return ''; } // Check if value is quoted - if so, return as string without quotes const isQuoted = isQuotedValue(value); if (isQuoted) { return removeQuotes(value); } // Parse boolean values (only for unquoted values) const booleanValue = parseBoolean(value); if (booleanValue !== null) { return booleanValue; } // Parse numeric values (only for unquoted values) const numericValue = parseNumber(value); if (numericValue !== null) { return numericValue; } // Return as string return value; }; exports.parseIniValue = parseIniValue; /** * Pure function to check if value is quoted */ const isQuotedValue = (value) => { // Guard clause: no value if (!value || value.length < 2) { return false; } return (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")); }; /** * Pure function to remove quotes from value */ const removeQuotes = (value) => { // Guard clause: no value if (!value) { return value; } if (isQuotedValue(value)) { return value.slice(1, -1); } return value; }; /** * Pure function to parse boolean values */ const parseBoolean = (value) => { // Guard clause: no value if (!value) { return null; } const lowerValue = value.toLowerCase(); if (lowerValue === 'true' || lowerValue === 'yes' || lowerValue === 'on') { return true; } if (lowerValue === 'false' || lowerValue === 'no' || lowerValue === 'off') { return false; } return null; }; /** * Pure function to parse numeric values */ const parseNumber = (value) => { // Guard clause: no value or empty string if (!value || value === '') { return null; } // Guard clause: values starting with 0 but not "0" or "0.x" are not valid numbers if (value.length > 1 && value.startsWith('0') && !value.includes('.')) { return null; } const numericValue = Number(value); // Guard clause: not a valid number if (isNaN(numericValue)) { return null; } return numericValue; }; //# sourceMappingURL=IniFileAdapter.js.map