Created
April 27, 2019 13:31
-
-
Save jdarling/ac71dee8a3c72205669390cc46426a4d to your computer and use it in GitHub Desktop.
Generic pattern matching and runner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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