fictional
Version:
Generate fake data deterministically from a given input
182 lines (143 loc) • 4.41 kB
JavaScript
const fs = require('fs')
const vm = require('vm')
const util = require('util')
const path = require('path')
const { createRequire } = require('module')
const readmePath = process.argv[2]
const header = process.argv[3] || ''
const ignoreRegex = process.argv[4] ? new RegExp(process.argv[4], 'g') : null
const parseReadme = source => {
const nodes = []
let lines = source.split('\n')
while (lines.length) {
const nextCodeBlockStart = lines.findIndex(isCodeBlockIndicatorLine)
if (nextCodeBlockStart > -1) {
const linesFromCodeBlockStart = lines.slice(nextCodeBlockStart + 1)
const nextCodeBlockEnd = linesFromCodeBlockStart.findIndex(
isCodeBlockIndicatorLine
)
if (nextCodeBlockEnd < 0) {
throw new Error('Found code block without an end marker')
}
const codeBlock = linesFromCodeBlockStart.slice(0, nextCodeBlockEnd)
const textBeforeCodeBlock = lines.slice(0, nextCodeBlockStart)
if (textBeforeCodeBlock.length) {
nodes.push({
kind: 'text',
value: textBeforeCodeBlock.join('\n')
})
}
nodes.push({
kind: 'code',
code: codeBlock.join('\n'),
startLine: lines[nextCodeBlockStart]
})
lines = linesFromCodeBlockStart.slice(nextCodeBlockEnd + 1)
} else {
nodes.push({
kind: 'text',
value: lines.join('\n')
})
lines = []
}
}
return nodes
}
const parseCode = code => {
const blocks = []
let lines = code.split('\n')
while (lines.length) {
const nextExampleStart = lines.findIndex(isExampleStartMarker)
if (nextExampleStart > -1) {
const nextExampleStartLine = lines[nextExampleStart]
let nextExampleEnd = nextExampleStart
if (isMultiLineExampleStartMarker(nextExampleStartLine)) {
nextExampleEnd = lines.findIndex(isMultilineCommentEndMarker)
if (nextExampleEnd < 0) {
throw new Error('Unending multiline comment found')
}
}
blocks.push({
kind: 'example',
value: lines.slice(0, nextExampleStart).join('\n')
})
const linesFromExampleEnd = lines.slice(nextExampleEnd + 1)
lines = linesFromExampleEnd
} else {
blocks.push({
kind: 'normal',
value: lines.join('\n')
})
lines = []
}
}
return blocks
}
const isExampleStartMarker = s =>
s.includes('// =>') || isMultiLineExampleStartMarker(s)
const isMultiLineExampleStartMarker = s => s.includes('/* =>')
const isMultilineCommentEndMarker = s => s.includes('*/')
const transformNode = node => {
if (node.kind === 'text') {
return node
}
const context = vm.createContext({
require: createRequire(path.join(process.cwd(), 'tmp.js'))
})
vm.runInContext(header, context)
let result = ''
for (const block of parseCode(node.code)) {
if (block.kind === 'normal') {
result += `${block.value}\n`
} else {
try {
let code = block.value
if (ignoreRegex) {
code = block.value.replaceAll(ignoreRegex, '')
}
const blockResult = vm.runInContext(code, context)
const stringifiedBlockResult = util.inspect(blockResult)
if (stringifiedBlockResult.indexOf('\n') !== -1) {
result += `${block.value}\n/* =>\n${stringifiedBlockResult}\n*/\n`
} else {
result += `${block.value}\n// => ${stringifiedBlockResult}\n`
}
} catch (e) {
throw new Error(
`Error in code block:\nError:\n${e.stack}\n\nCode block:\n${block.value}`
)
}
}
}
return {
...node,
code: result
}
}
const stringify = nodes => {
let result = ''
for (const node of nodes) {
if (node.kind === 'text') {
result += `${node.value}\n`
} else if (node.kind === 'code') {
result += `${node.startLine}\n${node.code}${'```'}\n`
}
}
return result
}
const isCodeBlockIndicatorLine = line => line.includes('```')
const main = () => {
const currentReadme = fs.readFileSync(readmePath).toString().trim()
let nodes = parseReadme(currentReadme)
nodes = nodes.map(transformNode)
const result = stringify(nodes)
if (process.env.DRY) {
process.stdout.write(result)
} else {
fs.writeFileSync(readmePath, result)
}
}
if (require.main === module && !process.env.IS_WORKER) {
main()
}