grunt-pages
Advanced tools
Comparing version 0.7.2 to 0.8.0
{ | ||
"name": "grunt-pages", | ||
"version": "0.7.2", | ||
"version": "0.8.0", | ||
"description": "Grunt task to create pages using markdown and templates", | ||
@@ -11,4 +11,3 @@ "main": "Gruntfile.js", | ||
"pygmentize-bundled": "~2.1.0", | ||
"marked": "git://github.com/ChrisWren/marked.git#40b3f286203ec7013e81d2c6ce03fabf8a4129b0", | ||
"js-yaml": "~2.1.0", | ||
"marked": "git://github.com/ChrisWren/marked.git#listener", | ||
"rss": "~0.2.0", | ||
@@ -23,3 +22,3 @@ "lodash": "~2.0.0" | ||
"grunt-contrib-watch": "~0.4.4", | ||
"grunt-concurrent": "~0.2.0", | ||
"grunt-concurrent": "~0.3.1", | ||
"matchdep": "~0.1.2", | ||
@@ -31,3 +30,5 @@ "grunt-contrib-copy": "~0.4.1", | ||
"grunt-release": "~0.4.0", | ||
"grunt-mdlint": "0.0.0" | ||
"grunt-mdlint": "0.0.0", | ||
"grunt-node-inspector": "~0.1.0", | ||
"grunt-shell": "~0.4.0" | ||
}, | ||
@@ -34,0 +35,0 @@ "peerDependencies": { |
@@ -9,7 +9,6 @@ # grunt-pages | ||
## Prerequisites | ||
This Grunt task uses [pygments](#syntax-highlighting), which relies on [Python](http://www.python.org/getit/) version `2.7.x`. | ||
> Please note that grunt-pages will **not** run on the `3.x` branch of Python | ||
This Grunt task uses [pygments](http://pygments.org/) which requires [Python](http://www.python.org/getit/) to be installed. | ||
## Getting Started | ||
If you haven't used grunt before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a gruntfile as well as install and use grunt plugins. Once you're familiar with that process, install this plugin with this command: | ||
If you haven't used Grunt before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, install this plugin with this command: | ||
```shell | ||
@@ -26,2 +25,3 @@ npm install grunt-pages --save-dev | ||
## Documentation | ||
### Sample config | ||
@@ -35,3 +35,3 @@ Here is a sample config to create a blog using grunt-pages: | ||
posts: { | ||
src: 'src/posts', | ||
src: 'posts', | ||
dest: 'dev', | ||
@@ -47,13 +47,4 @@ layout: 'src/layouts/post.jade', | ||
#### Post Format | ||
Posts are written in markdown and include a metadata section at the top to provide information about the post. There are two accepted metadata formats, YAML and a JavaScript object. Here is a YAML example: | ||
```yaml | ||
---- | ||
title: The Versace Sofa Thesis Vol. I | ||
date: 2010-10-4 | ||
author: Pusha T | ||
---- | ||
``` | ||
The YAML data is parsed into a JavaScript object and passed to the post's template to be rendered. Check out [js-yaml's site](http://nodeca.github.io/js-yaml/) to learn more about how the translation works. | ||
Posts are written in markdown and include a metadata section at the top to provide information about the post. The metadata format is a JavaScript Object, and here is an example: | ||
Here is a JavaScript object example: | ||
```js | ||
@@ -66,11 +57,13 @@ { | ||
``` | ||
The only property that is not interpreted literally is the `date`. It is used as a `dateString` when constructing a [Date object](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date) in JavaScript, and must be in a [parseable format](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse). For both YAML and JavaScript object metadata, the JavaScript `Date` object is available in the layout. | ||
The only property that is not interpreted literally is the `date`. It is used as a `dateString` when constructing a [Date object](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date) in JavaScript, and must be in a [parseable format](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse). | ||
#### Syntax Highlighting | ||
For adding code to your posts, grunt-pages has [GitHub flavoured markdown](https://help.github.com/articles/github-flavored-markdown) syntax highlighting using [pygments](http://pygments.org/). | ||
For adding code to your posts, grunt-pages has [GitHub flavoured markdown](https://help.github.com/articles/github-flavored-markdown) and syntax highlighting using [pygments](http://pygments.org/). | ||
#### Draft Posts | ||
To make a post a draft, simply prefix its filename with a `_`. These posts will not be rendered or available in list pages. | ||
To make a post a draft when deploying your site, simply prefix its filename with a `_`. These posts will not be rendered or available in list pages. | ||
### Required properties | ||
#### src | ||
@@ -91,6 +84,8 @@ Type: `String` | ||
**Note: you can run grunt-pages with the --debug flag set to see all the data passed to templates for rendering** | ||
#### url | ||
Type: `String` | ||
The url of each post. The url string takes variables as parameters using the `:variable` syntax. Variable(s) specified in the url are required in each post's metadata. Urls ending with a trailing `/` will generate posts as index.html files inside of the url's folder. | ||
The URL of each post. The URL string takes variables as parameters using the `:variable` syntax. Variables specified in the URL are required in each post's metadata. URLs ending with a trailing `/` will generate posts as index.html files inside of the URL's folder. | ||
@@ -102,3 +97,3 @@ ### Options | ||
The folder where the ejs or jade source pages of your website are located. These pages have access to each post's `content` and metadata properties via a `posts` array. Additionally, pages have access to their own filename(without extension) via the `currentPage` variable. All of the files in this folder are generated in the `dest` folder maintaining the same relative path from `pageSrc`. | ||
The folder where the ejs or jade source pages of your website are located. These pages have access to each post's `content` and metadata properties via a `posts` array. Additionally, pages have access to their own filename(without extension) via the `currentPage` variable to optionally display it differently when linking to pages. All of the files in this folder are generated in the `dest` folder maintaining the same relative path from `pageSrc`. | ||
@@ -138,2 +133,3 @@ #### data | ||
``` | ||
A function that takes a `url` as a parameter and returns a formatted url string. This is primarily used to remove special characters and replace whitespace. | ||
@@ -161,3 +157,3 @@ | ||
posts: { | ||
src: 'src/posts', | ||
src: 'posts', | ||
dest: 'dev', | ||
@@ -211,3 +207,3 @@ layout: 'src/layouts/post.jade', | ||
posts: { | ||
src: 'src/posts', | ||
src: 'posts', | ||
dest: 'dev', | ||
@@ -230,3 +226,3 @@ layout: 'src/layouts/post.jade', | ||
The location of the layout template which is used for each list page. [Here](https://github.com/CabinJS/grunt-pages/blob/master/test/fixtures/integration/input/jade/pages/blog/index.jade) is a sample `listPage` template. This template has access to the following variables: | ||
The location of the layout template which is used for each list page. This page will not be rendered as a regular page if inside the options.pageSrc folder and instead will be rendered as the root list page with the first post group. [Here](https://github.com/CabinJS/grunt-pages/blob/master/test/fixtures/integration/input/jade/pages/blog/index.jade) is a sample `listPage` template. This template has access to the following variables: | ||
@@ -286,4 +282,4 @@ ###### posts | ||
posts: [{ | ||
title: 'ES6', | ||
tags: ['javascript'], | ||
title: 'Front end web development', | ||
tags: ['javascript', 'css'], | ||
content: '...' | ||
@@ -301,2 +297,6 @@ }, { | ||
content: '...' | ||
},{ | ||
title: 'Front end web development', | ||
tags: ['javascript', 'css'], | ||
content: '...' | ||
}] | ||
@@ -354,2 +354,4 @@ }]; | ||
**0.7.2** - Added support for Python 3 due to updating of [node-pygmentize-bundled](https://github.com/rvagg/node-pygmentize-bundled/) dependency. | ||
**0.7.1** - Wrong node_modules were pushed to npm. Pushed correct dependencies listed in package.json to fix bug. | ||
@@ -356,0 +358,0 @@ |
@@ -15,3 +15,2 @@ /* | ||
require('colors'); | ||
var jsYAML = require('js-yaml'); | ||
var _ = require('lodash'); | ||
@@ -40,3 +39,3 @@ var marked = require('marked'); | ||
// Create a reference to the template engine that is available to all lib methods | ||
// Create a reference to the template engine that is available to all library methods | ||
var templateEngine; | ||
@@ -48,5 +47,8 @@ | ||
grunt.registerMultiTask('pages', 'Creates pages from markdown and templates.', function () { | ||
// Task is asynchronous due to usage of pygments syntax highlighter written in python | ||
var done = this.async(); | ||
// Create a reference to the the context object and task options so that they are available to all lib methods | ||
// Create a reference to the the context object and task options | ||
// so that they are available to all library methods | ||
_this = this; | ||
@@ -76,6 +78,7 @@ options = this.options(); | ||
// Start off the parsing with unmodified posts already included | ||
var parsedPosts = unmodifiedPosts.length; | ||
var postCollection = unmodifiedPosts; | ||
// If there are no posts to parse, immediately render the posts and pages | ||
// If none of the posts have been modified, immediately render the posts and pages | ||
if (parsedPosts === numPosts) { | ||
@@ -108,9 +111,20 @@ lib.renderPostsAndPages(postCollection, cacheFile, done); | ||
if (post.markdown.length <= 1) { | ||
grunt.fail.fatal('the following post is blank, please add some content to it or delete it: ' + postpath.red); | ||
grunt.fail.fatal('The following post is blank, please add some content to it or delete it: ' + postpath.red + '.'); | ||
} | ||
marked.setOptions({ | ||
// Parse post using [marked](https://github.com/chjj/marked) | ||
marked(post.markdown, { | ||
on: _.extend({ | ||
heading : function (token, callback) { | ||
callback(null, '<a name="' + | ||
token.text.toLowerCase().replace(/[^\w]+/g, '-') + | ||
'"class="anchor" href="#' + | ||
token.text.toLowerCase().replace(/[^\w]+/g, '-') + | ||
'"><span class="header-link"></span></a>' + | ||
token.text); | ||
} | ||
}, options.listeners || {}), | ||
highlight: function (code, lang, callback) { | ||
// Use [pygments](http://pygments.org/) for highlighting | ||
// Use [pygments](http://pygments.org/) for syntax highlighting | ||
pygmentize({ lang: lang, format: 'html' }, code, function (err, result) { | ||
@@ -122,9 +136,6 @@ callback(err, result.toString()); | ||
anchors: true | ||
}); | ||
// Parse post using [marked](https://github.com/chjj/marked) | ||
marked(post.markdown, function (err, content) { | ||
}, function (err, content) { | ||
if (err) throw err; | ||
// Replace markdown source with content property | ||
// Replace markdown property with parsed content property | ||
post.content = content; | ||
@@ -144,2 +155,39 @@ delete post.markdown; | ||
/** | ||
* Gets the end of the metadata section to allow for the metadata to be JSON.parsed | ||
* and for the content to be extracted | ||
* @param {String} fileString Contents of entire post | ||
* @param {Number} currentIndex Index delimiting the substring to be searched for {'s and }'s | ||
* @return {String} | ||
*/ | ||
lib.getMetadataEnd = function (fileString, currentIndex) { | ||
var curlyNest = 1; | ||
while (curlyNest !== 0 && fileString.substr(currentIndex).length > 0) { | ||
if (fileString.substr(currentIndex).indexOf('}') === -1 && | ||
fileString.substr(currentIndex).indexOf('{') === -1) { | ||
return false; | ||
} | ||
if (fileString.substr(currentIndex).indexOf('}') !== -1) { | ||
if (fileString.substr(currentIndex).indexOf('{') !== -1) { | ||
if (fileString.substr(currentIndex).indexOf('}') < fileString.substr(currentIndex).indexOf('{')) { | ||
currentIndex += fileString.substr(currentIndex).indexOf('}') + 1; | ||
curlyNest--; | ||
} else { | ||
currentIndex += fileString.substr(currentIndex).indexOf('{') + 1; | ||
curlyNest++; | ||
} | ||
} else { | ||
currentIndex += fileString.substr(currentIndex).indexOf('}') + 1; | ||
curlyNest--; | ||
} | ||
} else { | ||
currentIndex += fileString.substr(currentIndex).indexOf('{') + 1; | ||
curlyNest++; | ||
} | ||
} | ||
return curlyNest === 0 ? currentIndex : false; | ||
}; | ||
/** | ||
* Parses the metadata and markdown from a post | ||
@@ -152,24 +200,28 @@ * @param {String} postPath Absolute path of the post to be parsed | ||
var postData = {}; | ||
var errMessage = 'The metadata for the following post is formatted incorrectly: ' + postPath.red + '\n' + | ||
'Go to the following link to learn more about post formatting:\n\n' + | ||
'https://github.com/CabinJS/grunt-pages#authoring-posts'; | ||
try { | ||
var metaDataStart; | ||
if (fileString.indexOf('{') < fileString.indexOf('}')) { | ||
metaDataStart = fileString.indexOf('{'); | ||
} else { | ||
return grunt.fail.fatal(errMessage); | ||
} | ||
// Parse JSON metadata | ||
if (fileString.indexOf('{') === 0) { | ||
postData = eval('(' + fileString.substr(0, fileString.indexOf('\n}') + 2) + ')'); | ||
postData.date = new Date(postData.date); | ||
postData.markdown = fileString.slice(fileString.indexOf('\n}') + 2); | ||
var metaDataEnd = lib.getMetadataEnd(fileString, metaDataStart + 1); | ||
// Parse YAML metadata | ||
} else if (fileString.indexOf('----') === 0) { | ||
var sections = fileString.split('----'); | ||
postData = jsYAML.load(sections[1]); | ||
if (!metaDataEnd) { | ||
return grunt.fail.fatal(errMessage); | ||
} | ||
// Extract the content by removing the metadata section | ||
postData.markdown = sections.slice(2).join('----'); | ||
} else { | ||
grunt.fail.fatal('the metadata for the following post is formatted incorrectly: ' + postPath.red); | ||
} | ||
postData.date = new Date(postData.date.getUTCFullYear(), postData.date.getUTCMonth(), postData.date.getUTCDate(), postData.date.getUTCHours(), postData.date.getUTCMinutes(), postData.date.getUTCSeconds()); | ||
postData = eval('(' + fileString.substr(metaDataStart, metaDataEnd) + ')'); | ||
postData.date = new Date(postData.date); | ||
postData.markdown = fileString.slice(metaDataEnd); | ||
return postData; | ||
} catch (e) { | ||
grunt.fail.fatal('the metadata for the following post is formatted incorrectly: ' + postPath.red); | ||
grunt.fail.fatal(errMessage); | ||
} | ||
@@ -191,3 +243,3 @@ }; | ||
// Check if the post was last modified when the cached version was last modifie | ||
// Check if the post was last modified when the cached version was last modified | ||
if (('' + fs.statSync(post.sourcePath).mtime) === ('' + new Date(post.lastModified))) { | ||
@@ -211,3 +263,3 @@ | ||
} catch (e) { | ||
grunt.fail.fatal('data could not be parsed from ' + options.data + '.'); | ||
grunt.fail.fatal('Data could not be parsed from ' + options.data + '.'); | ||
} | ||
@@ -217,3 +269,3 @@ } else if (typeof options.data === 'object') { | ||
} else { | ||
grunt.fail.fatal('data format not recognized.'); | ||
grunt.fail.fatal('options.data format not recognized. Must be an Object or String.'); | ||
} | ||
@@ -236,5 +288,7 @@ }; | ||
lib.setPostUrls(postCollection); | ||
postCollection.forEach( function (post) { | ||
postCollection.forEach(function (post) { | ||
post.dest = lib.getDestFromUrl(post.url); | ||
}); | ||
lib.sortPosts(postCollection); | ||
@@ -244,2 +298,3 @@ | ||
// Remove data that will not be passed to templates for rendering | ||
templateData.posts.forEach(function (post) { | ||
@@ -254,3 +309,5 @@ | ||
// Record how long it takes to generate posts | ||
var postStart = new Date().getTime(); | ||
lib.generatePosts(templateData); | ||
@@ -262,3 +319,5 @@ | ||
// Record how long it takes to generate pages | ||
var pageStart = new Date().getTime(); | ||
if (options.pageSrc) { | ||
@@ -295,3 +354,3 @@ lib.generatePages(templateData); | ||
/** | ||
* Updates the post collection with each post's destination | ||
* Updates the post collection with each post's url | ||
* @param {Array} postCollection Collection of parsed posts with the content and metadata properties | ||
@@ -306,3 +365,3 @@ */ | ||
/** | ||
* Returns the post url based on the url property and postData | ||
* Returns the post url based on the url property and post metadata | ||
* @param {Object} post Post object containing all metadata properties of the post | ||
@@ -348,3 +407,3 @@ * @return {String} | ||
} else { | ||
grunt.fail.fatal('required ' + urlSegment + ' attribute not found in the following post\'s metadata: ' + post.sourcePath + '.'); | ||
grunt.fail.fatal('Required ' + urlSegment + ' attribute not found in the following post\'s metadata: ' + post.sourcePath + '.'); | ||
} | ||
@@ -357,3 +416,3 @@ }); | ||
/** | ||
* Gets a post's or page's destionation based on its url | ||
* Gets a post's or page's destination based on its url | ||
* @param {String} url Url to determine the destination from | ||
@@ -363,2 +422,4 @@ */ | ||
var dest = _this.data.dest + '/' + url; | ||
// Ensures that a .html is present at the end of the file's destination path | ||
if (dest.indexOf('.html') === -1) { | ||
@@ -396,4 +457,4 @@ if (dest.lastIndexOf('/') === dest.length - 1) { | ||
// Determine the template engine based on the file's extention name | ||
templateEngine = templateEngines[path.extname(_this.data.layout).slice(1)]; | ||
// Determine the template engine based on the file's extension name | ||
templateEngine = templateEngines[path.extname(_this.data.layout).slice(1).toLowerCase()]; | ||
@@ -408,2 +469,3 @@ var layoutString = fs.readFileSync(_this.data.layout, 'utf8'); | ||
grunt.log.debug(JSON.stringify(templateData, null, ' ')); | ||
grunt.file.write(post.dest, fn(templateData)); | ||
@@ -436,2 +498,3 @@ grunt.log.ok('Created '.green + 'post'.blue + ' at: ' + post.dest); | ||
templateData.currentPage = path.basename(abspath, path.extname(abspath)); | ||
grunt.log.debug(JSON.stringify(templateData, null, ' ')); | ||
grunt.file.write(dest, fn(templateData)); | ||
@@ -473,4 +536,4 @@ grunt.log.ok('Created '.green + 'page'.magenta + ' at: ' + dest); | ||
// If the template engine is specified, don't render templates with other filetypes | ||
if (options.templateEngine && path.extname(abspath) !== '.' + options.templateEngine) { | ||
// If options.templateEngine is specified, don't render templates with other file extensions | ||
if (options.templateEngine && path.extname(abspath).toLowerCase() !== '.' + options.templateEngine) { | ||
return false; | ||
@@ -483,3 +546,3 @@ } | ||
/** | ||
* Default function to get post groups for each paginated list page by grouping a specified number of posts per page | ||
* Default function to get post groups for each paginated page by grouping a specified number of posts per page | ||
* @param {Array} postCollection Collection of parsed posts with the content and metadata properties | ||
@@ -524,3 +587,3 @@ * @return {Array} Array of post arrays to be displayed on each paginated page | ||
/** | ||
* Creates paginated pages with a specified number of posts per page | ||
* Creates paginated pages based on a scheme to group posts | ||
* @param {Object} templateData Data to be passed to templates for rendering | ||
@@ -536,3 +599,4 @@ * @param {Object} pagination Configuration object for pagination | ||
pages.forEach(function (page, currentIndex) { | ||
grunt.file.write(lib.getDestFromUrl(page.url), fn({ | ||
var templateRenderData = { | ||
currentIndex: currentIndex, | ||
@@ -544,3 +608,6 @@ pages: _.map(pages, function (page) { | ||
data: templateData.data || {} | ||
})); | ||
}; | ||
grunt.log.debug(JSON.stringify(templateRenderData, null, ' ')); | ||
grunt.file.write(lib.getDestFromUrl(page.url), fn(templateRenderData)); | ||
grunt.log.ok('Created '.green + 'paginated'.rainbow + ' page'.magenta + ' at: ' + lib.getDestFromUrl(page.url)); | ||
@@ -605,3 +672,3 @@ }); | ||
/** | ||
* Gets a list page's destination to be written | ||
* Gets a list page's url based on its id, pagination.url, and options.pageSrc | ||
* @param {Number} pageId Identifier of current page to be written | ||
@@ -622,3 +689,3 @@ * @param {Object} pagination Configuration object for pagination | ||
} else { | ||
grunt.fail.fatal('the pagination.listPage must be within the options.pageSrc directory.'); | ||
grunt.fail.fatal('The pagination.listPage must be within the options.pageSrc directory.'); | ||
} | ||
@@ -636,3 +703,3 @@ } | ||
// Every other list page's url is generated using the urlFormat property and is either generated | ||
// Every other list page's url is generated using the pagination.url property and is either generated | ||
// relative to the folder that contains the listPage or relative to the root of the site | ||
@@ -650,3 +717,3 @@ } else { | ||
// Removed trailing index.html from urls | ||
// Remove unnecessary trailing index.html from urls | ||
if (url.lastIndexOf('index.html') === url.length - 'index.html'.length) { | ||
@@ -653,0 +720,0 @@ url = url.slice(0, - 'index.html'.length); |
Git dependency
Supply chain riskContains a dependency which resolves to a remote git URL. Dependencies fetched from git URLs are not immutable and can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
Git dependency
Supply chain riskContains a dependency which resolves to a remote git URL. Dependencies fetched from git URLs are not immutable and can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
40752
8
580
400
15
- Removedjs-yaml@~2.1.0
- Removedjs-yaml@2.1.3(transitive)
Updatedmarked@git://github.com/ChrisWren/marked.git#listener