Skip to content

Instantly share code, notes, and snippets.

@jdarling
Created April 27, 2019 13:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdarling/ac71dee8a3c72205669390cc46426a4d to your computer and use it in GitHub Desktop.
Save jdarling/ac71dee8a3c72205669390cc46426a4d to your computer and use it in GitHub Desktop.
Generic pattern matching and runner
const Async = require('async');
const test = `
Symptoms: #This is a comment line
Log into the kube-master switch to sudo and execute kube-master-check.sh script
Pre-checks:
N/A
Resolution:
Log into the kube-master switch to sudo and execute kube-master.sh script
Post-checks:
Log into the kube-master switch to sudo and execute kube-master-check.sh script
Rollback:
N/A
`;
const ORDER = [
'symptoms', //*
'pre-checks?',
'resolution',
'post-checks?',
'rollback' //*/
];
const PATTERNS = {
alpha: '[a-z]+',
numeric: '[0-9]+',
alphanum: '[a-z0-9]+',
alphanumdot: '[a-z0-9.-]+',
notwhite: '[^\\s]+',
quotedstring: /"((?:\\"|[^"])*)"|'((?:\\'|[^'])*)'|(\/\/.*|\/\*[\s\S]*?\*\/)/,
string: /"((?:\\"|[^"])*)"|'((?:\\'|[^'])*)'|(\/\/.*|\/\*[\s\S]*?\*\/)|[^\s]+/,
int: '-?[0-9]',
float: '-?[0-9](\\.[0-9]+)?(e[0-9]+)?',
number: '-?[0-9](\\.[0-9]+)?(e[0-9]+)?'
};
const reToken = /{([^}]+)}/gi;
const regexpFromSrc = src => {
if (src instanceof RegExp) {
return src;
}
return new RegExp(`^${src}`, 'i');
};
const createMatcherFunc = from => {
const isToken = reToken.exec(from);
if (isToken) {
const token = isToken[1];
const reSource = PATTERNS[token] || token;
const reMatchVar = regexpFromSrc(reSource);
return src => {
const match = reMatchVar.exec(src);
if (match) {
return { type: 'var', var: match, value: match[0] };
}
return false;
};
}
const reKeyoff = new RegExp(from, 'i');
return src => {
const block = reKeyoff.exec(src);
if (!block) {
return false;
}
return { type: 'block', block, value: block[0] };
};
};
const createMatcher = rule => {
const reSource = rule.replace(/[ \t]+/g, '[ \\t]+');
const parts = reSource.split(/({[^}]+}|[^{]+)/g).filter(s => s.trim());
const matchers = parts.map(createMatcherFunc);
return source => {
const matches = matchers.reduce(
(a, matcher) => {
if (!a) {
return a;
}
const m = matcher(source.substr(a.offset));
if (!m) {
return false;
}
const match = m[m.type];
a.offset = a.offset + match.index + match[0].length;
a.matches.push(m);
return a;
},
{ offset: 0, matches: [] }
);
if (matches) {
matches.length = matches.matches.reduce((v, m) => m.value.length + v, 0);
return matches;
}
return false;
};
};
const createRunner = ([rule, handler]) => {
const matcher = createMatcher(rule);
return source => {
const matches = matcher(source);
if (matches) {
const variables = matches.matches
.filter(m => m.type === 'var')
.map(m => m.value);
return {
handler,
offset: matches.offset,
length: matches.length,
matches,
variables
};
}
return false;
};
};
const createRunners = source => {
const rules = Object.entries(source);
return rules.map(createRunner);
};
const RUNNERS = [
{
'log into (a|the) {string}'(moduleName) {
console.log('Log into ', moduleName);
},
"log into all {string}('s|)"(moduleName) {
console.log('Log into ALL', moduleName);
}
},
{
'(switch to|as) sudo'() {
console.log('SUDO');
}
},
{
'execute {notwhite}'(scriptName) {
console.log('execute', scriptName);
}
}
]
.map(createRunners)
.reduce((a, b) => a.concat(...b), []);
//console.log(RUNNERS);
const reEOL = /(\r\n|\n)+/g;
const reNoWhiteStart = /^[^ \t]/;
const getStageSource = (stageName, source) => {
const reStage = new RegExp(
`^${stageName}[ \\t]*:?([ \\t]*#.*)?[\r\n]+`,
'im'
);
const segmentStart = reStage.exec(source);
if (!segmentStart) {
return '';
}
const tokenStart = segmentStart.index;
const tokenEnd = tokenStart + segmentStart[0].length;
const restSource = source.substr(tokenEnd).split(reEOL);
const sectionEnd = restSource.findIndex(
l => l.trim().length > 0 && reNoWhiteStart.exec(l)
);
return restSource
.slice(0, sectionEnd > -1 ? sectionEnd : restSource.length)
.join();
};
const bestMatchRunner = stage => {
return RUNNERS.map(f => f(stage)).reduce((best, m) => {
if (!best && !!m) {
return m;
}
if (m.offset < best.offset) {
return m;
}
return best;
}, false);
};
const runStage = (stageName, source, callback) => {
const stage = getStageSource(stageName, source);
if (!stage) {
return callback();
}
let offset = 0;
let next = bestMatchRunner(stage);
let src = stage;
while (next && src) {
offset += next.offset;
src = stage.substr(offset).trim();
next.handler(...next.variables);
next = bestMatchRunner(src);
}
return callback(null);
};
Async.each(ORDER, (key, next) => runStage(key, test, next));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment