l0bsterssg

node.js static responsive blog post generator
Log | Files | Refs | README

index.js (6130B)


      1 'use strict';
      2 
      3 const fs = require('fs');
      4 const sections = require('section-matter');
      5 const defaults = require('./lib/defaults');
      6 const stringify = require('./lib/stringify');
      7 const excerpt = require('./lib/excerpt');
      8 const engines = require('./lib/engines');
      9 const toFile = require('./lib/to-file');
     10 const parse = require('./lib/parse');
     11 const utils = require('./lib/utils');
     12 
     13 /**
     14  * Takes a string or object with `content` property, extracts
     15  * and parses front-matter from the string, then returns an object
     16  * with `data`, `content` and other [useful properties](#returned-object).
     17  *
     18  * ```js
     19  * const matter = require('gray-matter');
     20  * console.log(matter('---\ntitle: Home\n---\nOther stuff'));
     21  * //=> { data: { title: 'Home'}, content: 'Other stuff' }
     22  * ```
     23  * @param {Object|String} `input` String, or object with `content` string
     24  * @param {Object} `options`
     25  * @return {Object}
     26  * @api public
     27  */
     28 
     29 function matter(input, options) {
     30   if (input === '') {
     31     return { data: {}, content: input, excerpt: '', orig: input };
     32   }
     33 
     34   let file = toFile(input);
     35   const cached = matter.cache[file.content];
     36 
     37   if (!options) {
     38     if (cached) {
     39       file = Object.assign({}, cached);
     40       file.orig = cached.orig;
     41       return file;
     42     }
     43 
     44     // only cache if there are no options passed. if we cache when options
     45     // are passed, we would need to also cache options values, which would
     46     // negate any performance benefits of caching
     47     matter.cache[file.content] = file;
     48   }
     49 
     50   return parseMatter(file, options);
     51 }
     52 
     53 /**
     54  * Parse front matter
     55  */
     56 
     57 function parseMatter(file, options) {
     58   const opts = defaults(options);
     59   const open = opts.delimiters[0];
     60   const close = '\n' + opts.delimiters[1];
     61   let str = file.content;
     62 
     63   if (opts.language) {
     64     file.language = opts.language;
     65   }
     66 
     67   // get the length of the opening delimiter
     68   const openLen = open.length;
     69   if (!utils.startsWith(str, open, openLen)) {
     70     excerpt(file, opts);
     71     return file;
     72   }
     73 
     74   // if the next character after the opening delimiter is
     75   // a character from the delimiter, then it's not a front-
     76   // matter delimiter
     77   if (str.charAt(openLen) === open.slice(-1)) {
     78     return file;
     79   }
     80 
     81   // strip the opening delimiter
     82   str = str.slice(openLen);
     83   const len = str.length;
     84 
     85   // use the language defined after first delimiter, if it exists
     86   const language = matter.language(str, opts);
     87   if (language.name) {
     88     file.language = language.name;
     89     str = str.slice(language.raw.length);
     90   }
     91 
     92   // get the index of the closing delimiter
     93   let closeIndex = str.indexOf(close);
     94   if (closeIndex === -1) {
     95     closeIndex = len;
     96   }
     97 
     98   // get the raw front-matter block
     99   file.matter = str.slice(0, closeIndex);
    100 
    101   const block = file.matter.replace(/^\s*#[^\n]+/gm, '').trim();
    102   if (block === '') {
    103     file.isEmpty = true;
    104     file.empty = file.content;
    105     file.data = {};
    106   } else {
    107 
    108     // create file.data by parsing the raw file.matter block
    109     file.data = parse(file.language, file.matter, opts);
    110   }
    111 
    112   // update file.content
    113   if (closeIndex === len) {
    114     file.content = '';
    115   } else {
    116     file.content = str.slice(closeIndex + close.length);
    117     if (file.content[0] === '\r') {
    118       file.content = file.content.slice(1);
    119     }
    120     if (file.content[0] === '\n') {
    121       file.content = file.content.slice(1);
    122     }
    123   }
    124 
    125   excerpt(file, opts);
    126 
    127   if (opts.sections === true || typeof opts.section === 'function') {
    128     sections(file, opts.section);
    129   }
    130   return file;
    131 }
    132 
    133 /**
    134  * Expose engines
    135  */
    136 
    137 matter.engines = engines;
    138 
    139 /**
    140  * Stringify an object to YAML or the specified language, and
    141  * append it to the given string. By default, only YAML and JSON
    142  * can be stringified. See the [engines](#engines) section to learn
    143  * how to stringify other languages.
    144  *
    145  * ```js
    146  * console.log(matter.stringify('foo bar baz', {title: 'Home'}));
    147  * // results in:
    148  * // ---
    149  * // title: Home
    150  * // ---
    151  * // foo bar baz
    152  * ```
    153  * @param {String|Object} `file` The content string to append to stringified front-matter, or a file object with `file.content` string.
    154  * @param {Object} `data` Front matter to stringify.
    155  * @param {Object} `options` [Options](#options) to pass to gray-matter and [js-yaml].
    156  * @return {String} Returns a string created by wrapping stringified yaml with delimiters, and appending that to the given string.
    157  * @api public
    158  */
    159 
    160 matter.stringify = function(file, data, options) {
    161   if (typeof file === 'string') file = matter(file, options);
    162   return stringify(file, data, options);
    163 };
    164 
    165 /**
    166  * Synchronously read a file from the file system and parse
    167  * front matter. Returns the same object as the [main function](#matter).
    168  *
    169  * ```js
    170  * const file = matter.read('./content/blog-post.md');
    171  * ```
    172  * @param {String} `filepath` file path of the file to read.
    173  * @param {Object} `options` [Options](#options) to pass to gray-matter.
    174  * @return {Object} Returns [an object](#returned-object) with `data` and `content`
    175  * @api public
    176  */
    177 
    178 matter.read = function(filepath, options) {
    179   const str = fs.readFileSync(filepath, 'utf8');
    180   const file = matter(str, options);
    181   file.path = filepath;
    182   return file;
    183 };
    184 
    185 /**
    186  * Returns true if the given `string` has front matter.
    187  * @param  {String} `string`
    188  * @param  {Object} `options`
    189  * @return {Boolean} True if front matter exists.
    190  * @api public
    191  */
    192 
    193 matter.test = function(str, options) {
    194   return utils.startsWith(str, defaults(options).delimiters[0]);
    195 };
    196 
    197 /**
    198  * Detect the language to use, if one is defined after the
    199  * first front-matter delimiter.
    200  * @param  {String} `string`
    201  * @param  {Object} `options`
    202  * @return {Object} Object with `raw` (actual language string), and `name`, the language with whitespace trimmed
    203  */
    204 
    205 matter.language = function(str, options) {
    206   const opts = defaults(options);
    207   const open = opts.delimiters[0];
    208 
    209   if (matter.test(str)) {
    210     str = str.slice(open.length);
    211   }
    212 
    213   const language = str.slice(0, str.search(/\r?\n/));
    214   return {
    215     raw: language,
    216     name: language ? language.trim() : ''
    217   };
    218 };
    219 
    220 /**
    221  * Expose `matter`
    222  */
    223 
    224 matter.cache = {};
    225 matter.clearCache = () => (matter.cache = {});
    226 module.exports = matter;