markdown.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. "use strict";
  2. //=============================================================================
  3. // Exports a getMarkdown() and patches pipeline parts with a getMarkdown()
  4. //=============================================================================
  5. var pipeline = require("../pipeline"),
  6. matcher = require("../matcher");
  7. var MAX_LINE_LEN = 140,
  8. INDENT_STR = " ";
  9. function getIndentStr(level) {
  10. var indent = "";
  11. while (level-- > 0) {
  12. indent += INDENT_STR;
  13. }
  14. return indent;
  15. }
  16. function getSingleLineJsonStr(obj){
  17. return JSON.stringify(obj, 0, 1)
  18. .replace(/\n\s*/g, " ");
  19. }
  20. function getMultiLineJsonStr(obj) {
  21. return JSON.stringify(obj, 0, 4);
  22. }
  23. function toListItem(str, i) {
  24. return str.replace(/(^\s*)/, "$&" + (i || 0) + ". ");
  25. }
  26. (function patchMatcher(matcher) {
  27. //NOTE: this area especially needs finished
  28. // base implementation for matcher MatchExpression instances; just calls `#debugString()`
  29. matcher.MatchExpression.prototype.getMarkdown = function(level) {
  30. return this.debugString(level);
  31. };
  32. matcher.ComparisonMatchExpression.prototype.getMarkdown = function(level) {
  33. var retStr = this._debugAddSpace(level) + "`" + this.path() + "` ";
  34. switch (this._matchType) {
  35. case "LT": retStr += "$lt"; break;
  36. case "LTE": retStr += "$lte"; break;
  37. case "EQ": retStr += "=="; break;
  38. case "GT": retStr += "$gt"; break;
  39. case "GTE": retStr += "$gte"; break;
  40. default: throw new Error("Unknown comparison!");
  41. }
  42. retStr += " " + (this._rhs !== undefined ? "`" + JSON.stringify(this._rhs) + "`" : "?");
  43. if (this.getTag()) {
  44. retStr += this.getTag().debugString();
  45. }
  46. return retStr + "\n";
  47. };
  48. matcher.ListOfMatchExpression.prototype.getMarkdown = function(level) {
  49. var str = this._debugAddSpace(level);
  50. switch (this._matchType) {
  51. case "AND": str += "all of:\n"; break;
  52. case "OR": str += "one of:\n"; break;
  53. case "NOR": str += "none of:\n"; break;
  54. case "NOT": str += "not all of:\n"; break;
  55. default: throw new Error("Unknown match type!");
  56. }
  57. var exps = this._expressions;
  58. if (exps.length === 0 && this._exp) exps = [this._exp];
  59. for (var i = 0; i < exps.length; i++) {
  60. str += toListItem(exps[i].getMarkdown(level + 1), i);
  61. }
  62. return str;
  63. };
  64. matcher.ExistsMatchExpression.prototype.getMarkdown = function(level) {
  65. return this._debugAddSpace(level) +
  66. "`" + this.path() + "` exists" +
  67. (this.getTag() ? " " + this.getTag().debugString() : "") +
  68. "\n";
  69. };
  70. matcher.InMatchExpression.prototype.getMarkdown = function(level) {
  71. return this._debugAddSpace(level) +
  72. "`" + this.path() + "` in " +
  73. "`" + this._arrayEntries.debugString(level) + "`" +
  74. (this.getTag() ? this.getTag().debugString() : "") +
  75. "\n";
  76. };
  77. })(matcher);
  78. (function patchPipeline(pipeline) {
  79. // (function patchPipelineAccumulators(accumulators) {
  80. // // done in the $group handler for now
  81. // })(pipeline.accumulators);
  82. (function patchPipelineDocumentSources(documentSources) {
  83. //TODO: GeoNearDocumentSource
  84. //TODO: test single value for `_id` like `null` or `"$foo.bar.path"`
  85. documentSources.GroupDocumentSource.prototype.getMarkdown = function(level) {
  86. var i, l;
  87. var indent = getIndentStr(level),
  88. str = indent + "group docs into buckets:\n";
  89. if (this.idFieldNames.length === 0) {
  90. str += indent + INDENT_STR + "0. by `_id` which is from " + this.idExpressions[0].getMarkdown(0);
  91. } else {
  92. str += indent + INDENT_STR + "0. by `_id` which is from:\n";
  93. for (i = 0, l = this.idExpressions.length; i < l; i++) {
  94. var idKey = this.idFieldNames[i],
  95. idExp = this.idExpressions[i];
  96. str += indent + INDENT_STR + INDENT_STR + i + ". `" + idKey + "` from";
  97. var idExpStr = idExp.getMarkdown(level + 3).trimRight(),
  98. idExpStrLines = idExpStr.split("\n");
  99. if (idExpStrLines.length === 1) {
  100. str += " " + idExpStr.trimLeft() + "\n";
  101. } else {
  102. str += toListItem(idExp.getMarkdown(level + 3), i);
  103. }
  104. }
  105. }
  106. if (this.fieldNames.length > 0) {
  107. str += indent + INDENT_STR + "1. for each bucket keep:\n";
  108. for (i = 0, l = this.fieldNames.length; i < l; i++) {
  109. var key = this.fieldNames[i],
  110. acc = this.accumulatorFactories[i](),
  111. accName = acc.getOpName().replace(/^\$/, ""),
  112. accNameAliases = {
  113. addToSet: "unique set",
  114. push: "array",
  115. },
  116. exp = this.expressions[i];
  117. str += indent + INDENT_STR + INDENT_STR + i + ". `" + key + "` as " + (accNameAliases[accName] || accName) + " of";
  118. if (!exp.expressions) {
  119. str += " " + exp.getMarkdown(level + 2).trimLeft();
  120. } else {
  121. str += "\n" + toListItem(exp.getMarkdown(level + 3));
  122. }
  123. }
  124. }
  125. return str;
  126. };
  127. documentSources.LimitDocumentSource.prototype.getMarkdown = function(level) {
  128. return getIndentStr(level) + "limit to only `" + this.limit + "` output docs";
  129. };
  130. documentSources.MatchDocumentSource.prototype.getMarkdown = function(level) {
  131. var str = "",
  132. indent = getIndentStr(level),
  133. exp = this.matcher._expression;
  134. str += indent + "find docs matching:\n";
  135. if (exp.expressions) {
  136. str += exp.getMarkdown(level + 1);
  137. } else {
  138. str += indent + INDENT_STR + "0. " + exp.getMarkdown(level + 1).trimLeft();
  139. }
  140. return str;
  141. };
  142. //TODO: OutDocumentSource
  143. documentSources.ProjectDocumentSource.prototype.getMarkdown = function(level) {
  144. return "for each doc " + this.OE.getMarkdown(level);
  145. };
  146. //TODO: RedaactDocumentSource
  147. documentSources.SkipDocumentSource.prototype.getMarkdown = function(level) {
  148. return getIndentStr(level) + "skip the next `" + this.limit + "` output docs";
  149. };
  150. documentSources.SortDocumentSource.prototype.getMarkdown = function(level) {
  151. var indent = getIndentStr(level),
  152. str = indent + "sort docs by:\n";
  153. for (var i = 0, l = this.vSortKey.length; i < l; i++) {
  154. var orderStr = this.vAscending[i] ? "in order" : "in reverse order";
  155. str += indent + INDENT_STR + i + ". " + this.vSortKey[i].getMarkdown().trimRight() + ", " + orderStr;
  156. }
  157. return str;
  158. };
  159. documentSources.UnwindDocumentSource.prototype.getMarkdown = function(level) {
  160. return getIndentStr(level) + "unwind docs by using each item in `" +
  161. this._unwindPath.getPath(false) + "` to create a copy that has the list item rather than the list";
  162. };
  163. })(pipeline.documentSources);
  164. (function patchPipelineExpressions(expressions) {
  165. // base implementation for expression Expression instances; just calls `#serialize()`
  166. expressions.Expression.prototype.getMarkdown = function(level) {
  167. var obj = this.serialize(),
  168. objStr = typeof obj === "string" ? obj : getSingleLineJsonStr(obj);
  169. return getIndentStr(level) + "`" + objStr + "`\n";
  170. };
  171. expressions.AddExpression.prototype.getMarkdown = function(level) {
  172. var str = "",
  173. indent = getIndentStr(level),
  174. opStrs = this.operands.map(function(op) {
  175. return op.getMarkdown(level + 1).trimRight();
  176. }),
  177. isOneLine = opStrs.length <= 2 && opStrs.every(function(opStr, i) {
  178. return opStr.indexOf("\n") === -1;
  179. });
  180. if (isOneLine && indent.length + opStrs.join(" + ").length < MAX_LINE_LEN) {
  181. str += indent + "( " + opStrs.map(Function.prototype.call.bind(String.prototype.trimLeft)).join(" + ") + " )\n";
  182. } else {
  183. str += indent + "add:\n";
  184. for (var i = 0, l = this.operands.length; i < l; i++) {
  185. str += toListItem(opStrs[i], i) + "\n";
  186. }
  187. }
  188. return str;
  189. };
  190. //TODO: $allElementsTrue
  191. expressions.AndExpression.prototype.getMarkdown = function(level) {
  192. var str = getIndentStr(level) + "all of:\n",
  193. ops = this.operands;
  194. for (var i = 0; i < ops.length; i++) {
  195. var opStr = ops[i].getMarkdown(level + 1).trimRight();
  196. str += toListItem(opStr, i) + "\n";
  197. }
  198. return str;
  199. };
  200. //TODO: $anyElementTrue
  201. //TODO: $coerceToBool
  202. expressions.CompareExpression.prototype.getMarkdown = function(level) {
  203. var str = "",
  204. indent = getIndentStr(level),
  205. opStrs = this.operands.map(function(op) {
  206. return op.getMarkdown(level + 1).trimRight();
  207. }),
  208. isOneLine = opStrs.length <= 2 && opStrs.every(function(opStr, i) {
  209. return opStr.indexOf("\n") === -1;
  210. });
  211. if (isOneLine && indent.length + opStrs.join("").length < MAX_LINE_LEN) {
  212. var cmpOpAliases = {
  213. $eq: "==",
  214. $ne: "!=",
  215. },
  216. cmpOpAlias = cmpOpAliases[this.cmpOp],
  217. cmpOpStr = cmpOpAlias ? cmpOpAlias : "`" + this.cmpOp + "`";
  218. if (opStrs.length === 1) str += indent + cmpOpStr + " " + opStrs[0].trim() + "\n";
  219. else str += indent + opStrs[0].trim() + " " + cmpOpStr + " " + opStrs[1].trim() + "\n";
  220. } else {
  221. str += indent + "is `" + this.cmpOp + "`\n";
  222. for (var i = 0, l = this.operands.length; i < l; i++) {
  223. str += toListItem(opStrs[i], i) + "\n";
  224. }
  225. }
  226. return str;
  227. };
  228. expressions.ConcatExpression.prototype.getMarkdown = function(level) {
  229. var str = getIndentStr(level) + "concatenate:\n",
  230. ops = this.operands;
  231. for (var i = 0; i < ops.length; i++) {
  232. var opStr = ops[i].getMarkdown(level + 1).trimRight();
  233. str += toListItem(opStr, i) + "\n";
  234. }
  235. return str;
  236. };
  237. expressions.CondExpression.prototype.getMarkdown = function(level) {
  238. var indent = getIndentStr(level),
  239. str = indent + "conditional:\n";
  240. var names = ["if", "then", "else"];
  241. names.forEach(function(name, i) {
  242. str += indent + INDENT_STR + i + ". " + name;
  243. var opDocStr = this.operands[i].getMarkdown(level + 2).trimRight();
  244. if (opDocStr.indexOf("\n") === -1 && opDocStr.length < MAX_LINE_LEN) { // is one line
  245. str += " " + opDocStr.trimLeft() + "\n";
  246. } else {
  247. str += ":\n" + toListItem(opDocStr) + "\n";
  248. }
  249. }, this);
  250. return str;
  251. };
  252. expressions.ConstantExpression.prototype.getMarkdown = function(level) {
  253. return getIndentStr(level) + "the constant `" + JSON.stringify(this.value) + "`\n";
  254. };
  255. //TODO: $dayOfMonth
  256. //TODO: $dayOfWeek
  257. //TODO: $dayOfYear
  258. expressions.DivideExpression.prototype.getMarkdown = function(level) {
  259. var indent = getIndentStr(level),
  260. str = indent + "divide:\n";
  261. var names = ["numerator", "denominator"];
  262. names.forEach(function(name, i) {
  263. str += indent + INDENT_STR + i + ". " + name;
  264. var opDocStr = this.operands[i].getMarkdown(level + 2).trimRight();
  265. if (opDocStr.indexOf("\n") === -1 && opDocStr.length < MAX_LINE_LEN) { // is one line
  266. str += " is " + opDocStr.trimLeft() + "\n";
  267. } else {
  268. str += ":\n" + toListItem(opDocStr) + "\n";
  269. }
  270. }, this);
  271. return str;
  272. };
  273. expressions.FieldPathExpression.prototype.getMarkdown = function(level) {
  274. var str = "",
  275. indent = getIndentStr(level),
  276. fp = this._fieldPath;
  277. if ((fp.fieldNames[0] === "CURRENT" || fp.fieldNames[0] === "ROOT") && fp.fieldNames.length > 1) {
  278. str += fp.tail().getPath(false);
  279. } else {
  280. str += "$$" + fp.getPath(false);
  281. }
  282. return indent + "`" + str + "`\n";
  283. };
  284. //TODO: $hour
  285. expressions.IfNullExpression.prototype.getMarkdown = function(level) {
  286. var str = getIndentStr(level);
  287. var opExpDocStr = this.operands[0].getMarkdown(0).trimRight(),
  288. opOtherExpDocStr = this.operands[1].getMarkdown(0).trimRight();
  289. str += opExpDocStr + " if not null or fallback to " + opOtherExpDocStr;
  290. if (str.indexOf("\n") !== -1) throw new Error("TODO: fix multiline $ifNull output");
  291. str += "\n";
  292. return str;
  293. };
  294. //TODO: $let
  295. //TODO: $map
  296. //TODO: $millisecond
  297. //TODO: $minute
  298. //TODO: $mod
  299. //TODO: $month
  300. //TODO: $multiply
  301. expressions.NotExpression.prototype.getMarkdown = function(level) {
  302. var indent = getIndentStr(level),
  303. str = indent + "not:\n";
  304. str += toListItem(this.operands[0].getMarkdown(level + 1));
  305. return str;
  306. };
  307. expressions.ObjectExpression.prototype.getMarkdown = function(level) {
  308. var indent = getIndentStr(level),
  309. exps = this._expressions,
  310. keys = this._order;
  311. if (!this.excludeId && keys.indexOf("_id") === -1) keys.unshift("_id");
  312. if (keys.length === 0) return indent + "empty object\n";
  313. var str = indent + "build object:\n";
  314. for (var i = 0, l = keys.length; i < l; i++) {
  315. var key = keys[i],
  316. exp = exps[key];
  317. str += indent + INDENT_STR + i + ". `" + key + "` from";
  318. if (exp) {
  319. var expStr = exp.getMarkdown(level + 2).trimRight();
  320. if (expStr.indexOf("\n") === -1) { // is one line
  321. str += " " + expStr.trimLeft() + "\n";
  322. } else {
  323. str += "\n" + toListItem(expStr) + "\n";
  324. }
  325. } else {
  326. str += " `" + key + "` (unchanged)\n";
  327. }
  328. }
  329. return str;
  330. };
  331. expressions.OrExpression.prototype.getMarkdown = function(level) {
  332. var str = getIndentStr(level) + "one of:\n",
  333. ops = this.operands;
  334. for (var i = 0; i < ops.length; i++) {
  335. var opStr = ops[i].getMarkdown(level + 1).trimRight();
  336. str += toListItem(opStr, i) + "\n";
  337. }
  338. return str;
  339. };
  340. //TODO: $second
  341. //TODO: $setDifference
  342. //TODO: $setEquals
  343. //TODO: $setIntersection
  344. //TODO: $setIsSubset
  345. //TODO: $setUnion
  346. //TODO: $size
  347. //TODO: $strcasecmp
  348. expressions.SubstrExpression.prototype.getMarkdown = function(level) {
  349. var str = "",
  350. indent = getIndentStr(level);
  351. str += indent + "a substring";
  352. var opStringDocStr = this.operands[0].getMarkdown(0).trimRight();
  353. str += " from " + opStringDocStr.trimLeft();
  354. var opStartDocStr = this.operands[1].getMarkdown(0).trimRight();
  355. str += ", starting position is at " + opStartDocStr.trimLeft();
  356. var opLengthDocStr = this.operands[2].getMarkdown(0).trimRight();
  357. str += ", length is " + opLengthDocStr.trimLeft();
  358. if (str.indexOf("\n") !== -1) throw new Error("TODO: fix multiline $substr output");
  359. str += "\n";
  360. return str;
  361. };
  362. expressions.SubtractExpression.prototype.getMarkdown = function(level) {
  363. var indent = getIndentStr(level),
  364. str = indent + "subtract:\n";
  365. var names = ["minuend", "subtrahend"];
  366. names.forEach(function(name, i) {
  367. str += indent + INDENT_STR + i + ". " + name;
  368. var opDocStr = this.operands[i].getMarkdown(level + 2).trimRight();
  369. if (opDocStr.indexOf("\n") === -1 && opDocStr.length < MAX_LINE_LEN) { // is one line
  370. str += " is " + opDocStr.trimLeft() + "\n";
  371. } else {
  372. str += ":\n" + toListItem(opDocStr) + "\n";
  373. }
  374. }, this);
  375. return str;
  376. };
  377. //TODO: $toLower
  378. //TODO: $toUpper
  379. //TODO: $week
  380. //TODO: $year
  381. })(pipeline.expressions);
  382. })(pipeline);
  383. module.exports = {
  384. INDENT_STR: INDENT_STR,
  385. getIndentStr: getIndentStr,
  386. getSingleLineJsonStr: getSingleLineJsonStr,
  387. getMultiLineJsonStr: getMultiLineJsonStr,
  388. getMarkdown: function getMarkdown(pipelineJson) {
  389. var out = "",
  390. docSrcs = pipelineJson;
  391. if (!(docSrcs[0] instanceof pipeline.documentSources.DocumentSource)) {
  392. docSrcs = pipeline.Pipeline.parseDocumentSources(pipelineJson, {});
  393. }
  394. for (var i = 0, l = docSrcs.length; i < l; i++) {
  395. var docSrc = docSrcs[i];
  396. out += i + ". " + docSrc.getMarkdown(0).trimRight() + "\n";
  397. }
  398. return out;
  399. },
  400. };