227 lines
6.0 KiB
JavaScript
227 lines
6.0 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const fs = require('fs');
|
||
|
const sections = require('section-matter');
|
||
|
const defaults = require('./lib/defaults');
|
||
|
const stringify = require('./lib/stringify');
|
||
|
const excerpt = require('./lib/excerpt');
|
||
|
const engines = require('./lib/engines');
|
||
|
const toFile = require('./lib/to-file');
|
||
|
const parse = require('./lib/parse');
|
||
|
const utils = require('./lib/utils');
|
||
|
|
||
|
/**
|
||
|
* Takes a string or object with `content` property, extracts
|
||
|
* and parses front-matter from the string, then returns an object
|
||
|
* with `data`, `content` and other [useful properties](#returned-object).
|
||
|
*
|
||
|
* ```js
|
||
|
* const matter = require('gray-matter');
|
||
|
* console.log(matter('---\ntitle: Home\n---\nOther stuff'));
|
||
|
* //=> { data: { title: 'Home'}, content: 'Other stuff' }
|
||
|
* ```
|
||
|
* @param {Object|String} `input` String, or object with `content` string
|
||
|
* @param {Object} `options`
|
||
|
* @return {Object}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
function matter(input, options) {
|
||
|
if (input === '') {
|
||
|
return { data: {}, content: input, excerpt: '', orig: input };
|
||
|
}
|
||
|
|
||
|
let file = toFile(input);
|
||
|
const cached = matter.cache[file.content];
|
||
|
|
||
|
if (!options) {
|
||
|
if (cached) {
|
||
|
file = Object.assign({}, cached);
|
||
|
file.orig = cached.orig;
|
||
|
return file;
|
||
|
}
|
||
|
|
||
|
// only cache if there are no options passed. if we cache when options
|
||
|
// are passed, we would need to also cache options values, which would
|
||
|
// negate any performance benefits of caching
|
||
|
matter.cache[file.content] = file;
|
||
|
}
|
||
|
|
||
|
return parseMatter(file, options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parse front matter
|
||
|
*/
|
||
|
|
||
|
function parseMatter(file, options) {
|
||
|
const opts = defaults(options);
|
||
|
const open = opts.delimiters[0];
|
||
|
const close = '\n' + opts.delimiters[1];
|
||
|
let str = file.content;
|
||
|
|
||
|
if (opts.language) {
|
||
|
file.language = opts.language;
|
||
|
}
|
||
|
|
||
|
// get the length of the opening delimiter
|
||
|
const openLen = open.length;
|
||
|
if (!utils.startsWith(str, open, openLen)) {
|
||
|
excerpt(file, opts);
|
||
|
return file;
|
||
|
}
|
||
|
|
||
|
// if the next character after the opening delimiter is
|
||
|
// a character from the delimiter, then it's not a front-
|
||
|
// matter delimiter
|
||
|
if (str.charAt(openLen) === open.slice(-1)) {
|
||
|
return file;
|
||
|
}
|
||
|
|
||
|
// strip the opening delimiter
|
||
|
str = str.slice(openLen);
|
||
|
const len = str.length;
|
||
|
|
||
|
// use the language defined after first delimiter, if it exists
|
||
|
const language = matter.language(str, opts);
|
||
|
if (language.name) {
|
||
|
file.language = language.name;
|
||
|
str = str.slice(language.raw.length);
|
||
|
}
|
||
|
|
||
|
// get the index of the closing delimiter
|
||
|
let closeIndex = str.indexOf(close);
|
||
|
if (closeIndex === -1) {
|
||
|
closeIndex = len;
|
||
|
}
|
||
|
|
||
|
// get the raw front-matter block
|
||
|
file.matter = str.slice(0, closeIndex);
|
||
|
|
||
|
const block = file.matter.replace(/^\s*#[^\n]+/gm, '').trim();
|
||
|
if (block === '') {
|
||
|
file.isEmpty = true;
|
||
|
file.empty = file.content;
|
||
|
file.data = {};
|
||
|
} else {
|
||
|
|
||
|
// create file.data by parsing the raw file.matter block
|
||
|
file.data = parse(file.language, file.matter, opts);
|
||
|
}
|
||
|
|
||
|
// update file.content
|
||
|
if (closeIndex === len) {
|
||
|
file.content = '';
|
||
|
} else {
|
||
|
file.content = str.slice(closeIndex + close.length);
|
||
|
if (file.content[0] === '\r') {
|
||
|
file.content = file.content.slice(1);
|
||
|
}
|
||
|
if (file.content[0] === '\n') {
|
||
|
file.content = file.content.slice(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
excerpt(file, opts);
|
||
|
|
||
|
if (opts.sections === true || typeof opts.section === 'function') {
|
||
|
sections(file, opts.section);
|
||
|
}
|
||
|
return file;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Expose engines
|
||
|
*/
|
||
|
|
||
|
matter.engines = engines;
|
||
|
|
||
|
/**
|
||
|
* Stringify an object to YAML or the specified language, and
|
||
|
* append it to the given string. By default, only YAML and JSON
|
||
|
* can be stringified. See the [engines](#engines) section to learn
|
||
|
* how to stringify other languages.
|
||
|
*
|
||
|
* ```js
|
||
|
* console.log(matter.stringify('foo bar baz', {title: 'Home'}));
|
||
|
* // results in:
|
||
|
* // ---
|
||
|
* // title: Home
|
||
|
* // ---
|
||
|
* // foo bar baz
|
||
|
* ```
|
||
|
* @param {String|Object} `file` The content string to append to stringified front-matter, or a file object with `file.content` string.
|
||
|
* @param {Object} `data` Front matter to stringify.
|
||
|
* @param {Object} `options` [Options](#options) to pass to gray-matter and [js-yaml].
|
||
|
* @return {String} Returns a string created by wrapping stringified yaml with delimiters, and appending that to the given string.
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
matter.stringify = function(file, data, options) {
|
||
|
if (typeof file === 'string') file = matter(file, options);
|
||
|
return stringify(file, data, options);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Synchronously read a file from the file system and parse
|
||
|
* front matter. Returns the same object as the [main function](#matter).
|
||
|
*
|
||
|
* ```js
|
||
|
* const file = matter.read('./content/blog-post.md');
|
||
|
* ```
|
||
|
* @param {String} `filepath` file path of the file to read.
|
||
|
* @param {Object} `options` [Options](#options) to pass to gray-matter.
|
||
|
* @return {Object} Returns [an object](#returned-object) with `data` and `content`
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
matter.read = function(filepath, options) {
|
||
|
const str = fs.readFileSync(filepath, 'utf8');
|
||
|
const file = matter(str, options);
|
||
|
file.path = filepath;
|
||
|
return file;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns true if the given `string` has front matter.
|
||
|
* @param {String} `string`
|
||
|
* @param {Object} `options`
|
||
|
* @return {Boolean} True if front matter exists.
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
matter.test = function(str, options) {
|
||
|
return utils.startsWith(str, defaults(options).delimiters[0]);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Detect the language to use, if one is defined after the
|
||
|
* first front-matter delimiter.
|
||
|
* @param {String} `string`
|
||
|
* @param {Object} `options`
|
||
|
* @return {Object} Object with `raw` (actual language string), and `name`, the language with whitespace trimmed
|
||
|
*/
|
||
|
|
||
|
matter.language = function(str, options) {
|
||
|
const opts = defaults(options);
|
||
|
const open = opts.delimiters[0];
|
||
|
|
||
|
if (matter.test(str)) {
|
||
|
str = str.slice(open.length);
|
||
|
}
|
||
|
|
||
|
const language = str.slice(0, str.search(/\r?\n/));
|
||
|
return {
|
||
|
raw: language,
|
||
|
name: language ? language.trim() : ''
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Expose `matter`
|
||
|
*/
|
||
|
|
||
|
matter.cache = {};
|
||
|
matter.clearCache = () => (matter.cache = {});
|
||
|
module.exports = matter;
|