UNPKG

@syntropysoft/praetorian

Version:

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

513 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseHclValue = exports.parseHclContent = exports.HclFileAdapter = void 0; const AbstractFileAdapter_1 = require("../base/AbstractFileAdapter"); /** * HCL File Adapter - Functional Programming * * Single Responsibility: Parse HCL configuration files only * Pure functions, no state, no side effects */ class HclFileAdapter extends AbstractFileAdapter_1.AbstractFileAdapter { canHandle(filePath) { // Guard clause: no file path if (!filePath || typeof filePath !== 'string') { return false; } return filePath.endsWith('.hcl') || filePath.endsWith('.tf') || filePath.endsWith('.tfvars'); } 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.parseHclContent)(content); } catch (error) { throw new Error(`Failed to parse HCL file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } getFormat() { return 'hcl'; } getSupportedExtensions() { return ['.hcl', '.tf', '.tfvars']; } } exports.HclFileAdapter = HclFileAdapter; /** * Pure function to parse HCL content */ const parseHclContent = (content) => { // Guard clause: no content if (!content || typeof content !== 'string') { return {}; } const lines = splitIntoLines(content); const cleanLines = removeCommentsFromLines(lines); const blocks = parseBlocks(cleanLines); return blocks.reduce((result, block) => { if (hasValidBlockName(block.name)) { result[block.name] = block.data; } return result; }, {}); }; exports.parseHclContent = parseHclContent; /** * Pure function to split content into lines */ const splitIntoLines = (content) => { // Guard clause: empty content if (!content) { return []; } return content.split('\n'); }; /** * Pure function to remove comments from lines */ const removeCommentsFromLines = (lines) => { // Guard clause: no lines if (!lines || lines.length === 0) { return []; } return lines .map(removeCommentsFromLine) .filter(isNotEmptyLine); }; /** * Pure function to remove comments from a single line */ const removeCommentsFromLine = (line) => { // Guard clause: no line if (!line) { return ''; } return line .replace(/#.*$/, '') .replace(/\/\/.*$/, '') .trim(); }; /** * Pure function to check if line is not empty */ const isNotEmptyLine = (line) => { return line.length > 0; }; /** * Pure function to remove quotes from block names */ const removeQuotesFromBlockName = (blockName) => { // Guard clause: no block name if (!blockName) { return ''; } return blockName .split(' ') .map(part => part.replace(/^["']|["']$/g, '')) .join('.'); }; /** * Pure function to parse blocks from lines */ const parseBlocks = (lines) => { // Guard clause: no lines if (!lines || lines.length === 0) { return []; } let currentBlock = null; let currentBlockData = {}; const blocks = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Guard clause: skip empty lines if (!isNotEmptyLine(line)) { continue; } // Check if it's a block definition const blockInfo = parseBlockDefinition(line); if (isValidBlockInfo(blockInfo)) { // Save previous block if exists if (hasValidBlockName(currentBlock) && hasBlockData(currentBlockData)) { blocks.push({ name: currentBlock, data: currentBlockData }); } currentBlock = blockInfo.name; currentBlockData = {}; continue; } // Check if it's a key-value assignment const assignment = parseKeyValueAssignment(line); if (isValidAssignment(assignment) && hasValidBlockName(currentBlock)) { currentBlockData[assignment.key] = (0, exports.parseHclValue)(assignment.value); continue; } // Check if it's a nested block const nestedBlock = parseNestedBlockDefinition(line); if (isValidNestedBlock(nestedBlock) && hasValidBlockName(currentBlock)) { const nestedData = parseNestedBlockContent(lines, i + 1); currentBlockData[nestedBlock.name] = nestedData.data; i += nestedData.consumedLines; continue; } // Check if it's a closing brace if (isClosingBrace(line) && hasValidBlockName(currentBlock)) { if (hasBlockData(currentBlockData)) { blocks.push({ name: currentBlock, data: currentBlockData }); } currentBlock = null; currentBlockData = {}; } } // Save final block if exists if (hasValidBlockName(currentBlock) && hasBlockData(currentBlockData)) { blocks.push({ name: currentBlock, data: currentBlockData }); } return blocks; }; /** * Pure function to parse block definition */ const parseBlockDefinition = (line) => { // Guard clause: no line if (!line) { return null; } // Check for resource block (only valid resource types) const resourceMatch = line.match(/^(resource|provider|terraform|locals|output)\s+([^{]+)\s*{/); if (isValidResourceMatch(resourceMatch)) { const [, blockType, blockName] = resourceMatch; const cleanBlockName = removeQuotesFromBlockName(blockName.trim()); return { name: `${blockType}.${cleanBlockName}`, type: blockType }; } // Check for variable block const variableMatch = line.match(/^variable\s+([^{]+)\s*{/); if (isValidVariableMatch(variableMatch)) { const cleanBlockName = removeQuotesFromBlockName(variableMatch[1].trim()); return { name: `variable.${cleanBlockName}`, type: 'variable' }; } // Check for data block const dataMatch = line.match(/^data\s+([^{]+)\s*{/); if (isValidDataMatch(dataMatch)) { const cleanBlockName = removeQuotesFromBlockName(dataMatch[1].trim()); return { name: `data.${cleanBlockName}`, type: 'data' }; } // Check for module block const moduleMatch = line.match(/^module\s+([^{]+)\s*{/); if (isValidModuleMatch(moduleMatch)) { const cleanBlockName = removeQuotesFromBlockName(moduleMatch[1].trim()); return { name: `module.${cleanBlockName}`, type: 'module' }; } return null; }; /** * Pure function to check if resource match is valid */ const isValidResourceMatch = (match) => { return match !== null && match.length >= 3; }; /** * Pure function to check if variable match is valid */ const isValidVariableMatch = (match) => { return match !== null && match.length >= 2; }; /** * Pure function to check if data match is valid */ const isValidDataMatch = (match) => { return match !== null && match.length >= 2; }; /** * Pure function to check if module match is valid */ const isValidModuleMatch = (match) => { return match !== null && match.length >= 2; }; /** * Pure function to check if block info is valid */ const isValidBlockInfo = (blockInfo) => { return blockInfo !== null && hasValidBlockName(blockInfo.name); }; /** * Pure function to check if block name is valid */ const hasValidBlockName = (blockName) => { return blockName !== null && blockName.length > 0; }; /** * Pure function to check if block has data */ const hasBlockData = (blockData) => { return Object.keys(blockData).length > 0; }; /** * Pure function to parse key-value assignment */ const parseKeyValueAssignment = (line) => { // Guard clause: no line if (!line) { return null; } const match = line.match(/^(\w+)\s*=\s*(.+)$/); if (!isValidAssignmentMatch(match)) { return null; } const [, key, value] = match; return { key, value: value.trim() }; }; /** * Pure function to check if assignment match is valid */ const isValidAssignmentMatch = (match) => { return match !== null && match.length >= 3; }; /** * Pure function to check if assignment is valid */ const isValidAssignment = (assignment) => { return assignment !== null && assignment.key.length > 0; }; /** * Pure function to parse nested block definition */ const parseNestedBlockDefinition = (line) => { // Guard clause: no line if (!line) { return null; } if (!containsOpeningBrace(line)) { return null; } const match = line.match(/^(\w+)\s*{/); if (!isValidNestedBlockMatch(match)) { return null; } return { name: match[1] }; }; /** * Pure function to check if line contains opening brace */ const containsOpeningBrace = (line) => { return line.includes('{'); }; /** * Pure function to check if nested block match is valid */ const isValidNestedBlockMatch = (match) => { return match !== null && match.length >= 2; }; /** * Pure function to check if nested block is valid */ const isValidNestedBlock = (nestedBlock) => { return nestedBlock !== null && nestedBlock.name.length > 0; }; /** * Pure function to parse nested block content */ const parseNestedBlockContent = (lines, startIndex) => { // Guard clause: no lines or invalid start index if (!lines || startIndex < 0 || startIndex >= lines.length) { return { data: {}, consumedLines: 0 }; } let braceCount = 1; let consumedLines = 0; const data = {}; for (let i = startIndex; i < lines.length; i++) { consumedLines++; const line = lines[i]; // Guard clause: skip empty lines if (!isNotEmptyLine(line)) { continue; } if (containsOpeningBrace(line)) { braceCount++; } if (isClosingBrace(line)) { braceCount--; if (isBraceCountZero(braceCount)) { break; } } // Parse key-value pairs in nested block const assignment = parseKeyValueAssignment(line); if (isValidAssignment(assignment)) { data[assignment.key] = (0, exports.parseHclValue)(assignment.value); continue; } // Handle nested blocks within nested blocks const nestedBlock = parseNestedBlockDefinition(line); if (isValidNestedBlock(nestedBlock)) { const nestedData = parseNestedBlockContent(lines, i + 1); data[nestedBlock.name] = nestedData.data; i += nestedData.consumedLines; consumedLines += nestedData.consumedLines; } } return { data, consumedLines }; }; /** * Pure function to check if line is closing brace */ const isClosingBrace = (line) => { return line === '}'; }; /** * Pure function to check if brace count is zero */ const isBraceCountZero = (braceCount) => { return braceCount === 0; }; /** * Pure function to parse an HCL value */ const parseHclValue = (value) => { // Guard clause: no value if (!value || value === null || value === undefined) { return ''; } // Check if value is quoted - if so, return as string if (isQuotedValue(value)) { return removeQuotes(value); } // Parse boolean values const booleanValue = parseBoolean(value); if (booleanValue !== null) { return booleanValue; } // Parse array values const arrayValue = parseArray(value); if (arrayValue !== null) { return arrayValue; } // Parse object values const objectValue = parseObject(value); if (objectValue !== null) { return objectValue; } // Parse numeric values const numericValue = parseNumber(value); if (numericValue !== null) { return numericValue; } // Return as string return value; }; exports.parseHclValue = parseHclValue; /** * 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 check if value is quoted */ const isQuotedValue = (value) => { // Guard clause: no value or too short if (!value || value.length < 2) { return false; } return (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")); }; /** * Pure function to parse boolean values */ const parseBoolean = (value) => { // Guard clause: no value if (!value) { return null; } if (value === 'true') { return true; } if (value === 'false') { return false; } return null; }; /** * Pure function to parse array values */ const parseArray = (value) => { // Guard clause: no value or not array format if (!value || !isArrayFormat(value)) { return null; } try { // Try to parse as JSON array return JSON.parse(value); } catch { // Fallback: parse manually const content = value.slice(1, -1).trim(); if (!hasArrayContent(content)) { return []; } return content.split(',').map(item => (0, exports.parseHclValue)(item.trim())); } }; /** * Pure function to check if value is array format */ const isArrayFormat = (value) => { return value.startsWith('[') && value.endsWith(']'); }; /** * Pure function to check if array has content */ const hasArrayContent = (content) => { return content.length > 0; }; /** * Pure function to parse object values */ const parseObject = (value) => { // Guard clause: no value or not object format if (!value || !isObjectFormat(value)) { return null; } try { // Try to parse as JSON object return JSON.parse(value); } catch { // Invalid JSON - return null return null; } }; /** * Pure function to check if value is object format */ const isObjectFormat = (value) => { return value.startsWith('{') && value.endsWith('}'); }; /** * Pure function to parse numeric values */ const parseNumber = (value) => { // Guard clause: no value if (!value) { return null; } const numericValue = Number(value); // Guard clause: not a valid number if (isNaN(numericValue)) { return null; } return numericValue; }; //# sourceMappingURL=HclFileAdapter.js.map