brainwave
Version:
Programmatically update frontmatter without leaving JS
447 lines (393 loc) • 14.1 kB
JavaScript
;
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)));