front_matter.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. 'use strict';
  2. const yaml = require('js-yaml');
  3. const rPrefixSep = /^(-{3,}|;{3,})/;
  4. const rFrontMatter = /^(-{3,}|;{3,})\n([\s\S]+?)\n\1\n?([\s\S]*)/;
  5. const rFrontMatterNew = /^([\s\S]+?)\n(-{3,}|;{3,})\n?([\s\S]*)/;
  6. function split(str) {
  7. if (typeof str !== 'string') throw new TypeError('str is required!');
  8. const matchOld = str.match(rFrontMatter);
  9. if (matchOld) {
  10. return {
  11. data: matchOld[2],
  12. content: matchOld[3] || '',
  13. separator: matchOld[1],
  14. prefixSeparator: true
  15. };
  16. }
  17. if (rPrefixSep.test(str)) return { content: str };
  18. const matchNew = str.match(rFrontMatterNew);
  19. if (matchNew) {
  20. return {
  21. data: matchNew[1],
  22. content: matchNew[3] || '',
  23. separator: matchNew[2],
  24. prefixSeparator: false
  25. };
  26. }
  27. return { content: str };
  28. }
  29. function parse(str, options) {
  30. if (typeof str !== 'string') throw new TypeError('str is required!');
  31. const splitData = split(str);
  32. const raw = splitData.data;
  33. if (!raw) return { _content: str };
  34. let data;
  35. if (splitData.separator.startsWith(';')) {
  36. data = parseJSON(raw);
  37. } else {
  38. data = parseYAML(raw, options);
  39. }
  40. if (!data) return { _content: str };
  41. // Convert timezone
  42. Object.keys(data).forEach(key => {
  43. const item = data[key];
  44. if (item instanceof Date) {
  45. data[key] = new Date(item.getTime() + (item.getTimezoneOffset() * 60 * 1000));
  46. }
  47. });
  48. data._content = splitData.content;
  49. return data;
  50. }
  51. function parseYAML(str, options) {
  52. const result = yaml.load(escapeYAML(str), options);
  53. if (typeof result !== 'object') return;
  54. return result;
  55. }
  56. function parseJSON(str) {
  57. try {
  58. return JSON.parse(`{${str}}`);
  59. } catch (err) {
  60. return; // eslint-disable-line
  61. }
  62. }
  63. function escapeYAML(str) {
  64. if (typeof str !== 'string') throw new TypeError('str is required!');
  65. return str.replace(/\n(\t+)/g, (match, tabs) => {
  66. let result = '\n';
  67. for (let i = 0, len = tabs.length; i < len; i++) {
  68. result += ' ';
  69. }
  70. return result;
  71. });
  72. }
  73. function stringify(obj, options = {}) {
  74. if (!obj) throw new TypeError('obj is required!');
  75. const { _content: content = '' } = obj;
  76. delete obj._content;
  77. if (!Object.keys(obj).length) return content;
  78. const { mode, prefixSeparator } = options;
  79. const separator = options.separator || (mode === 'json' ? ';;;' : '---');
  80. let result = '';
  81. if (prefixSeparator) result += `${separator}\n`;
  82. if (mode === 'json') {
  83. result += stringifyJSON(obj);
  84. } else {
  85. result += stringifyYAML(obj, options);
  86. }
  87. result += `${separator}\n${content}`;
  88. return result;
  89. }
  90. function stringifyYAML(obj, options) {
  91. const keys = Object.keys(obj);
  92. const data = {};
  93. const nullKeys = [];
  94. const dateKeys = [];
  95. let key, value, i, len;
  96. for (i = 0, len = keys.length; i < len; i++) {
  97. key = keys[i];
  98. value = obj[key];
  99. if (value == null) {
  100. nullKeys.push(key);
  101. } else if (value instanceof Date) {
  102. dateKeys.push(key);
  103. } else {
  104. data[key] = value;
  105. }
  106. }
  107. let result = yaml.dump(data, options);
  108. if (dateKeys.length) {
  109. for (i = 0, len = dateKeys.length; i < len; i++) {
  110. key = dateKeys[i];
  111. result += `${key}: ${formatDate(obj[key])}\n`;
  112. }
  113. }
  114. if (nullKeys.length) {
  115. for (i = 0, len = nullKeys.length; i < len; i++) {
  116. result += `${nullKeys[i]}:\n`;
  117. }
  118. }
  119. return result;
  120. }
  121. function stringifyJSON(obj) {
  122. return JSON.stringify(obj, null, ' ')
  123. // Remove indention
  124. .replace(/\n {2}/g, () => '\n')
  125. // Remove prefixing and trailing braces
  126. .replace(/^{\n|}$/g, '');
  127. }
  128. function doubleDigit(num) {
  129. return num.toString().padStart(2, '0');
  130. }
  131. function formatDate(date) {
  132. return `${date.getFullYear()}-${doubleDigit(date.getMonth() + 1)}-${doubleDigit(date.getDate())} ${doubleDigit(date.getHours())}:${doubleDigit(date.getMinutes())}:${doubleDigit(date.getSeconds())}`;
  133. }
  134. module.exports = {
  135. parse,
  136. split,
  137. escape: escapeYAML,
  138. stringify
  139. };