Skip to content

Instantly share code, notes, and snippets.

@mildsunrise
Last active May 17, 2019 20:17
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 mildsunrise/5adece4bf728af4c16cb496176d9a499 to your computer and use it in GitHub Desktop.
Save mildsunrise/5adece4bf728af4c16cb496176d9a499 to your computer and use it in GitHub Desktop.
Quick script to write many config files in a single file
const util = require('util')
const path = require('path')
const fs = require('fs')
const mkdir = util.promisify(fs.mkdir)
const readFile = util.promisify(fs.readFile)
const writeFile = util.promisify(fs.writeFile)
const trimLines = text => text.replace(/^[ \t]*\n?|\n?[ \t]*$/g, '')
const dedent = text => {
const lines = text.split('\n').map(line => line.trim() ? line : '')
let indent = 0, indentLine
for (const line of lines.filter(x => x)) {
if (!indentLine) {
indentLine = line
indent = /^\s*/.exec(line)[0].length
} else {
let n = 0
while (n < line.length && n < indent && line[n] === indentLine[n]) n++
indent = n
}
}
return lines.map(line => line.substring(indent)).join('\n')
}
const prepare = text => dedent(trimLines(text)) + '\n'
async function main() {
const args = process.argv.slice(2)
if (args.length < 1 || args.length > 2) {
console.error('Usage: unpack-config <source file> [<destination dir>]')
process.exit(1)
}
const base = args[1] || '.'
const source = await readFile(args[0], 'utf8')
const files = parse(source.replace(/\r\n?/g, '\n'))
const writtenFiles = new Map()
for (const [name, contents] of files) {
const file = path.join(base, path.normalize(name))
if (writtenFiles.has(file))
throw new Error(`Duplicate file specified: ${util.inspect(name)}`)
console.log(`Writing: ${name}`)
writtenFiles.set(file, (async () => {
await mkdir(path.dirname(file), { recursive: true })
.catch(err => err.code === 'EEXIST' || Promise.reject(err))
await writeFile(file, prepare(contents), 'utf8')
})())
}
await Promise.all(writtenFiles.values())
}
main().catch(err => {
console.error(`Error: ${(err && err.stack) || err}`)
process.exit(1)
})
// PARSER
// ------
interface StackFrame {
parent?: StackFrame
file?: string
contents: string
}
function parse(data: string): Map<String, String> {
const ESCAPABLE_CHARS = '\\{}='
const size = data.length
const files: Map<String, String> = new Map()
let frame: StackFrame = { contents: '' }
let i = 0
while (i < size) {
if (data[i] === '\\' && i+1 < size &&
ESCAPABLE_CHARS.indexOf(data[i+1]) !== -1) {
frame.contents += data[i+1]
i += 2
continue
} else if (data[i] === '{') {
let file = null
const m = /(^|\s)([^ \x00-\x1F\x7F]+)\s*\=\s*$/.exec(frame.contents)
if (m) {
const remove = m[0].length - m[1].length
frame.contents = frame.contents.substring(frame.contents.length - remove)
file = m[2]
}
frame = { parent: frame, contents: '', file }
} else if (data[i] === '}') {
if (!frame.parent)
throw new Error('Unexpected closing bracket')
if (frame.file)
files.set(frame.file, frame.contents)
frame = frame.parent
} else {
frame.contents += data[i]
}
i++
}
if (frame.parent)
throw new Error('Unclosed blocks!')
return files
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment