node-puppetdbquery
Version:
a simple query language parser for PuppetDB v4 API
140 lines (137 loc) • 4.27 kB
JavaScript
import timespec from 'timespec';
import { visit } from './ast';
import { formatLocation, capitalizeClass, regexpEscape, capitalize } from './util';
const comparison = (operator, left, right) => {
if (operator === '!=' || operator === '!~') {
return ['not', [operator[1], left, right]];
}
return [operator, left, right];
};
export default (ast) => {
const mode = ['fact'];
return visit(ast, {
visitComparison(path) {
this.traverse(path);
// Function to handle negating comparisons
if (mode[0] === 'fact') {
return ['in', 'certname',
['extract', 'certname',
['select_fact_contents',
['and',
path.node.left,
comparison(path.node.operator, 'value', path.node.right)]]]];
} else if (mode[0] === 'subquery') {
let left;
if (path.node.left.length === 1) {
left = path.node.left[0];
} else {
left = path.node.left;
}
return comparison(path.node.operator, left, path.node.right);
} else if (mode[0] === 'resource') {
if (path.node.left[0] === 'tag') {
return comparison(path.node.operator, path.node.left[0], path.node.right);
}
return comparison(path.node.operator, ['parameter', path.node.left[0]], path.node.right);
}
throw Error(`Unknown mode ${mode}`);
},
visitBoolean(path) {
// returning false to use it as a replacement doesn't work
path.replace(path.node.value);
return false;
},
visitString(path) {
return path.node.value;
},
visitNumber(path) {
return path.node.value;
},
visitDate(path) {
try {
return timespec.parse(path.node.value).toISOString();
} catch (error) {
const loc = formatLocation(path.node);
throw new Error(`Failed to parse date: "${path.node.value}" at ${loc}`);
}
},
visitAndExpression(path) {
this.traverse(path);
return ['and', path.node.left, path.node.right];
},
visitOrExpression(path) {
this.traverse(path);
return ['or', path.node.left, path.node.right];
},
visitNotExpression(path) {
this.traverse(path);
return ['not', path.node.expression];
},
visitQuery(path) {
this.traverse(path);
return path.node.expression;
},
visitParentesizedExpression(path) {
this.traverse(path);
return path.node.expression;
},
visitBlockExpression(path) {
this.traverse(path);
return path.node.expression;
},
visitSubquery(path) {
mode.unshift('subquery');
this.traverse(path);
mode.shift();
return ['in', 'certname',
['extract', 'certname',
[`select_${path.node.endpoint}s`, path.node.expression]]];
},
visitRegexpNodeMatch(path) {
mode.unshift('regexp');
this.traverse(path);
mode.shift();
return ['~', 'certname', regexpEscape(path.node.value.join('.'))];
},
visitIdentifierPath(path) {
this.traverse(path);
if (mode[0] === 'fact') {
return [
(path.node.regexp ? '~>' : '='),
'path',
path.node.components,
];
}
return path.node.components;
},
visitRegexpIdentifier(path) {
return path.node.name;
},
visitIdentifier(path) {
if (path.parentPath.node.regexp) {
return regexpEscape(path.node.name);
}
return path.node.name;
},
visitResource(path) {
const regexp = (path.node.title.type === 'RegexpIdentifier');
mode.unshift('resource');
this.traverse(path);
mode.shift();
let { title } = path.node;
if (!regexp && capitalize(path.node.res_type) === 'Class') {
title = capitalizeClass(title);
}
const andExpr = ['and',
['=', 'type', capitalizeClass(path.node.res_type)],
[(regexp ? '~' : '='), 'title', title],
['=', 'exported', path.node.exported]];
if (path.node.parameters) {
andExpr.push(path.node.parameters);
}
return ['in', 'certname',
['extract', 'certname',
['select_resources', andExpr]]];
},
});
};