Repository: ducksoupdev/gulp-site-generator
Branch: master
Commit: 074332c1e52f
Files: 71
Total size: 166.9 KB
Directory structure:
gitextract_29i1oj7i/
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── gulp/
│ ├── lib/
│ │ ├── compile-dates.js
│ │ ├── compile-home.js
│ │ ├── compile-options.js
│ │ ├── compile-pages.js
│ │ ├── compile-rss.js
│ │ ├── compile-tags.js
│ │ ├── dates.js
│ │ ├── downzero.js
│ │ ├── drafts.js
│ │ ├── paths.js
│ │ ├── promises.js
│ │ ├── remove-dir.js
│ │ └── tags.js
│ ├── tasks/
│ │ ├── build.js
│ │ ├── clobber.js
│ │ ├── compile.js
│ │ ├── concat-js.js
│ │ ├── content.js
│ │ ├── copy-assets.js
│ │ ├── copy-css.js
│ │ ├── copy-fonts.js
│ │ ├── default.js
│ │ ├── develop.js
│ │ ├── help.js
│ │ ├── image-min.js
│ │ ├── minify-html.js
│ │ ├── sass.js
│ │ ├── server.js
│ │ ├── test-ci.js
│ │ ├── test.js
│ │ └── uncss.js
│ └── tests/
│ ├── compile-dates.spec.js
│ ├── compile-home.spec.js
│ ├── compile-options.spec.js
│ ├── compile-pages.spec.js
│ ├── compile-rss.spec.js
│ ├── compile-tags.spec.js
│ ├── dates.spec.js
│ ├── downzero.spec.js
│ ├── drafts.spec.js
│ ├── paths.spec.js
│ ├── remove-dir.spec.js
│ └── tags.spec.js
├── gulpfile.js
├── install/
│ ├── content/
│ │ ├── pages/
│ │ │ └── about.md
│ │ ├── posts/
│ │ │ └── sample-blog-post.md
│ │ └── template.md
│ ├── files.json
│ ├── lib/
│ │ ├── json-file.js
│ │ └── version-compare.js
│ ├── sass/
│ │ └── style.scss
│ ├── templates/
│ │ ├── index.hbs
│ │ ├── page.hbs
│ │ ├── partials/
│ │ │ ├── footer.hbs
│ │ │ ├── header.hbs
│ │ │ ├── loop.hbs
│ │ │ ├── navigation.hbs
│ │ │ ├── pagination.hbs
│ │ │ └── sidebar.hbs
│ │ └── post.hbs
│ └── tests/
│ └── version-compare.spec.js
├── install.js
├── package.json
├── site.json
└── update.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
[*.md]
indent_style = tab
[*.yml]
indent_style = space
indent_size = 2
================================================
FILE: .eslintrc.json
================================================
{
"env": {
"node": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
],
"no-console": [
"error", {
"allow": ["info", "warn", "error"]
}
]
},
"globals": {
"process": false,
"it": false,
"afterEach": false,
"beforeEach": false,
"after": false,
"describe": false,
"before": false
}
}
================================================
FILE: .gitignore
================================================
node_modules/
coverage/
*.log
.vscode/
.idea/
.tmp/
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "6.9"
- "4.7"
before_script:
- npm install -g gulp
script: gulp test-ci
================================================
FILE: CHANGELOG.md
================================================
# Change log
## v0.4.1 (2017-08-27)
**Fixes**
* Fix `gulp-mocha` to v3.0.1 to ensure `istanbul` coverage is generated
## v0.4.0 (2017-08-27)
**Features**
* Replace livereload with browser-sync [#35]
* Replace uncss with sass in develop task [#35]
* Uplift npm packages
## v0.3.2 (2017-02-24)
**Fixes**
* Fix update script to ignore installing `content/template.md` if missing
## v0.3.1 (2017-02-23)
**Features**
* Open browser in development task
* Open browser in server task
## v0.3.0 (2017-02-23)
**Features**
* Change tasks for development - run specific tasks on changes
* Draft posts/pages are compiled by default in development
* Uplift npm packages
* Add editorconfig to installed files
* Update gitignore
* Code cleanup
## v0.2.2 (2017-02-09)
**Fixes**
* Added eslint [#28]
* New lines for some files [#28]
* Changed .editorconfig to use utf-8 [#28]
## v0.2.1 (2017-01-24)
**Features**
* Removed comma in getTagsAsLinks [#27]
## v0.2.0 (2017-01-11)
**Features**
* Refactor compilation [#26]
* Uplift npm packages
* Remove temp files
* Code cleanup
* Fix image npm module versions
## v0.1.29 (2017-01-11)
**Features**
* Add cssIgnore configuration option
## v0.1.28 (2016-11-11)
**Fixes**
* Fix livereload race condition + HBS watch [#24]
## v0.1.27 (2016-01-03)
**Fixes**
* Fix issue with Handlebars date helper and the wrong format.
## v0.1.26 (2016-01-03)
**Fixes**
* Fix issue with missing node modules after running update script.
## v0.1.25 (2016-01-02)
**Features**
* Add image compression setting to site.json. If set to false, no image compression is performed.
## v0.1.24 (2015-12-30)
**Features**
* Add update script for easy node module upgrades. Readme edited with info on how to update.
* Refactor install script
* Code cleanup
## v0.1.23 (2015-12-29)
**Features**
* Update install script to modify package.json dependencies
* Uplift node modules
**Fixes**
* Update Travis CI build config
## v0.1.22 (2015-12-29)
**Features**
* Support leaving out the initial --- for front-matter [#12]
* Support tags defined as an array as well as a string [#13]
**Fixes**
* Fix automatically generated slug doesn't link correctly [#11]
* Fix post sorting is not correct [#10]
## v0.1.21 (2015-12-16)
**Fixes**
* Fix missing pagination partial from the install script
## v0.1.20 (2015-11-11)
**Features**
* Add site data to individual pages or posts
**Fixes**
* Fix issue with pagination on home, date and tag pages
## v0.1.19 (2015-10-09)
**Fixes**
* Fix sidebar dates and tags
## v0.1.18 (2015-10-09)
**Features**
* Update readme and add help task
## v0.1.17 (2015-10-06)
**Fixes**
* Fix develop task to watch files in `src` and run default task
## v0.1.16 (2015-09-23)
**Fixes**
* Add auto-generated changelog
## v0.1.15 (2015-09-23)
**Fixes**
* Fix compile rss tests
## v0.1.14 (2015-09-22)
**Features**
* Replace imagemin-jpegoptim with imagemin-mozjpeg
* Uplift node modules
**Breaking changes**
* If installed as a sub-module, you'll need to update your package.json in the root of your project as several node modules have been updated
## v0.1.13 (2015-07-29)
**Features**
* Add slug as optional page or post variable.
**Breaking changes**
* If installed as a sub-module, you'll need to update your package.json in the root of your project as several node modules have been updated
## v0.1.12 (2015-07-11)
**Notes**
* Uplift node modules
## v0.1.11 (2015-02-26)
**Fixes**
* Fix an issue where date pages were skipped if tags exist
## v0.1.10 (2015-02-26)
**Fixes**
* Fix example handlebars templates
## v0.1.9 (2015-02-26)
**Fixes**
* Fix issue with pagination paths in tests
## v0.1.8 (2015-02-26)
**Fixes**
* Fixed an issue with promises on tests
* Fixed an issue with incorrect URLs on originated pages
**Breaking changes**
New npm dev module `gulp-coveralls`
New npm module `mout`
## v0.1.7 (2015-02-25)
* Update to generate full paginated pages
* Update so pages/posts can be stored anywhere in src/content
* Update README to include coveralls badge
* Add coveralls coverage
* Update node version for travis ci
* Add pages array to handlebars template data
## v0.1.5 (2015-02-19)
* Add RSS feed feature to readme
* Add install sample files
## v0.1.4 (2015-02-18)
* Improve test coverage
* Update readme for install script
* Implement install script
## v0.1.3 (2015-02-17)
* Add minify css to copy-css task
## v0.1.2 (2015-02-17)
* Fix error handling on no content
## v0.1.1 (2015-02-17)
* Update copy tasks to remove dependencies
## v0.1.0 (2015-02-17)
* Initial commit
================================================
FILE: README.md
================================================
# Static site generator using Gulp
[](https://travis-ci.org/ducksoupdev/gulp-site-generator)
[](https://coveralls.io/r/ducksoupdev/gulp-site-generator?branch=master)
This is a simple static site generator which is perfect for a personal, blog or documentation site.
It is similar to other static site generators in that it takes your Markdown content, renders it, optimises it and creates a production-ready site that can be served by Nginx, Apache or another web server.
## Features
* Convert Markdown files to static HTML
* [Handlebars](http://handlebarsjs.com) templates and partials
* Sass compiling and minification
* Css reducing (Uncss)
* Javascript concatenating and minification
* Asset copying
* Image compression
* HTML compression
* RSS feed creation
* Server for viewing built site
* Clobber for cleaning build directory
* Save content as draft
* Convert draft templates
* Creates a `build/` directory with built content and assets
## Installation
This project is ideal when used as a Git sub-module or installed along-side your site so you can update it when new releases are made.
### Getting the generator
#### Git sub-module
$ mkdir my-static-site
$ cd my-static-site
$ git init
$ git submodule add https://github.com/ducksoupdev/gulp-site-generator.git tools
#### Straightforward checkout
$ mkdir my-static-site
$ cd my-static-site
$ git clone https://github.com/ducksoupdev/gulp-site-generator.git tools
### Installing the dependencies
The generator requires Gulp to be installed globally. If you don't have it you can install it using:
$ npm install -g gulp
The generator has some dependencies that need to be installed using the following script:
$ node tools/install
The script creates the following files in the root of your site:
* `package.json` - the node modules required by the generator
* `gulpfile.js` - the gulp file for all the generator tasks
* `site.json` - the metadata for your site
* `src/content` - the content folder for the markdown files including some sample ones
* `src/images` - a sample image
* `src/sass` - a sample sass file
* `src/templates` - a set of Handlebar templates for creating pages and posts
Once created, the next thing is to install the required node modules:
$ npm install
Finally, you can run the generator to create the sample site:
$ gulp
The generator will create a `build/` folder with the compiled and optimised site ready to be deployed to your web server.
So putting it all together, the installation steps are:
$ mkdir my-static-site
$ cd my-static-site
$ git init
$ git submodule add https://github.com/ducksoupdev/gulp-site-generator.git tools
$ npm install -g gulp
$ node tools/install
$ npm install
$ gulp
## Updating
Updating the generator submodule to the latest version can be done using git:
$ cd tools
$ git remote update
$ git pull
$ cd ..
The generator has an update script that can then be run:
$ node tools/update
The script updates the `package.json` in the root of your site and updates any dependencies to the latest versions.
## Tasks
When you have created templates, content and assets, the default task will run the generator:
$ gulp
The following tasks can also be run individually:
* **develop** - a live-reload task for developing your site where changes are reloaded automatically including draft posts and pages - view at http://localhost:8080
* **sass** - converts sass files to css
* **clobber** - removes the `build/` directory
* **server** - view your built site locally at http://localhost:8080
* **help** - lists available tasks
## Configuration
The metadata file `site.json` contains all configuration required by your site. The following properties are used by the generator.
You are free to add properties to this file for use in your Handlebars templates.
* title (string) (required) - the title of your site
* description (string) (required) - a description of your site
* url (string) (required) - the URL of your site
* rss (string) (required) - the RSS feed XML file
* maxItems (number) (optional) - the number to use for pagination
* authors (object) (optional) - an map of authors with metadata
* concatJs (array) (optional) - a list of javascript files to combine and minify
* styleSheet (string) (optional) - the name of your main CSS file created by the sass task
* imageCompression (boolean) (optional) - a boolean value to enable/disable image compresssion on build
* uncssIgnore (array) (optional) - a list of selectors that uncss should ignore (for example ".container" or "#my-element")
## Content
Content must be added to the `src/content` directory.
### Pages and posts
Pages and posts must created in the `src/content/pages` and `src/content/posts` directories.
Pages and posts are Markdown files with a YAML front-matter block. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines. Here is a basic example:
---
title: Home
template: index.hbs
---
The rest of the template goes here as markdown text.
Between these triple-dashed lines, you can set predefined variables (see below). These variables will then be available to you in any Handlebars templates or includes that the page or post in question relies on.
* title (required) - the title of the page or post
* template (required) - the Handlebars template to use to compile the page or post
* slug (optional) - the URL slug which is used as the directory name when the page or post is built
* date (optional) - used for posts and in the format YYYY-MM-DD
* author (optional) - used for posts and the author key in the `site.json` file
* status (optional) - set to 'draft' to ignore the page or post when running the generator
### Assets
Images, javascripts, fonts etc can all be added to the `src/` directory. You are free to create directories for these and name them accordingly.
Content is created in the `src/content/pages` or `src/content/posts`.
The generator is opinionated in that it expects certain files in particular directories.
To help with this, [an example site](https://github.com/ducksoupdev/gulp-site-generator-example) is available that shows you how to structure your site with the generator.
### Templates
Handlebars is used for rendering templates. Partials located in `src/templates/partials` are automatically available to your Handlebar templates.
Helpers are available to your Handlebar templates and partials, these are:
* date - format a date in a particular format (uses Moment)
`{{date format="MMM Do, YYYY"}}`
* excerpt - returns an excerpt of the text of your content, use 'words' or 'characters' to set the length
`{{excerpt words="50"}}`
* content - returns an excerpt of content and is tag aware, use 'words' or 'characters' to set the length
`{{content words="50"}}`
* resolve - resolves the path to an asset relative to the site root
`{{resolve "/favicon.ico"}}`
## Further information
The [example site](https://github.com/ducksoupdev/gulp-site-generator-example) is a good place to start and shows a basic structure of a site with templates and content.
If you encounter any issues or have any feedback, please [send me a tweet](http://twitter.com/ducksoupdev) or raise a bug on the issue tracker.
================================================
FILE: gulp/lib/compile-dates.js
================================================
"use strict";
var gulp = require("gulp"),
Promise = require("bluebird"),
compileHandlebars = require("gulp-compile-handlebars"),
rename = require("gulp-rename"),
fs = require("fs"),
glob = require("glob"),
moment = require("moment"),
_ = require("lodash"),
compileOptions = require("../lib/compile-options"),
tags = require("../lib/tags"),
dates = require("../lib/dates"),
resolvePaths = require("../lib/paths"),
compileDrafts = require("../lib/drafts"),
promiseList = require("../lib/promises");
module.exports = function (rootPath) {
return new Promise(function(resolve, reject) {
var siteData = JSON.parse(fs.readFileSync(rootPath + "/site.json", "utf8"));
var gulpVersion = require("gulp/package").version;
var compileOptionsObj = compileOptions(rootPath);
glob(rootPath + "/build/content/**/*.json", {
cwd: "."
}, function (err, files) {
if (err) {
reject(err);
} else {
var datePosts = {},
posts = [],
allPosts = [];
files.forEach(function (file) {
var fileData = JSON.parse(fs.readFileSync(file, "utf8"));
if (fileData.status && fileData.status === "draft" && !compileDrafts()) {
return;
}
if (!fileData.date || !fileData.template) {
return;
}
// check and fill-in missing file meta data
fileData = compileOptionsObj.checkContent(fileData);
var metaData = {
title: fileData.title,
description: resolvePaths.resolve(fileData.body, "../.."),
url: "../../" + fileData.slug + "/",
tagStr: fileData.tags,
tags: (fileData.tags ? tags.getTagsAsLinks("../..", fileData.tags) : undefined),
date: fileData.date,
post_class: "post" + (fileData.tags ? tags.getTagClasses(fileData.tags) : fileData.slug),
meta: fileData
};
if (fileData.date && fileData.template === "post.hbs") {
posts.push(metaData);
}
if (fileData.date) {
var dateMonth = fileData.date.substr(0, 7); //2014-12
if (datePosts[dateMonth]) {
datePosts[dateMonth].push(metaData);
} else {
datePosts[dateMonth] = [metaData];
}
}
});
if (_.size(datePosts)) {
var promises = [];
posts.sort(dates.sortFunc);
allPosts = _.cloneDeep(posts);
for (var dateMonth in datePosts) {
// sort the dateMonth posts
datePosts[dateMonth].sort(dates.sortFunc);
var templateData = {
date: moment().format("YYYY-MM-DD"),
resourcePath: "../..",
generator: "Gulp " + gulpVersion,
meta_title: siteData.title,
url: "../..",
site: siteData,
posts: datePosts[dateMonth],
body_class: "home-template",
rss: "../.." + siteData.rss,
dateStr: moment(dateMonth, "YYYY-MM").format("MMMM YYYY")
};
if (siteData.maxItems && datePosts[dateMonth].length > siteData.maxItems) {
// how many pages do we need to create?
var totalPages = Math.ceil(datePosts[dateMonth].length / siteData.maxItems);
// shorten posts
var paginatedPosts = datePosts[dateMonth].splice(siteData.maxItems);
for (var i = 1; i < totalPages; i++) {
var pageNumber = i + 1;
var nextPosts = paginatedPosts.splice(0, siteData.maxItems);
// update the resource paths
nextPosts.forEach(function (post) {
post.description = resolvePaths.resolve(post.meta.body, "../../../..");
post.url = "../../../../" + post.meta.slug;
});
// create custom template data for this paginated page
var pageTemplateData = _.cloneDeep(templateData);
_.extend(pageTemplateData, {
posts: nextPosts,
resourcePath: "../../../..",
url: "../../../..",
rss: "../../../.." + siteData.rss,
allDates: dates.getAllDatesAsLinks("../../../..", allPosts),
allTags: tags.getAllTagsAsLinks("../../../..", allPosts)
});
delete pageTemplateData.pages;
// add pagination data
if (pageNumber === 2) {
pageTemplateData.prevUrl = "../../";
} else {
pageTemplateData.prevUrl = "../" + (pageNumber - 1);
}
if (pageNumber < totalPages) {
pageTemplateData.nextUrl = "../" + (pageNumber + 1);
}
pageTemplateData.totalPages = totalPages;
promises.push(new Promise(function (resolve, reject) {
gulp.src(rootPath + "/src/templates/index.hbs")
.pipe(compileHandlebars(pageTemplateData, compileOptionsObj))
.pipe(rename("index.html"))
.pipe(gulp.dest(rootPath + "/build/date/" + dateMonth + "/page/" + pageNumber))
.on("error", reject)
.on("end", resolve);
}));
}
templateData.nextUrl = "../../date/" + dateMonth + "/page/2";
templateData.totalPages = totalPages;
}
// update template
_.extend(templateData, {
allDates: dates.getAllDatesAsLinks("../..", allPosts),
allTags: tags.getAllTagsAsLinks("../..", allPosts)
});
promises.unshift(new Promise(function (resolve, reject) {
gulp.src(rootPath + "/src/templates/index.hbs")
.pipe(compileHandlebars(templateData, compileOptionsObj))
.pipe(rename("index.html"))
.pipe(gulp.dest(rootPath + "/build/date/" + dateMonth))
.on("error", reject)
.on("end", resolve);
}));
}
Promise.all(promiseList.filter(promises))
.then(function () {
resolve();
}, function (err) {
reject(err);
});
} else {
resolve();
}
}
});
});
};
================================================
FILE: gulp/lib/compile-home.js
================================================
"use strict";
var gulp = require("gulp"),
Promise = require("bluebird"),
compileHandlebars = require("gulp-compile-handlebars"),
rename = require("gulp-rename"),
fs = require("fs"),
glob = require("glob"),
moment = require("moment"),
_ = require("lodash"),
compileOptions = require("../lib/compile-options"),
tags = require("../lib/tags"),
dates = require("../lib/dates"),
resolvePaths = require("../lib/paths"),
compileDrafts = require("../lib/drafts"),
promiseList = require("../lib/promises");
module.exports = function (rootPath) {
return new Promise(function(resolve, reject) {
var siteData = JSON.parse(fs.readFileSync(rootPath + "/site.json", "utf8"));
var gulpVersion = require("gulp/package").version;
var compileOptionsObj = compileOptions(rootPath);
glob(rootPath + "/build/content/**/*.json", {
cwd: "."
}, function (err, files) {
if (err) {
reject(err);
} else {
var posts = [],
allPosts = [],
pages = [];
files.forEach(function (file) {
var fileData = JSON.parse(fs.readFileSync(file, "utf8"));
if (fileData.status && fileData.status === "draft" && !compileDrafts()) {
return;
}
// check and fill-in missing file meta data
fileData = compileOptionsObj.checkContent(fileData);
var metaData = {
title: fileData.title,
description: resolvePaths.resolve(fileData.body, ""),
url: fileData.slug + "/",
tagStr: fileData.tags,
tags: (fileData.tags ? tags.getTagsAsLinks("", fileData.tags) : undefined),
date: fileData.date,
post_class: "post " + (fileData.template === "page.hbs" ? "page " : "") + (fileData.tags ? tags.getTagClasses(fileData.tags) : fileData.slug),
meta: fileData
};
if (fileData.date && fileData.template === "post.hbs") {
posts.push(metaData);
} else {
pages.push(metaData);
}
});
if (posts.length || pages.length) {
posts.sort(dates.sortFunc);
allPosts = _.cloneDeep(posts);
var templateData = {
date: moment().format("YYYY-MM-DD"),
resourcePath: "",
generator: "Gulp " + gulpVersion,
meta_title: siteData.title,
url: "",
site: siteData,
posts: posts,
pages: pages,
body_class: "home-template",
rss: siteData.rss,
allDates: dates.getAllDatesAsLinks("", allPosts),
allTags: tags.getAllTagsAsLinks("", allPosts)
};
var promises = [];
if (siteData.maxItems && posts.length > siteData.maxItems) {
// how many pages do we need to create?
var totalPages = Math.ceil(posts.length / siteData.maxItems);
// shorten posts
var paginatedPosts = posts.splice(siteData.maxItems);
for (var i = 1; i < totalPages; i++) {
var pageNumber = i + 1;
var nextPosts = paginatedPosts.splice(0, siteData.maxItems);
// update the resource paths
nextPosts.forEach(function (post) {
post.description = resolvePaths.resolve(post.meta.body, "../..");
post.url = "../../" + post.meta.slug;
});
// create custom template data for this paginated page
var pageTemplateData = _.cloneDeep(templateData);
_.extend(pageTemplateData, {
posts: nextPosts,
resourcePath: "../..",
url: "../..",
rss: "../.." + siteData.rss,
allDates: dates.getAllDatesAsLinks("../..", allPosts),
allTags: tags.getAllTagsAsLinks("../..", allPosts)
});
delete pageTemplateData.pages;
// add pagination data
if (pageNumber === 2) {
pageTemplateData.prevUrl = "../../";
} else {
pageTemplateData.prevUrl = "../" + (pageNumber - 1);
}
if (pageNumber < totalPages) {
pageTemplateData.nextUrl = "../" + (pageNumber + 1);
}
pageTemplateData.totalPages = totalPages;
promises.push(new Promise(function (resolve, reject) {
gulp.src(rootPath + "/src/templates/index.hbs")
.pipe(compileHandlebars(pageTemplateData, compileOptionsObj))
.pipe(rename("index.html"))
.pipe(gulp.dest(rootPath + "/build/page/" + pageNumber))
.on("error", reject)
.on("end", function () {
resolve();
});
}));
}
// update template
templateData.nextUrl = "page/2";
templateData.totalPages = totalPages;
}
promises.unshift(new Promise(function (resolve, reject) {
gulp.src(rootPath + "/src/templates/index.hbs")
.pipe(compileHandlebars(templateData, compileOptionsObj))
.pipe(rename("index.html"))
.pipe(gulp.dest(rootPath + "/build"))
.on("error", reject)
.on("end", resolve);
}));
Promise.all(promiseList.filter(promises))
.then(function () {
resolve();
}, function (err) {
reject(err);
});
} else {
resolve();
}
}
});
});
};
================================================
FILE: gulp/lib/compile-options.js
================================================
"use strict";
var moment = require("moment"),
_ = require("lodash"),
downsize = require("downsize"),
downzero = require("./downzero"),
stringUtils = require("mout/string");
module.exports = function (rootPath) {
return {
batch: [rootPath + "/src/templates/partials"],
checkContent: function (fileData) {
var excerpt = stringUtils.stripHtmlTags(fileData.body);
excerpt = excerpt.replace(/(\r\n|\n|\r)+/gm, " ");
var title = downsize(excerpt, { words: 10 });
if (!fileData.title) {
fileData.title = title;
}
if (!fileData.slug) {
fileData.slug = stringUtils.slugify(fileData.title, "-");
}
if (!fileData.template && !fileData.date) {
fileData.template = "page.hbs";
} else if (fileData.date && !fileData.template) {
fileData.template = "post.hbs";
}
return fileData;
},
helpers: {
date: function (context, options) {
if (!options && context.hasOwnProperty("hash")) {
options = context;
context = undefined;
// set to published_at by default, if it"s available
// otherwise, this will print the current date
if (this.date) {
context = this.date;
}
}
// ensure that context is undefined, not null, as that can cause errors
context = context === null ? undefined : context;
var format = options.hash.format || "MMM Do, YYYY";
var date = moment(context, "YYYY-MM-DD").format(format);
return date;
},
excerpt: function (options) {
var truncateOptions = (options || {}).hash || {},
excerpt;
truncateOptions = _.pick(truncateOptions, ["words", "characters"]);
_.keys(truncateOptions).map(function (key) {
truncateOptions[key] = parseInt(truncateOptions[key], 10);
});
excerpt = stringUtils.stripHtmlTags(this.description);
excerpt = excerpt.replace(/(\r\n|\n|\r)+/gm, " ");
if (!truncateOptions.words && !truncateOptions.characters) {
truncateOptions.words = 50;
}
return downsize(excerpt, truncateOptions);
},
content: function (options) {
var truncateOptions = (options || {}).hash || {};
truncateOptions = _.pick(truncateOptions, ["words", "characters"]);
_.keys(truncateOptions).map(function (key) {
truncateOptions[key] = parseInt(truncateOptions[key], 10);
});
if (truncateOptions.hasOwnProperty("words") || truncateOptions.hasOwnProperty("characters")) {
// Legacy function: {{content words="0"}} should return leading tags.
if (truncateOptions.hasOwnProperty("words") && truncateOptions.words === 0) {
return downzero(this.description);
}
return downsize(this.description, truncateOptions);
}
return this.description;
},
resolve: function (path) {
if (path && this.resourcePath && this.resourcePath !== "") {
return this.resourcePath + path;
}
if (/^\//.test(path)) {
path = path.substring(1);
}
return "" + path;
},
or: function (v1, v2) {
return v1 || v2;
}
}
};
};
================================================
FILE: gulp/lib/compile-pages.js
================================================
"use strict";
var gulp = require("gulp"),
Promise = require("bluebird"),
compileHandlebars = require("gulp-compile-handlebars"),
rename = require("gulp-rename"),
fs = require("fs"),
path = require("path"),
glob = require("glob"),
moment = require("moment"),
_ = require("lodash"),
compileOptions = require("../lib/compile-options"),
tags = require("../lib/tags"),
dates = require("../lib/dates"),
resolvePaths = require("../lib/paths"),
compileDrafts = require("../lib/drafts"),
promiseList = require("../lib/promises");
module.exports = function (rootPath) {
return new Promise(function(resolve, reject) {
var siteData = JSON.parse(fs.readFileSync(rootPath + "/site.json", "utf8"));
var gulpVersion = require("gulp/package").version;
var compileOptionsObj = compileOptions(rootPath);
glob(rootPath + "/build/content/**/*.json", {
cwd: "."
}, function (err, files) {
if (err) {
reject(err);
} else {
var templatesToCreate = [],
posts = [];
files.forEach(function (file) {
var fileData = JSON.parse(fs.readFileSync(file, "utf8"));
if (fileData.status && fileData.status === "draft" && !compileDrafts()) {
return;
}
// check and fill-in missing file meta data
fileData = compileOptionsObj.checkContent(fileData);
var tagClasses = tags.getTagClasses(fileData.tags);
var metaData = {
title: fileData.title,
body: resolvePaths.resolve(fileData.body, ".."),
url: "../" + fileData.slug + "/",
tagStr: fileData.tags,
tags: (fileData.tags ? tags.getTagsAsLinks("..", fileData.tags) : undefined),
date: fileData.date,
post_class: "post " + (fileData.template === "page.hbs" ? "page " : "") + (fileData.tags ? tagClasses : fileData.slug),
author: (fileData.author ? siteData.authors[fileData.author] : ""),
meta: fileData
};
if (fileData.date && fileData.template === "post.hbs") {
posts.push(metaData);
}
// post class
var bodyClass = "post-template";
if (fileData.template === "page.hbs") {
bodyClass += " page-template page";
}
// tags
if (fileData.tags) {
bodyClass += tagClasses;
}
var templateData = {
date: moment().format("YYYY-MM-DD"),
resourcePath: "..",
generator: "Gulp " + gulpVersion,
meta_title: fileData.title,
url: "..",
site: siteData,
post: metaData,
body_class: bodyClass,
rss: ".." + siteData.rss
};
var outDir = rootPath + "/build/" + path.basename(file).replace(/\.[^/.]+$/, "");
templatesToCreate.push({
outDir: outDir,
templateSrc: rootPath + "/src/templates/" + fileData.template,
templateData: templateData
});
});
if (templatesToCreate.length) {
var promises = [];
templatesToCreate.forEach(function (templateToCreate) {
_.extend(templateToCreate.templateData.post, {
site: siteData,
allDates: dates.getAllDatesAsLinks("..", posts),
allTags: tags.getAllTagsAsLinks("..", posts)
});
promises.push(new Promise(function (resolve, reject) {
gulp.src(templateToCreate.templateSrc)
.pipe(compileHandlebars(templateToCreate.templateData, compileOptionsObj))
.pipe(rename("index.html"))
.pipe(gulp.dest(templateToCreate.outDir))
.on("error", reject)
.on("end", resolve);
}));
});
Promise.all(promiseList.filter(promises))
.then(function () {
resolve();
}, function (err) {
reject(err);
});
} else {
resolve();
}
}
});
});
};
================================================
FILE: gulp/lib/compile-rss.js
================================================
"use strict";
var fs = require("fs"),
Promise = require("bluebird"),
glob = require("glob"),
moment = require("moment"),
RSS = require("rss"),
dates = require("../lib/dates"),
resolvePaths = require("../lib/paths"),
compileDrafts = require("../lib/drafts");
module.exports = function (rootPath) {
return new Promise(function(resolve, reject) {
var siteData = JSON.parse(fs.readFileSync(rootPath + "/site.json", "utf8"));
var gulpVersion = require("gulp/package").version;
glob(rootPath + "/build/content/posts/*.json", {
cwd: "."
}, function (err, files) {
if (err) {
reject(err);
} else {
var posts = [];
files.forEach(function (file) {
var fileData = JSON.parse(fs.readFileSync(file, "utf8"));
if (fileData.status && fileData.status === "draft" && !compileDrafts()) {
return;
}
var metaData = {
title: fileData.title,
description: resolvePaths.resolve(fileData.body, siteData.url),
url: siteData.url + "/" + fileData.slug + "/",
tags: (fileData.tags ? (fileData.tags.split ? fileData.tags.split(" ") : fileData.tags) : undefined), // tags can be either string or array
date: fileData.date
};
posts.push(metaData);
});
if (posts.length) {
var feed = new RSS({
title: siteData.title,
description: siteData.description,
generator: "Gulp " + gulpVersion,
site_url: siteData.url,
feed_url: siteData.url + "/rss.xml",
ttl: 60
});
posts.sort(dates.sortFunc);
posts.forEach(function (item) {
feed.item({
title: item.title,
description: item.description,
url: item.url,
guid: item.url,
categories: item.tags,
date: moment(item.date, "YYYY-MM-DD").toDate()
});
});
var xml = feed.xml();
fs.writeFile(rootPath + "/build/rss.xml", xml, {
encoding: "utf8"
}, function (err) {
if (err) {
reject(err);
}
resolve();
});
} else {
resolve();
}
}
});
});
};
================================================
FILE: gulp/lib/compile-tags.js
================================================
"use strict";
var gulp = require("gulp"),
Promise = require("bluebird"),
compileHandlebars = require("gulp-compile-handlebars"),
rename = require("gulp-rename"),
fs = require("fs"),
glob = require("glob"),
moment = require("moment"),
_ = require("lodash"),
compileOptions = require("../lib/compile-options"),
tags = require("../lib/tags"),
dates = require("../lib/dates"),
resolvePaths = require("../lib/paths"),
compileDrafts = require("../lib/drafts"),
promiseList = require("../lib/promises");
module.exports = function (rootPath) {
return new Promise(function(resolve, reject) {
var siteData = JSON.parse(fs.readFileSync(rootPath + "/site.json", "utf8"));
var gulpVersion = require("gulp/package").version;
var compileOptionsObj = compileOptions(rootPath);
glob(rootPath + "/build/content/**/*.json", {
cwd: "."
}, function (err, files) {
if (err) {
reject(err);
} else {
var tagPosts = {},
posts = [],
allPosts = [];
files.forEach(function (file) {
var fileData = JSON.parse(fs.readFileSync(file, "utf8"));
if (fileData.status && fileData.status === "draft" && !compileDrafts()) {
return;
}
if (!fileData.tags || !fileData.date || !fileData.template) {
return;
}
// check and fill-in missing file meta data
fileData = compileOptionsObj.checkContent(fileData);
var metaData = {
title: fileData.title,
description: resolvePaths.resolve(fileData.body, "../.."),
url: "../../" + fileData.slug + "/",
tagStr: fileData.tags,
tags: (fileData.tags ? tags.getTagsAsLinks("../..", fileData.tags) : undefined),
date: fileData.date,
post_class: "post" + (fileData.tags ? tags.getTagClasses(fileData.tags) : fileData.slug),
meta: fileData
};
if (fileData.date && fileData.template === "post.hbs") {
posts.push(metaData);
}
if (fileData.tags) {
var tagList = fileData.tags.split(" ");
tagList.forEach(function (tag) {
if (tagPosts[tag]) {
tagPosts[tag].push(metaData);
} else {
tagPosts[tag] = [metaData];
}
});
}
});
if (_.size(tagPosts)) {
var promises = [];
posts.sort(dates.sortFunc);
allPosts = _.cloneDeep(posts);
for (var tag in tagPosts) {
// sort the tag posts
tagPosts[tag].sort(dates.sortFunc);
var templateData = {
date: moment().format("YYYY-MM-DD"),
resourcePath: "../..",
generator: "Gulp " + gulpVersion,
meta_title: siteData.title,
url: "../..",
site: siteData,
posts: tagPosts[tag],
body_class: "home-template",
rss: "../.." + siteData.rss,
tag: tag,
allDates: dates.getAllDatesAsLinks("../..", allPosts),
allTags: tags.getAllTagsAsLinks("../..", allPosts)
};
if (siteData.maxItems && tagPosts[tag].length > siteData.maxItems) {
// how many pages do we need to create?
var totalPages = Math.ceil(tagPosts[tag].length / siteData.maxItems);
// shorten posts
var paginatedPosts = tagPosts[tag].splice(siteData.maxItems);
for (var i = 1; i < totalPages; i++) {
var pageNumber = i + 1;
var nextPosts = paginatedPosts.splice(0, siteData.maxItems);
// update the resource paths
nextPosts.forEach(function (post) {
post.description = resolvePaths.resolve(post.meta.body, "../../../..");
post.url = "../../../../" + post.meta.slug;
});
// create custom template data for this paginated page
var pageTemplateData = _.cloneDeep(templateData);
_.extend(pageTemplateData, {
posts: nextPosts,
resourcePath: "../../../..",
url: "../../../..",
rss: "../../../.." + siteData.rss,
allDates: dates.getAllDatesAsLinks("../../../..", allPosts),
allTags: tags.getAllTagsAsLinks("../../../..", allPosts)
});
delete pageTemplateData.pages;
// add pagination data
if (pageNumber === 2) {
pageTemplateData.prevUrl = "../../";
} else {
pageTemplateData.prevUrl = "../" + (pageNumber - 1);
}
if (pageNumber < totalPages) {
pageTemplateData.nextUrl = "../" + (pageNumber + 1);
}
pageTemplateData.totalPages = totalPages;
promises.push(new Promise(function (resolve, reject) {
gulp.src(rootPath + "/src/templates/index.hbs")
.pipe(compileHandlebars(pageTemplateData, compileOptionsObj))
.pipe(rename("index.html"))
.pipe(gulp.dest(rootPath + "/build/tag/" + tag + "/page/" + pageNumber))
.on("error", reject)
.on("end", resolve);
}));
}
// update template
templateData.nextUrl = "../../tag/" + tag + "/page/2";
templateData.totalPages = totalPages;
}
promises.push(new Promise(function (resolve, reject) {
gulp.src(rootPath + "/src/templates/index.hbs")
.pipe(compileHandlebars(templateData, compileOptionsObj))
.pipe(rename("index.html"))
.pipe(gulp.dest(rootPath + "/build/tag/" + tag))
.on("error", reject)
.on("end", resolve);
}));
}
Promise.all(promiseList.filter(promises))
.then(function () {
resolve();
}, function (err) {
reject(err);
});
} else {
resolve();
}
}
});
});
};
================================================
FILE: gulp/lib/dates.js
================================================
"use strict";
var moment = require("moment");
module.exports = {
getDateAsLink: function (path, dateMonth, dateStr) {
if (!dateMonth) {
return undefined;
}
return "" + dateStr + "";
},
getAllDatesAsLinks: function (path, posts) {
var allDates = {},
allDatesArray = [];
posts.forEach(function (post) {
var dateMonth = post.date.substr(0, 7); //2014-12
if (!allDates[dateMonth]) {
allDates[dateMonth] = path + "/date/" + dateMonth;
}
});
var keys = Object.keys(allDates);
if (keys.length) {
keys.sort();
keys.forEach(function (key) {
allDatesArray.push({
dateMonth: key,
dateStr: moment(key, "YYYY-MM").format("MMMM YYYY"),
dateLink: allDates[key]
});
});
}
return allDatesArray;
},
sortFunc: function (a, b) {
var timeA = new Date(a.date).getTime(),
timeB = new Date(b.date).getTime();
if (timeA > timeB) return -1;
if (timeA === timeB) return 0;
if (timeA < timeB) return 1;
}
};
================================================
FILE: gulp/lib/downzero.js
================================================
"use strict";
// Functions to imitate the behavior of Downsize@0.0.5 with "words: "0"" (heavily based on Downsize)
// This is used to extract the first image from the post content which is used as a header
var stack, tagName, tagBuffer, truncatedText, parseState, pointer,
states = {
unitialized: 0,
tag_commenced: 1,
tag_string: -1,
tag_string_single: -2,
comment: -3
},
voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input",
"keygen", "link", "meta", "param", "source", "track", "wbr"
];
function getTagName(tag) {
var tagName = (tag || "").match(/<\/*([a-z0-9\:\-\_]+)/i);
return tagName ? tagName[1] : null;
}
function closeTag(openingTag) {
var tagName = (getTagName(openingTag)) ? "" + getTagName(openingTag) + ">" : "";
return tagName;
}
function downzero(text) {
stack = [];
tagName = "";
tagBuffer = "";
truncatedText = "";
parseState = 0;
pointer = 0;
for (; pointer < text.length; pointer += 1) {
if (parseState !== states.unitialized) {
tagBuffer += text[pointer];
}
switch (text[pointer]) {
case "<":
if (parseState === states.unitialized && text[pointer + 1].match(/[a-z0-9\-\_\/\!]/)) {
parseState = states.tag_commenced;
tagBuffer += text[pointer];
}
break;
case "!":
if (parseState === states.tag_commenced && text[pointer - 1] === "<") {
parseState = states.comment;
}
break;
case "\"":
if (parseState === states.tag_string) {
parseState = states.tag_commenced;
} else if (parseState === states.tag_string_single) {
// if double quote is found in a single quote string, ignore it and let the string finish
break;
} else if (parseState !== states.unitialized) {
parseState = states.tag_string;
}
break;
case "'":
if (parseState === states.tag_string_single) {
parseState = states.tag_commenced;
} else if (parseState === states.tag_string) {
break;
} else if (parseState !== states.unitialized) {
parseState = states.tag_string_single;
}
break;
case ">":
if (parseState === states.tag_commenced) {
parseState = states.unitialized;
truncatedText += tagBuffer;
tagName = getTagName(tagBuffer);
if (tagBuffer.match(/<\s*\//) && getTagName(stack[stack.length - 1]) === tagName) {
stack.pop();
} else if (voidElements.indexOf(tagName) < 0 && !tagBuffer.match(/\/\s*>$/)) {
stack.push(tagBuffer);
}
tagBuffer = "";
continue;
}
if (parseState === states.comment && text.substring(pointer - 2, pointer) === "--") {
parseState = states.unitialized;
truncatedText += tagBuffer;
tagBuffer = "";
continue;
}
break;
case "-":
break;
}
if (!parseState) {
break;
}
}
truncatedText += tagBuffer;
while (stack.length) {
truncatedText += closeTag(stack.pop());
}
return truncatedText;
}
module.exports = downzero;
================================================
FILE: gulp/lib/drafts.js
================================================
"use strict";
var minimist = require("minimist");
var knownOptions = {
string: "compile",
default: {
compile: "drafts"
}
};
var options = minimist(process.argv.slice(2), knownOptions);
module.exports = function () {
if (options.compile === "published") {
return false;
}
if (process.env.GSD_PUBLISHED != null && process.env.GSD_PUBLISHED !== "") {
return false;
}
return true;
};
================================================
FILE: gulp/lib/paths.js
================================================
"use strict";
module.exports = {
/**
* Method to update all image and hyperlink URLs to paths relative to the site root
* @param {String} html The HTML containing images and hyperlinks
* @param {String} path The path to the site root
* @returns {String} The HTML with images and hyperlinks updated
*/
resolve: function (html, path) {
// images
html = html.replace(//gim, function (match, p1, p2, p3) {
var imagePath = p2;
if (/^\//.test(imagePath)) {
imagePath = path + (path === "" ? p2.substring(1) : p2);
}
return "
";
});
// links
html = html.replace(//gim, function (match, p1, p2, p3) {
var linkPath = p2;
if (/^\//.test(linkPath)) {
linkPath = path + (path === "" ? p2.substring(1) : p2);
}
return "";
});
return html;
}
};
================================================
FILE: gulp/lib/promises.js
================================================
"use strict";
/* simple filter for mocking a list of promises */
module.exports = {
filter: function (promises) {
return promises;
}
};
================================================
FILE: gulp/lib/remove-dir.js
================================================
"use strict";
var fs = require("fs");
var rmDir = function (dirPath, removeSelf) {
if (removeSelf === undefined) {
removeSelf = true;
}
var files = [];
try {
files = fs.readdirSync(dirPath);
} catch (e) {
return e;
}
if (files.length > 0) {
for (var i = 0; i < files.length; i++) {
var filePath = dirPath + "/" + files[i];
if (fs.statSync(filePath).isFile()) {
fs.unlinkSync(filePath);
} else {
rmDir(filePath);
}
}
}
if (removeSelf) {
fs.rmdirSync(dirPath);
}
};
module.exports = rmDir;
================================================
FILE: gulp/lib/tags.js
================================================
"use strict";
var tagObjToArray = function (tagObj) {
var tags = [];
if (tagObj) {
if (typeof tagObj === "string" || tagObj instanceof String) {
tags = tagObj.split(" ");
} else if (Array.isArray(tagObj)) {
tags = tagObj;
}
}
return tags;
};
module.exports = {
getTagClasses: function (tagObj) {
var tags = tagObjToArray(tagObj);
if (!tags.length) {
return undefined;
}
var classStr = "";
tags.forEach(function (tag) {
classStr += " tag-" + tag;
});
return classStr;
},
getTagsAsLinks: function (path, tagObj) {
var tags = tagObjToArray(tagObj);
if (!tags.length) {
return undefined;
}
var tagLinks = [];
tags.forEach(function (tag) {
tagLinks.push("" + tag + "");
});
return tagLinks.join("");
},
getTagAsLink: function (path, tag) {
if (!tag) {
return undefined;
}
return "" + tag + "";
},
getAllTagsAsLinks: function (path, posts) {
var allTags = {}, allTagsArray = [];
posts.forEach(function (post) {
if (post.tagStr) {
var tagList = tagObjToArray(post.tagStr);
tagList.forEach(function (tag) {
if (!allTags[tag]) {
allTags[tag] = path + (path !== "" ? "/": "") + "tag/" + tag;
}
});
}
});
var keys = Object.keys(allTags);
if (keys.length) {
keys.sort();
keys.forEach(function (key) {
allTagsArray.push({
tag: key,
tagLink: allTags[key]
});
});
}
return allTagsArray;
}
};
================================================
FILE: gulp/tasks/build.js
================================================
"use strict";
var gulp = require("gulp");
gulp.task("build", ["uncss", "concat-js", "image-min", "copy-assets", "minify-html"]);
================================================
FILE: gulp/tasks/clobber.js
================================================
"use strict";
var gulp = require("gulp"),
removeDir = require("../lib/remove-dir");
gulp.task("clobber", function () {
return removeDir("./build");
});
================================================
FILE: gulp/tasks/compile.js
================================================
"use strict";
var gulp = require("gulp"),
Promise = require("bluebird"),
compileRss = require("../lib/compile-rss"),
compileHome = require("../lib/compile-home"),
compilePages = require("../lib/compile-pages"),
compileTags = require("../lib/compile-tags"),
compileDates = require("../lib/compile-dates"),
removeDir = require("../lib/remove-dir");
gulp.task("compile", ["content"], function (done) {
var rootPath = ".";
var compilePromises = [];
// pages
compilePromises.push(compilePages(rootPath));
// tags
compilePromises.push(compileTags(rootPath));
// dates
compilePromises.push(compileDates(rootPath));
// rss feed compilation
compilePromises.push(compileRss(rootPath));
// index page generation
compilePromises.push(compileHome(rootPath));
Promise.all(compilePromises)
.then(function () {
removeDir("./build/content");
done();
}, function () {
// call done even if there are errors
done();
});
});
================================================
FILE: gulp/tasks/concat-js.js
================================================
"use strict";
var gulp = require("gulp"),
concat = require("gulp-concat"),
uglify = require("gulp-uglify"),
rename = require("gulp-rename"),
fs = require("fs");
gulp.task("concat-js", function () {
var siteData = JSON.parse(fs.readFileSync("./site.json", "utf8"));
var jsFiles = ["./src/js/*.js"];
if (siteData.concatJs) {
jsFiles = siteData.concatJs;
}
return gulp.src(jsFiles)
.pipe(concat("combined.js"))
.pipe(uglify())
.pipe(rename({
suffix: ".min"
}))
.pipe(gulp.dest("./build/js"));
});
================================================
FILE: gulp/tasks/content.js
================================================
"use strict";
var gulp = require("gulp"),
markdownToJson = require("gulp-markdown-to-json"),
marked = require("marked");
gulp.task("content", function () {
return gulp.src("./src/content/**/*.md")
.pipe(markdownToJson(marked))
.pipe(gulp.dest("./build/content"));
});
================================================
FILE: gulp/tasks/copy-assets.js
================================================
"use strict";
var gulp = require("gulp");
gulp.task("copy-assets", ["copy-fonts"], function () {
return gulp.src(["./src/favicon.ico", "./src/*.png"])
.pipe(gulp.dest("./build"));
});
================================================
FILE: gulp/tasks/copy-css.js
================================================
"use strict";
var gulp = require("gulp"),
fs = require("fs"),
minifyCSS = require("gulp-minify-css");
gulp.task("copy-css", ["sass"], function () {
var siteData = JSON.parse(fs.readFileSync("./site.json", "utf8"));
var styleSheet = "style.css";
if (siteData.styleSheet) {
styleSheet = siteData.styleSheet;
}
return gulp.src(["./src/css/**/*.css", "!./src/css/**/" + styleSheet])
.pipe(minifyCSS())
.pipe(gulp.dest("./build/css"));
});
================================================
FILE: gulp/tasks/copy-fonts.js
================================================
"use strict";
var gulp = require("gulp");
gulp.task("copy-fonts", function () {
return gulp.src(["src/fonts/**/*"])
.pipe(gulp.dest("./build/fonts"));
});
================================================
FILE: gulp/tasks/default.js
================================================
"use strict";
var gulp = require("gulp"),
runSequence = require("run-sequence");
gulp.task("default", function(done) {
process.env.GSD_PUBLISHED = "true";
runSequence("build", done);
});
================================================
FILE: gulp/tasks/develop.js
================================================
var gulp = require("gulp"),
browserSync = require("browser-sync");
gulp.task("bs-reload", function () {
browserSync.reload();
});
gulp.task("browser-sync", function () {
browserSync.init(["css/*.css", "js/*.js"], {
server: {
baseDir: "./"
}
});
});
gulp.task("serve", function () {
browserSync.init({
server: {
baseDir: "./build"
}
});
});
gulp.task("watch", function () {
gulp.watch(["./src/sass/**/*.scss"], ["sass"]);
gulp.watch(["./src/templates/**/*.hbs"], ["minify-html"]);
gulp.watch(["./src/js/**/*.js"], ["concat-js"]);
gulp.watch(["./src/images/**/*.{gif,jpg,png}"], ["image-min"]);
gulp.watch(["./src/content/**/*.md"], ["minify-html"]);
gulp.watch(["./build/**/*.*"], ["bs-reload"]);
});
gulp.task("develop", ["watch", "serve"]);
================================================
FILE: gulp/tasks/help.js
================================================
"use strict";
var gulp = require("gulp");
gulp.task("help", function () {
console.log("Static site generator using Gulp\n\n");
console.log("Tasks available:\n");
console.log("* default\n");
console.log("* develop\n");
console.log("* clobber\n");
console.log("* image-min\n");
console.log("* sass\n");
console.log("* server\n");
console.log("* help\n");
});
================================================
FILE: gulp/tasks/image-min.js
================================================
"use strict";
var gulp = require("gulp"),
imageminMozjpeg = require("imagemin-mozjpeg"),
pngquant = require("imagemin-pngquant"),
optipng = require("imagemin-optipng"),
fs = require("fs");
gulp.task("image-min", function () {
var siteData = JSON.parse(fs.readFileSync("./site.json", "utf8"));
if (siteData.hasOwnProperty("imageCompression") && !siteData.imageCompression) {
return gulp.src("./src/images/**/*.{png,jpg,jpeg,gif,svg}")
.pipe(gulp.dest("./build/images"));
} else {
return gulp.src("./src/images/**/*.{png,jpg,jpeg,gif,svg}")
.pipe(optipng({ optimizationLevel: 3 })())
.pipe(pngquant({ quality: "65-80", speed: 4 })())
.pipe(imageminMozjpeg({ quality: 70 })())
.pipe(gulp.dest("./build/images"));
}
});
================================================
FILE: gulp/tasks/minify-html.js
================================================
"use strict";
var gulp = require("gulp"),
minifyHTML = require("gulp-minify-html");
gulp.task("minify-html", ["compile"], function () {
return gulp.src("./build/**/*.html")
.pipe(minifyHTML({
comments: false,
spare: true
}))
.pipe(gulp.dest("./build"));
});
================================================
FILE: gulp/tasks/sass.js
================================================
"use strict";
var gulp = require("gulp"),
sass = require("gulp-sass"),
minifyCSS = require("gulp-minify-css"),
rename = require("gulp-rename");
gulp.task("sass", function () {
return gulp.src("./src/sass/**/*.scss")
.pipe(sass())
.pipe(gulp.dest("./src/css"))
.pipe(minifyCSS())
.pipe(rename({
suffix: ".min"
}))
.pipe(gulp.dest("./build/css"));
});
================================================
FILE: gulp/tasks/server.js
================================================
"use strict";
var gulp = require("gulp"),
open = require("gulp-open"),
connect = require("gulp-connect");
gulp.task("server", function () {
connect.server({
root: "build"
});
gulp.src(__filename).pipe(open({uri: "http://localhost:8080"}));
});
================================================
FILE: gulp/tasks/test-ci.js
================================================
"use strict";
var gulp = require("gulp");
gulp.task("test-ci", ["test"], function () {
var coveralls = require("gulp-coveralls");
return gulp.src("./coverage/**/lcov.info")
.pipe(coveralls());
});
================================================
FILE: gulp/tasks/test.js
================================================
"use strict";
var gulp = require("gulp");
gulp.task("test", function (done) {
var mocha = require("gulp-mocha"),
istanbul = require("gulp-istanbul");
gulp.src(["./gulp/lib/*.js", "./install/lib/*.js"])
.pipe(istanbul())
.pipe(istanbul.hookRequire())
.on("finish", function () {
gulp.src(["./gulp/tests/*.spec.js", "./install/tests/*.spec.js"])
.pipe(mocha())
.pipe(istanbul.writeReports())
.on("end", done);
});
});
================================================
FILE: gulp/tasks/uncss.js
================================================
"use strict";
var gulp = require("gulp"),
glob = require("glob"),
uncss = require("gulp-uncss"),
minifyCSS = require("gulp-minify-css"),
rename = require("gulp-rename"),
fs = require("fs");
gulp.task("uncss", ["copy-css"], function () {
var siteData = JSON.parse(fs.readFileSync("./site.json", "utf8"));
var uncssIgnore;
if (siteData.uncssIgnore) {
uncssIgnore = siteData.uncssIgnore;
}
return gulp.src("./src/css/style.css")
.pipe(uncss({
html: glob.sync("./build/**/*.html"),
ignore: uncssIgnore
}))
.pipe(minifyCSS())
.pipe(rename({
suffix: ".min"
}))
.pipe(gulp.dest("./build/css"));
});
================================================
FILE: gulp/tests/compile-dates.spec.js
================================================
"use strict";
var compileDates = require("../lib/compile-dates"),
removeDir = require("../lib/remove-dir"),
expect = require("chai").expect,
mockery = require("mockery"),
Promise = require("bluebird"),
sinon = require("sinon"),
fs = require("fs");
describe("When compiling date pages", function () {
var rootPath = ".tmp/compile-dates";
var doneStub, errorStub;
before(function () {
doneStub = sinon.stub();
errorStub = sinon.stub();
// create the root path
[
".tmp",
".tmp/compile-dates"
].forEach(function (dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
});
// set-up folders:
[
"/src",
"/src/templates",
"/src/templates/partials",
"/build",
"/build/date",
"/build/date/page",
"/build/content",
"/build/content/pages",
"/build/content/posts"
].forEach(function (dir) {
if (!fs.existsSync(rootPath + dir)) {
fs.mkdirSync(rootPath + dir);
}
});
// set-up files:
fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}");
fs.writeFileSync(rootPath + "/src/templates/partials/loop.hbs", "
Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"date\":\"2014-12-05\",\"tags\":\"mocha coke\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); compileDates(rootPath).then(done, errorStub); }); it("Should create the June 2014 date page", function () { expect(fs.existsSync(rootPath + "/build/date/2014-06/index.html")).to.be.true; }); it("Should create the December 2014 date page", function () { expect(fs.existsSync(rootPath + "/build/date/2014-12/index.html")).to.be.true; }); it("Should have the correct date page content for June 2014", function () { var expectedHtml = "Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"date\":\"2014-12-05\",\"status\":\"draft\",\"tags\":\"mocha coke\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); newCompileDates = require("../lib/compile-dates"); newCompileDates(rootPath).then(done, errorStub); }); after(function () { mockery.disable(); }); it("Should have the correct date page content for June 2014", function () { var expectedHtml = "Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post3.json", "{\"slug\":\"test-post3\",\"title\":\"Test post 3\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post4.json", "{\"slug\":\"test-post4\",\"title\":\"Test post 4\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post5.json", "{\"slug\":\"test-post5\",\"title\":\"Test post 5\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post6.json", "{\"slug\":\"test-post6\",\"title\":\"Test post 6\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); fs.writeFileSync(rootPath + "/build/content/posts/test-post7.json", "{\"slug\":\"test-post7\",\"title\":\"Test post 7\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}"); compileDates(rootPath).then(done, errorStub); }); it("Should have the correct date page content for December 2014", function () { var expectedHtml = "Test page content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post1.json", "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); compileHome(rootPath).then(done, errorStub); }); it("Should create the static home page", function () { expect(fs.existsSync(rootPath + "/build/index.html")).to.be.true; }); it("Should have the correct home page content", function () { var expectedHtml = "Test page content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post1.json", "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"template\":\"post.hbs\",\"status\":\"draft\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); newCompileHome = require("../lib/compile-home"); newCompileHome(rootPath).then(done, errorStub); }); after(function () { mockery.disable(); }); it("Should have the correct home page content", function () { var expectedHtml = ""; expect(fs.readFileSync(rootPath + "/build/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When compiling the home page with pagination", function () { before(function (done) { fs.writeFileSync(rootPath + "/site.json", "{ \"title\": \"Test site\", \"maxItems\": \"2\" }", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/pages/test-page.json", "{\"slug\":\"test-page\",\"title\":\"Test page\",\"template\":\"page.hbs\",\"body\":\"Test page content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post1.json", "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"date\":\"2015-02-20\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"date\":\"2015-02-20\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post3.json", "{\"slug\":\"test-post3\",\"title\":\"Test post 3\",\"date\":\"2015-02-20\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post4.json", "{\"slug\":\"test-post4\",\"title\":\"Test post 4\",\"date\":\"2015-02-20\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post5.json", "{\"slug\":\"test-post5\",\"title\":\"Test post 5\",\"date\":\"2015-02-20\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post6.json", "{\"slug\":\"test-post6\",\"title\":\"Test post 6\",\"date\":\"2015-02-20\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); compileHome(rootPath).then(done, errorStub); }); it("Should have the correct home page content", function () { var expectedHtml = "Test page content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/test-post1.json", "{\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); compileHome(rootPath).then(done, errorStub); }); it("Should have the correct home page content", function () { expect(fs.readFileSync(rootPath + "/build/index.html", "utf8")).to.equal(""); }); }); describe("When there are no posts or pages for the home page", function () { beforeEach(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); compileHome(rootPath).then(function () { doneStub(); done(); }, function () { errorStub(); done(); }); }); it("Should call done", function () { expect(doneStub.called).to.be.true; }); }); describe("When a glob error occurs", function () { var globStub, newCompileHome; beforeEach(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); mockery.deregisterAll(); globStub = function (paths, options, callback) { callback({ message: "I threw an error" }, null); }; mockery.registerMock("glob", globStub); newCompileHome = require("../lib/compile-home"); newCompileHome(rootPath).then(function () { done(); }, function (err) { errorStub(err); done(); }); }); it("Should throw a glob error", function () { expect(errorStub.called).to.be.true; }); it("Should throw a specific error", function () { expect(errorStub.calledWith({ message: "I threw an error" })).to.be.true; }); afterEach(function () { mockery.deregisterMock("glob"); mockery.disable(); }); }); }); ================================================ FILE: gulp/tests/compile-options.spec.js ================================================ "use strict"; var compileOptions = require("../lib/compile-options"), Handlebars = require("handlebars"), expect = require("chai").expect; describe("Given the compile options", function () { describe("When using the date helper", function () { beforeEach(function () { Handlebars.registerHelper("date", compileOptions(".").helpers.date); this.html = "{{date format=\"MMM Do, YYYY\"}}"; }); it("Should format the date as MMM Do, YYYY", function () { var template = Handlebars.compile(this.html); var result = template({ date: "2015-10-15" }); expect(result).to.equal("Oct 15th, 2015"); }); }); describe("When using the excerpt helper", function () { describe("When using the excerpt helper with characters", function () { beforeEach(function () { Handlebars.registerHelper("excerpt", compileOptions(".").helpers.excerpt); this.html = "{{{excerpt characters=100}}}...
"; }); it("Should return an excerpt of the given text", function () { var template = Handlebars.compile(this.html); var result = template({ description: "Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut." }); expect(result).to.equal("Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake po...
"); }); }); describe("When using the excerpt helper with words", function () { beforeEach(function () { Handlebars.registerHelper("excerpt", compileOptions(".").helpers.excerpt); this.html = "{{{excerpt words=20}}}...
"; }); it("Should return an excerpt of the given text", function () { var template = Handlebars.compile(this.html); var result = template({ description: "Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut." }); expect(result).to.equal("Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw...
"); }); }); describe("When using the excerpt helper without words or characters", function () { beforeEach(function () { Handlebars.registerHelper("excerpt", compileOptions(".").helpers.excerpt); this.html = "{{{excerpt}}}...
"; }); it("Should return an excerpt of the given text", function () { var template = Handlebars.compile(this.html); var result = template({ description: "Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut." }); expect(result).to.equal("Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut....
"); }); }); }); describe("When using the content helper", function () { describe("When using the content helper with characters", function () { beforeEach(function () { Handlebars.registerHelper("content", compileOptions(".").helpers.content); this.html = "{{{content characters=100}}}"; }); it("Should return an content of the given text", function () { var template = Handlebars.compile(this.html); var result = template({ description: "Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut.
" }); expect(result).to.equal("Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake po
"); }); }); describe("When using the content helper with words", function () { beforeEach(function () { Handlebars.registerHelper("content", compileOptions(".").helpers.content); this.html = "{{{content words=20}}}"; }); it("Should return an content of the given text", function () { var template = Handlebars.compile(this.html); var result = template({ description: "Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut.
" }); expect(result).to.equal("Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw
"); }); }); describe("When using the content helper without words or characters", function () { beforeEach(function () { Handlebars.registerHelper("content", compileOptions(".").helpers.content); this.html = "{{{content}}}"; }); it("Should return an content of the given text", function () { var template = Handlebars.compile(this.html); var result = template({ description: "Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut.
" }); expect(result).to.equal("Gingerbread marshmallow fruitcake topping jelly-o halvah. Dragée icing cheesecake. Apple pie cake powder biscuit gingerbread tart gingerbread bonbon. Bear claw danish cake pie gummi bears macaroon tart jujubes toffee Candy canes brownie oat cake gummi bears cupcake powder donut.
"); }); }); describe("When using the content helper with no words", function () { beforeEach(function () { Handlebars.registerHelper("content", compileOptions(".").helpers.content); this.html = "{{{content words=0}}}"; }); it("Should return an content of the given text", function () { var template = Handlebars.compile(this.html); var result = template({ description: "
Image

Test page content.
Blah blah blah
" }; fileData = compileOptions().checkContent(fileData); expect(fileData).to.deep.equal({ slug: "test-page-content-blah-blah-blah", template: "page.hbs", title: "Test page content. Blah blah blah", body: "Test page content.
Blah blah blah
" }); }); it("Should check the content and add missing post meta data", function () { var fileData = { date: "2015-02-23", body: "Test post content.
Blah blah blah
" }; fileData = compileOptions().checkContent(fileData); expect(fileData).to.deep.equal({ slug: "test-post-content-blah-blah-blah", template: "post.hbs", title: "Test post content. Blah blah blah", date: "2015-02-23", body: "Test post content.
Blah blah blah
" }); }); }); describe("When using the or helper", function () { beforeEach(function () { Handlebars.registerHelper("or", compileOptions(".").helpers.or); this.html = "Test page content
\"}", { encoding: "utf8" }); compilePages(rootPath).then(done, errorStub); }); it("Should create the static page", function () { expect(fs.existsSync(rootPath + "/build/test-page/index.html" )).to.be.true; }); it("Should have the correct page content", function () { expect(fs.readFileSync(rootPath + "/build/test-page/index.html", "utf8")).to.equal( "Test page content
Test page content
\"}", { encoding: "utf8" }); compilePages(rootPath).then(done, errorStub); }); it("Should create the static page", function () { expect(fs.existsSync(rootPath + "/build/test-page-no-slug/index.html" )).to.be.true; }); it("Should have the correct page content", function () { expect(fs.readFileSync(rootPath + "/build/test-page-no-slug/index.html", "utf8")).to.equal( "Test page content
Test post content
\"}", { encoding: "utf8" }); compilePages(rootPath).then(done, errorStub); }); it("Should create the static post", function () { expect(fs.existsSync(rootPath + "/build/test-post/index.html" )).to.be.true; }); it("Should have the correct post content", function () { expect(fs.readFileSync(rootPath + "/build/test-post/index.html", "utf8")).to.equal( "Test post content
Test post content
\"}", { encoding: "utf8" }); compilePages(rootPath).then(done, errorStub); }); it("Should create the static post", function () { expect(fs.existsSync(rootPath + "/build/test-post-no-slug/index.html" )).to.be.true; }); it("Should have the correct post content", function () { expect(fs.readFileSync(rootPath + "/build/test-post-no-slug/index.html", "utf8")).to.equal( "Test post content
Test page content
\"}", { encoding: "utf8" }); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); mockery.deregisterAll(); promisesListStub = { filter: function () { return [Promise.reject( "An error occurred" )]; } }; mockery.registerMock( "../lib/promises", promisesListStub); newCompilePages = require( "../lib/compile-pages"); newCompilePages(rootPath).then(function () { done(); }, function (err) { errorStub(err); done(); }); }); it("Should call the error function", function () { expect(errorStub.called).to.be.true; }); afterEach(function () { mockery.deregisterMock( "../lib/promises"); mockery.disable(); }); }); describe("When compiling posts and excluding draft templates", function () { var minimistStub, newCompilePages; before(function (done) { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); minimistStub = function () { return { compile: "published" }; }; mockery.registerAllowable("../lib/drafts"); mockery.registerMock("minimist", minimistStub); fs.writeFileSync(rootPath + "/build/content/posts/test-draft-post.json", "{\"slug\":\"test-draft-post\",\"title\":\"Test draft post\",\"date\":\"2014-06-11\",\"template\":\"post.hbs\",\"status\":\"draft\",\"body\":\"Test draft post content
\"}", { encoding: "utf8" }); newCompilePages = require("../lib/compile-pages"); newCompilePages(rootPath).then(done, errorStub); }); after(function () { mockery.disable(); }); it("Should not create the static post", function () { expect(fs.existsSync(rootPath + "/build/test-draft-post/index.html" )).to.be.false; }); }); describe("When compiling posts and including tags", function () { before(function (done) { fs.writeFileSync(rootPath + "/src/templates/post.hbs", "Test tagged post content
\"}", { encoding: "utf8" }); compilePages(rootPath).then(done, errorStub); }); it("Should have the correct post content", function () { expect(fs.readFileSync(rootPath + "/build/test-tagged-post/index.html", "utf8")).to.equal( "Test tagged post content
Test page content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post1.json", "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"template\":\"post.hbs\",\"date\":\"2015-01-10\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"template\":\"post.hbs\",\"date\":\"2015-01-20\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); compileRss(rootPath).then(done, errorStub); }); it("Should create the static RSS xml file", function () { expect(fs.existsSync(rootPath + "/build/rss.xml")).to.be.true; }); it("Should have the correct RSS content", function () { expect(fs.readFileSync(rootPath + "/build/rss.xml", "utf8")).to.match(/Test post 2/); }); }); describe("When compiling the RSS feed excluding draft posts", function () { var minimistStub, newCompileRss; before(function (done) { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); minimistStub = function () { return { compile: "published" }; }; mockery.registerAllowable("../lib/drafts"); mockery.registerMock("minimist", minimistStub); fs.writeFileSync(rootPath + "/build/content/posts/test-post1.json", "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"template\":\"post.hbs\",\"date\":\"2015-01-10\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"template\":\"post.hbs\",\"date\":\"2015-01-20\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post3.json", "{\"slug\":\"test-post3\",\"title\":\"Test post 3\",\"template\":\"post.hbs\",\"date\":\"2015-01-30\",\"status\":\"draft\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); newCompileRss = require("../lib/compile-rss"); newCompileRss(rootPath).then(done, errorStub); }); after(function () { mockery.disable(); }); it("Should have the correct RSS content", function () { expect(fs.readFileSync(rootPath + "/build/rss.xml", "utf8")).to.not.match(/Test post 3/); }); }); describe("When there are no posts for the RSS feed", function () { beforeEach(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); compileRss(rootPath).then(function () { doneStub(); done(); }, function () { errorStub(); done(); }); }); it("Should call done", function () { expect(doneStub.called).to.be.true; }); }); describe("When a fs writeFile error occurs", function () { var fsStub, globStub, newCompileRss; before(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); globStub = function (paths, options, callback) { callback(null, ["./file1", "./file2", "/file3"]); }; fsStub = { readdir: function (path, callback) { callback(null, ["./file1", "./file2", "/file3"]); }, readFileSync: function () { return "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"template\":\"post.hbs\",\"date\":\"2015-01-10\",\"body\":\"Test post content
\"}"; }, writeFile: function (path, content, options, callback) { callback({ message: "I threw an error" }, null); } }; mockery.registerMock("fs", fsStub); mockery.registerMock("glob", globStub); newCompileRss = require("../lib/compile-rss"); newCompileRss(rootPath).then(function () { //done(); }, function (err) { errorStub(err); done(); }); }); it("Should throw the writeFile error", function () { expect(errorStub.called).to.be.true; }); it("Should throw a specific error", function () { expect(errorStub.calledWith({ message: "I threw an error" })).to.be.true; }); after(function () { mockery.disable(); }); }); describe("When a glob error occurs", function () { var globStub, newCompileRss; before(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); globStub = function (paths, options, callback) { callback({ message: "I threw an error" }, null); }; mockery.registerMock("glob", globStub); newCompileRss = require("../lib/compile-rss"); newCompileRss(rootPath).then(function () { done(); }, function (err) { errorStub(err); done(); }); }); it("Should throw a glob error", function () { expect(errorStub.called).to.be.true; }); it("Should throw a specific error", function () { expect(errorStub.calledWith({ message: "I threw an error" })).to.be.true; }); after(function () { mockery.disable(); }); }); after(function () { removeDir(rootPath); }); }); ================================================ FILE: gulp/tests/compile-tags.spec.js ================================================ "use strict"; var compileTags = require("../lib/compile-tags"), removeDir = require("../lib/remove-dir"), expect = require("chai").expect, mockery = require("mockery"), Promise = require("bluebird"), sinon = require("sinon"), fs = require("fs"); describe("When compiling tag pages", function () { var rootPath = ".tmp/compile-tags"; var doneStub, errorStub; before(function () { doneStub = sinon.stub(); errorStub = sinon.stub(); // create the root path [ ".tmp", ".tmp/compile-tags" ].forEach(function (dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); // set-up folders: [ "/src", "/src/templates", "/src/templates/partials", "/build", "/build/tag", "/build/tag/page", "/build/content", "/build/content/pages", "/build/content/posts" ].forEach(function (dir) { if (!fs.existsSync(rootPath + dir)) { fs.mkdirSync(rootPath + dir); } }); // set-up files: fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/partials/loop.hbs", "Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"date\":\"2014-12-05\",\"tags\":\"mocha coke\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); compileTags(rootPath).then(done, errorStub); }); it("Should create the mocha tag page", function () { expect(fs.existsSync(rootPath + "/build/tag/mocha/index.html")).to.be.true; }); it("Should create the coke tag page", function () { expect(fs.existsSync(rootPath + "/build/tag/coke/index.html")).to.be.true; }); it("Should have the correct mocha tag page content", function () { var expectedHtml = ""; expect(fs.readFileSync(rootPath + "/build/tag/mocha/index.html", "utf8")).to.equal(expectedHtml); }); it("Should have the correct coke tag page content", function () { var expectedHtml = ""; expect(fs.readFileSync(rootPath + "/build/tag/coke/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When an error occurs with the promises", function () { var promisesListStub, newCompileTags; beforeEach(function (done) { errorStub.reset(); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); mockery.deregisterAll(); promisesListStub = { filter: function () { return [Promise.reject("An error occurred")]; } }; mockery.registerMock("../lib/promises", promisesListStub); newCompileTags = require("../lib/compile-tags"); newCompileTags(rootPath).then(function () { done(); }, function (err) { errorStub(err); done(); }); }); it("Should call the error function", function () { expect(errorStub.called).to.be.true; }); afterEach(function () { mockery.deregisterMock("../lib/promises"); mockery.disable(); }); }); describe("When compiling tag pages and excluding draft templates", function () { var minimistStub, newCompileTags; before(function (done) { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); minimistStub = function () { return { compile: "published" }; }; mockery.registerAllowable("../lib/drafts"); mockery.registerMock("minimist", minimistStub); fs.writeFileSync(rootPath + "/build/content/posts/test-post1.json", "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"date\":\"2014-06-11\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"date\":\"2014-12-05\",\"status\":\"draft\",\"tags\":\"mocha coke\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); newCompileTags = require("../lib/compile-tags"); newCompileTags(rootPath).then(done, errorStub); }); after(function () { mockery.disable(); }); it("Should have the correct mocha tag page content", function () { var expectedHtml = ""; expect(fs.readFileSync(rootPath + "/build/tag/mocha/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When compiling tag posts with pagination", function () { before(function (done) { fs.writeFileSync(rootPath + "/site.json", "{ \"title\": \"Test site\", \"maxItems\": \"2\" }", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post1.json", "{\"slug\":\"test-post1\",\"title\":\"Test post 1\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post2.json", "{\"slug\":\"test-post2\",\"title\":\"Test post 2\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post3.json", "{\"slug\":\"test-post3\",\"title\":\"Test post 3\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post4.json", "{\"slug\":\"test-post4\",\"title\":\"Test post 4\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post5.json", "{\"slug\":\"test-post5\",\"title\":\"Test post 5\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post6.json", "{\"slug\":\"test-post6\",\"title\":\"Test post 6\",\"date\":\"2014-12-05\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-post7.json", "{\"slug\":\"test-post7\",\"title\":\"Test post 7\",\"tags\":\"mocha\",\"template\":\"post.hbs\",\"body\":\"Test post content
\"}", { encoding: "utf8" }); compileTags(rootPath).then(done, errorStub); }); it("Should have the correct mocha tag page content", function () { var expectedHtml = "
This is the first paragraph.
"; }); it("Should strip just the first tag from the html", function () { expect(downzero(this.html)).to.equal("

This is the \"first paragraph\".
"; }); it("Should strip just the first tag from the html", function () { expect(downzero(this.html)).to.equal("

This is the first paragraph.
"; }); it("Should strip just the first tag from the html", function () { expect(downzero(this.html)).to.equal("
This is a sentence with a link.
This sentence contains an
image.
Posts tagged: {{tag}}
{{else}} {{#if dateStr}}Posts dated: {{dateStr}}
{{else}}{{site.description}}
{{/if}} {{/if}}