UNPKG

brainwave

Version:

Programmatically update frontmatter without leaving JS

447 lines (393 loc) 14.1 kB
#!/usr/bin/env node 'use strict'; function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var fluture = require('fluture'); var path = _interopDefault(require('path')); var ramda = require('ramda'); var parser = _interopDefault(require('yargs-parser')); var torpor = require('torpor'); var cosmiconfig = require('cosmiconfig'); var ensorcel = require('ensorcel'); var jsYaml = require('js-yaml'); var globby = _interopDefault(require('globby')); var grayMatter = _interopDefault(require('gray-matter')); var globWithCancel = ensorcel.futurizeWithCancel(ramda.__, 1, globby); var fork = ensorcel.tacit(3, fluture.fork); var relativePath = ramda.curryN(2, path.resolve); var wrap = ramda.curryN(2, function (kk, vv) { var obj; return (( obj = {}, obj[kk] = vv, obj )); }); var isString = function (x) { return typeof x === "string"; }; var stringInString = ramda.curryN( 2, function (xx, yy) { return isString(xx) && isString(yy) && (xx.includes(yy) || yy.includes(xx)); } ); var BR = "brain"; var CO = "content"; // comes from cosmiconfig var CC = "config"; // comes from cosmiconfig var CF = "configFile"; var FI = "filepath"; var FT = "fileTypes"; var MC = "control"; var NS = "namespace"; var RT = "root"; var GLOB = "glob"; var TK = "telepathy"; var ST = "stats"; var DA = "data"; var DR = "dryRun"; var FC = "fileContent"; var RP = "relativePath"; var INIT = "init"; var addStats = function (x) { return ramda.pipe( torpor.stat(ramda.__, { bigint: false }), ramda.map(ramda.pipe(ramda.reject(ramda.is(Function)), wrap(ST), ramda.assoc(FI, x))) )(x); }; var brainScan = ramda.pipe( torpor.readFile(ramda.__, "utf8"), ramda.map(ramda.pipe(grayMatter, ensorcel.box, ramda.ap([ramda.prop(DA), ramda.prop(CO)]))) ); var addBrains = function (x) { return ramda.pipe( brainScan, ramda.map( ramda.pipe( function (ref) { var obj; var brain = ref[0]; var fileContent = ref[1]; return (( obj = {}, obj[BR] = brain, obj[FC] = fileContent, obj )); }, ramda.assoc(FI, x) ) ) )(x); }; var consumeData = ramda.pipe( ramda.reject(ramda.includes("node_modules")), ensorcel.box, ramda.ap([ramda.map(addStats), ramda.map(addBrains)]), ramda.reduce(ramda.concat, []), fluture.parallel(10), ramda.map(ramda.pipe(ramda.groupBy(ramda.prop(FI)), ramda.map(ramda.reduce(ramda.mergeRight, {})))) ); var joint = ramda.curry(function (xx, aa, bb) { return path.join(aa, xx + "." + bb); }); var toYAML = ramda.curryN(2, jsYaml.safeDump); var findBrainsRelativeTo = ramda.curry( function (cancel, basePath, lookup, fileTypes) { return ramda.pipe( joint(lookup, basePath), globWithCancel(cancel), ramda.chain(consumeData) )(fileTypes); } ); var ERRORS = Object.freeze({ RETURN_A_FUNCTION: ("Expected to have brainwave config return a function! Have you run 'brainwave --" + INIT + "'?"), TELEPATHY_OR_MIND_CONTROL: ("Expected brainwave config to have one or more keys in: [" + MC + ", " + TK + "]") }); // ;) var generateNeuralNetwork = ramda.curry(function (bad, xxx) { var cnf = ramda.propOr(false, CC, xxx); if (!cnf) { return bad(new Error(ERRORS.RETURN_A_FUNCTION)) } var memoryPalace = ramda.is(Function, cnf) ? cnf() : cnf; var control = ramda.propOr(false, MC, memoryPalace); var telepathy = ramda.propOr(false, TK, memoryPalace); if (!control && !telepathy) { return bad(new Error(ERRORS.TELEPATHY_OR_MIND_CONTROL)) } return memoryPalace }); var runTelepathy = ramda.curry( function (ref, ref$1) { var isCancelled = ref.isCancelled; var cancel = ref.cancel; var ct = ref$1.control; var brains = ref$1.brains; var tk = ref$1.telepathy; return new fluture.Future(function (bad, good) { try { /* istanbul ignore next */ if (isCancelled) { /* istanbul ignore next */ bad("Is cancelled!"); return } ramda.pipe( // each telepathy config entry ramda.toPairs, ramda.map(function (ref) { var kk = ref[0]; var vv = ref[1]; // each brain return [kk, ramda.pipe(ramda.map(ramda.pipe(ramda.propOr({}, BR), vv)), ensorcel.smooth)(brains)] }), ramda.fromPairs, function (telepathy) { return ({ brains: brains, control: ct, telepathy: telepathy }); }, good )(tk); } catch (e) { /* istanbul ignore next */ bad(e); } return cancel }); } ); var NO_CHANGE = "NO_CHANGE"; var mindMapper = ramda.curry(function (brains, conditions) { return ramda.map(function (mind) { return ramda.reduce( function (agg, ref) { var kk = ref[0]; var ref_1 = ref[1]; var pred = ref_1[0]; var trans = ref_1[1]; var changed = ramda.ifElse(pred, trans, function () { return NO_CHANGE; })(mind); var hasChanged = changed !== NO_CHANGE; if (!hasChanged) { return agg } var matched = agg.matched.concat(kk); var transformed = ramda.mergeRight(agg.transformed, changed); return { transformed: transformed, matched: matched } }, { transformed: {}, matched: [] }, conditions ); } )(brains); } ); var runMindControl = ramda.curry( function (ref, ref$1) { var isCancelled = ref.isCancelled; var cancel = ref.cancel; var ct = ref$1.control; var brains = ref$1.brains; var tk = ref$1.telepathy; return new fluture.Future(function (bad, good) { try { /* istanbul ignore next */ if (isCancelled) { /* istanbul ignore next */ bad("Is cancelled!"); return } ramda.pipe( ramda.toPairs, // cond short-circuits, so we are gonna use when + reduce instead mindMapper(brains), // conditions => map(mindMapper(conditions), brains), function (control) { return ({ control: control, brains: brains, telepathy: tk }); }, good )(ct); } catch (e) { /* istanbul ignore next */ bad(e); } return cancel }); } ); var search = ramda.curry(function (cancellationPolicy, config, network) { var control = ramda.propOr(null, MC, network); var telepathy = ramda.propOr(null, TK, network); var basePath = ramda.propOr(process.cwd(), RT, config); var lookup = ramda.propOr(path.join("**", "*"), GLOB, config); var getFileTypes = ramda.propOr("{md,mdx}", FT); return ramda.pipe( getFileTypes, findBrainsRelativeTo(cancellationPolicy.cancel, basePath, lookup), ramda.map(function (brains) { return ({ brains: brains, control: control, telepathy: telepathy }); }), ramda.chain(runTelepathy(cancellationPolicy)), ramda.chain(runMindControl(cancellationPolicy)) )(config) }); var psychic = ramda.curry( function (ref, bad, config) { var isCancelled = ref.isCancelled; var cancel = ref.cancel; var loadOrSearch = ref.loadOrSearch; try { return ramda.pipe( ramda.propOr(false, CF), loadOrSearch, ramda.map(generateNeuralNetwork(bad)), ramda.chain(search({ isCancelled: isCancelled, cancel: cancel }, config)) )(config) } catch (e) { bad(e); } } ); var telepath = ramda.curry(function (cancellationPolicy, bad, config) { return ramda.ifElse( function () { return cancellationPolicy.isCancelled; }, function () { return bad(new Error("Is cancelled!")); }, psychic(cancellationPolicy, bad) )(config); } ); var structureTransformation = ramda.map( ramda.pipe(function (ref) { var brains = ref.brains; var control = ref.control; return ramda.map( ramda.pipe( ensorcel.box, ramda.ap([ramda.propOr({}, BR), ramda.propOr("???", FI), ramda.propOr("", FC)]), function (ref) { var obj; var brain = ref[0]; var filepath = ref[1]; var fileContent = ref[2]; return (( obj = {}, obj[FC] = fileContent, obj.before = brain, obj.after = control[filepath].transformed, obj.filepath = filepath, obj.because = control[filepath].matched, obj )); } ) )(brains); } ) ); var tripleDash = "---\n"; var DEFAULT_YAML_OPTS = { indent: 2, noArrayIndent: false, skipInvalid: false, flowLevel: -1, sortKeys: true, noRefs: true, condenseFlow: false }; var reyaml = ramda.curry( function (config, content, head) { return tripleDash + toYAML(head, config.yamlOpts || DEFAULT_YAML_OPTS) + tripleDash + content; } ); var mapKeys = function (fn) { return ramda.pipe( ramda.toPairs, ramda.map(function (ref) { var k = ref[0]; var v = ref[1]; return [fn(k), v]; }), ramda.fromPairs ); }; var relativize = function (x) { var ind = path.resolve(x, process.cwd()); if (x && x.substr) { if (!stringInString(ind, x)) { return x } return x.substr(ind.length, Infinity) } return mapKeys(relativize)(x) }; var runTransformationWithWriter = ramda.curry(function (writer, config, xxx) { return ramda.pipe( ramda.map(function (ref) { var fileContent = ref.fileContent; var before = ref.before; var after = ref.after; var filepath = ref.filepath; return ramda.pipe( writer( filepath, reyaml(config, fileContent, ramda.mergeRight(before, after)) ), ramda.map(function () { return filepath; }) )("utf8"); } ), ramda.values, fluture.parallel(10), ramda.map(function (changed) { return toYAML( { changed: ramda.map(relativize, changed) }, config.yamlOpts || DEFAULT_YAML_OPTS ); } ) )(xxx); } ); var runTransformation = runTransformationWithWriter(torpor.writeFile); var relativeKeys = ramda.curry(function (config, obj) { return ramda.when(ramda.propOr(false, RP), function () { return ramda.map(relativize)(obj); })(config); } ); var printOut = ramda.curry(function (config, xxx) { return ramda.map( ramda.pipe( ramda.ifElse( function () { return config.telepathy; }, ramda.pipe(relativeKeys(config), function (x) { return toYAML(x, config.yamlOpts || DEFAULT_YAML_OPTS); } ), ramda.pipe( ramda.map(function (ref) { var changes = ref.after; var because = ref.because; return ({ changes: changes, because: because }); }), ramda.when( function () { return ramda.propOr(false, RP, config); }, ramda.pipe( ramda.toPairs, ramda.map(function (ref) { var kk = ref[0]; var vv = ref[1]; return [relativize(kk), vv]; }), ramda.fromPairs ) ), toYAML(ramda.__, config.yamlOpts || DEFAULT_YAML_OPTS) ) ) ) )(xxx); } ); /* istanbul ignore next */ var makeDefaultBrainwaveConfig = function () { return torpor.writeFile( /* istanbul ignore next */ path.resolve(process.cwd(), "brainwave.config.js"), "module.exports = {\n // telepathy specifies function predicates to run against the codebase \n telepathy: {\n // this will print out all files which have a \"review\" key in their frontmatter\n review: z => z && z.review\n },\n // control specifies both predicates and mutations, each a function \n control: {\n edited: [\n // *REMEMBER* to use --dryRun and it will instead tell you what _would_ be modified\n // this runs always!\n () => true,\n // it will add / update dateEdited field in the mdx files\n x => (\n x && x.stats && x.stats.ctime\n ? { dateEdited: new Date(x.stats.ctime) }\n : {}\n )\n ]\n }\n}\n", { flag: "wx", format: "utf8" } ); }; var brainwave = function (config) { var ccf = ramda.propOr("brainwave", NS, config); var initialize = ramda.propOr(false, INIT, config); /* istanbul ignore if */ if (initialize) /* istanbul ignore next */ { return ramda.pipe( makeDefaultBrainwaveConfig, ramda.map(function (z) { return z ? "Wrote default config to brainwave.config.js" : "Unable to write config file to brainwave.config.js"; } ) )() } var cc = cosmiconfig.cosmiconfig(ccf); var isCancelled = false; var cancel = function () { isCancelled = true; }; var futurize = ensorcel.futurizeWithCancel(cancel); var cosmicLoad = futurize(1, cc.load); var cosmicSearch = futurize(0, cc.search); var loadOrSearch = function (c) { return (c ? cosmicLoad(c) : cosmicSearch()); }; var mindControl = telepath({ cancel: cancel, isCancelled: isCancelled, loadOrSearch: loadOrSearch }); var dryRun = ramda.propOr(false, DR, config); var telepathyOnly = ramda.propOr(false, TK, config); return new fluture.Future(function (bad, good) { ramda.pipe( mindControl(bad), ramda.ifElse( function () { return telepathyOnly; }, ramda.map(ramda.pipe(ramda.prop("telepathy"))), structureTransformation ), ramda.ifElse( function () { return dryRun || telepathyOnly; }, printOut(config), ramda.chain(runTransformation(config)) ), fork(bad, good) )(config); return cancel }) }; var obj, obj$1; var OPTS = { boolean: "dirt".split(""), default: ( obj = {}, obj[DR] = false, obj[RP] = true, obj[GLOB] = path.join("**", "*"), obj[FT] = "{md,mdx}", obj ), alias: ( obj$1 = {}, obj$1[GLOB] = ["g"], obj$1[CC] = ["c"], obj$1[NS] = ["n"], obj$1[DR] = ["d"], obj$1[TK] = ["t"], obj$1[RP] = ["r"], obj$1[INIT] = ["i"], obj$1[FT] = ["f"], obj$1 ) }; var cli = function (args) { return ramda.pipe(function (c) { return parser(c, OPTS); }, brainwave)(args); }; fluture.fork(console.warn)(console.log)(cli(process.argv.slice(2)));