function needsQuotes(s) {
if (s === '') return true;
if (/^[0-9]+(?:[.][0-9]+)?$/.test(s)) return false;
return true;
}
function escapeString(s) {
let result = '';
for (const c of s) {
switch (c) {
case '"': result += '\\"'; break;
case '\\': result += '\\\\'; break;
case '\n': result += '\\n'; break;
case '\r': result += '\\r'; break;
case '\t': result += '\\t'; break;
default: result += c;
}
}
return result;
}
function writeValue(value, indent = 0) {
switch (value.kind) {
case 'String':
return needsQuotes(value.value) ? `"${escapeString(value.value)}"` : value.value;
case 'Number':
return String(value.value);
case 'Bool':
return String(value.value);
case 'Null':
return 'null';
case 'List':
if (value.items.length === 0) return '[]';
return `[${value.items.map(v => writeValue(v, indent)).join(', ')}]`;
case 'Object':
if (value.pairs.length === 0) return '{}';
const pairStrs = value.pairs.map(([k, v]) => `${k} = ${writeValue(v, indent)}`);
return `{${pairStrs.join(', ')}}`;
case 'Call':
const args = value.args.map(a => writeValue(a, indent)).join(', ');
return `${value.name}(${args})`;
}
}
function generateBlock(block, indent = 0, config = {}) {
const spaces = ' '.repeat(indent);
let result = spaces;
if (block.labels.length === 0 && block.blocks.length === 0 && block.attributes.size === 1) {
const [key, value] = Array.from(block.attributes.entries())[0];
if (value.kind === 'String' && !value.value.includes(' ')) {
result += block.name + ' = ' + writeValue(value, indent) + '\n';
return result;
}
}
if (!config.labelsAsAttrs && block.labels.length > 0) {
result += `${block.name} ${block.labels.map(l => `"${escapeString(l)}"`).join(' ')}`;
} else {
result += block.name;
}
const attrKeys = Array.from(block.attributes.keys()).sort();
const hasLabelsAsAttrs = config.labelsAsAttrs && block.labels.length > 0;
if (attrKeys.length === 0 && block.blocks.length === 0 && !hasLabelsAsAttrs) {
result += ' { }';
return result;
}
result += ' {\n';
if (hasLabelsAsAttrs) {
const labelValue = block.labels[0];
const attrName = block.name === 'models' ? 'id' : 'name';
result += `${spaces} ${attrName} = "${escapeString(labelValue)}"\n`;
}
for (const key of attrKeys) {
const value = block.attributes.get(key);
result += `${spaces} ${key} = ${writeValue(value, indent + 1)}\n`;
}
for (const nested of block.blocks) {
result += generateBlock(nested, indent + 1, config) + '\n';
}
result += `${spaces}}`;
return result;
}
function generate(doc, config = {}) {
return doc.blocks.map(b => generateBlock(b, 0, config)).join('\n');
}
function generateHCL(doc) {
return generate(doc, { labelsAsAttrs: true });
}
module.exports = { generate, generateHCL, generateBlock, writeValue, needsQuotes, escapeString };