Skip to content

Instantly share code, notes, and snippets.

@oncomouse
Last active May 19, 2020 17:02
Show Gist options
  • Save oncomouse/9d97929ad212963ed7061b0361afefa9 to your computer and use it in GitHub Desktop.
Save oncomouse/9d97929ad212963ed7061b0361afefa9 to your computer and use it in GitHub Desktop.
Restore Pandoc Citation Keys
/*
I use this to restore citeproc keys to my Pandoc MS's after de-compiling Word
documents that have been edited using track changes.
*/
const fs = require('fs')
const child_process = require('child_process')
// Tiny ARGV Parser:
const TinyArgv = function (argv) {
this.__argv = argv
}
/**
* Figure out the location of an argument in ARGV based on a key and an alias.
* Returns -1 if neither is found.
*/
TinyArgv.prototype.__argIndex = function (key, alias) {
const index = {
key: this.__argv.indexOf(`--${key}`),
alias: alias ? this.__argv.indexOf(`-${alias}`) : -1
}
return index.key >= 0 ? index.key : index.alias >= 0 ? index.alias : -1
}
/**
* Extracts the value of a switch, which may be more than one word.
*/
TinyArgv.prototype.__extractValue = function (index) {
const nextSwitch = this.__argv.slice(index + 1).reduce((acc, cur, idx) => acc >= 0 ? acc : cur.indexOf('-') === 0 ? idx : acc, -1)
return this.__argv.slice(index + 1, nextSwitch < 0 ? undefined : index + 1 + nextSwitch).join(' ')
}
/**
* Extract a flag (boolean) argument.
* Can have a single-character alias and a default value (so can be true instead
* of false).
*/
TinyArgv.prototype.flag = function ({key, defaultValue = false, alias = false}) {
const indexOf = this.__argIndex(key, alias)
this[key] = indexOf >= 0 ? !defaultValue : defaultValue
return this;
}
/*
* Extract a string-based argument.
* Can have a single-character alias or a default value.
*/
TinyArgv.prototype.value = function ({key, defaultValue = null, alias = false}) {
const indexOf = this.__argIndex(key, alias)
this[key] = indexOf >= 0 ? this.__extractValue(indexOf) : defaultValue
return this;
}
/**
* Convenient chain-initiating function. Call this to start the processing.
* Optional argument: can process a different array than process.argv
*/
const argv = function (argv) {
return new TinyArgv(argv ? argv : process.argv.slice(2))
}
const arguments = argv()
.value({key: 'original', defaultValue: './chapter.md', alias: 'r'})
.value({key: 'formatted', defaultValue: './chapter-revised.md', alias: 'f'})
.value({key: 'output', defaultValue: './chapter-corrected.md', alias: 'o'})
.value({key: 'csl', defaultValue: fs.readFileSync('./Rakefile').toString().match(/\$cite_style = \"(.+?)\"/)[1]})
.value({key: 'bibliography', defaultValue: fs.readFileSync('./Rakefile').toString().match(/bib_file = \"(.+?)\"/)[1].replace('#{ENV[\'HOME\']}', process.env.HOME)})
// Thanks, Stack Overflow:
// https://stackoverflow.com/questions/5069464/replace-multiple-strings-at-once/34560648#34560648
function str_replace($f, $r, $s) {
return $s.replace(new RegExp("(" + (typeof ($f) === "string" ? $f.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&") : $f.map(function (i) {return i.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&")}).join("|")) + ")", "g"), typeof ($r) === "string" ? $r : typeof ($f) === "string" ? $r[0] : function (i) {return $r[$f.indexOf(i)]});
}
const rawCitations = fs.readFileSync(arguments.original).toString().match(/\[\@[^\]]+\]/g)
const substitutions = {}
// Process the list of all the raw citations from the original file to find the cite keys:
process.stdout.write('Processing citations in Pandoc (this is slow)…')
rawCitations.forEach(cite => {
substitutions[cite] = child_process.execSync(`echo "${cite}" | pandoc -f markdown+smart -t plain+smart --bibliography="${arguments.bibliography}" --csl="${arguments.csl}.csl"`).toString().split(/\n/g)[0]
})
console.log('done')
fs.readFile(arguments.formatted, (_err, text) => {
fs.writeFile(arguments.output, str_replace(Object.values(substitutions), Object.keys(substitutions), text.toString()), () => {})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment