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 [![Build Status](https://travis-ci.org/ducksoupdev/gulp-site-generator.svg?branch=master)](https://travis-ci.org/ducksoupdev/gulp-site-generator) [![Coverage Status](https://coveralls.io/repos/ducksoupdev/gulp-site-generator/badge.svg?branch=master)](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)) ? "" : ""; 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", ""); fs.writeFileSync(rootPath + "/src/templates/partials/pagination.hbs", ""); fs.writeFileSync(rootPath + "/src/templates/index.hbs", "
Posts dated: {{dateStr}}{{> loop}}{{> pagination}}
"); }); after(function () { removeDir(rootPath); }); describe("When compiling date pages", function () { before(function (done) { 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

\"}"); 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 = "
" + "Posts dated: June 2014" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/date/2014-06/index.html", "utf8")).to.equal(expectedHtml); }); it("Should have the correct date page content for December 2014", function () { var expectedHtml = "
" + "Posts dated: December 2014" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/date/2014-12/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When an error occurs with the promises", function () { var promisesListStub, newCompileDates; 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); newCompileDates = require("../lib/compile-dates"); newCompileDates(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 date pages and excluding draft templates", function () { var minimistStub, newCompileDates; 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

\"}"); 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 = "
" + "Posts dated: June 2014" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/date/2014-06/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When compiling date posts with pagination", function () { before(function (done) { fs.writeFileSync(rootPath + "/site.json", "{ \"title\": \"Test site\", \"maxItems\": \"2\" }"); 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

\"}"); 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 = "
" + "Posts dated: December 2014" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/date/2014-12/index.html", "utf8")).to.equal(expectedHtml); }); it("Should create the second paginated date page for December 2014", function () { expect(fs.existsSync(rootPath + "/build/date/2014-12/page/2/index.html")).to.be.true; }); it("Should create the third paginated date page for December 2014", function () { expect(fs.existsSync(rootPath + "/build/date/2014-12/page/3/index.html")).to.be.true; }); it("Should have the correct content for the second paginated page", function () { var expectedHtml = "
" + "Posts dated: December 2014" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/date/2014-12/page/2/index.html", "utf8")).to.equal(expectedHtml); }); it("Should have the correct content for the third paginated page", function () { var expectedHtml = "
" + "Posts dated: December 2014" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/date/2014-12/page/3/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When there are no posts for the date pages", function () { beforeEach(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}"); compileDates(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, newCompileDates; beforeEach(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}"); 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); newCompileDates = require("../lib/compile-dates"); newCompileDates(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-home.spec.js ================================================ "use strict"; var compileHome = require("../lib/compile-home"), removeDir = require("../lib/remove-dir"), expect = require("chai").expect, mockery = require("mockery"), sinon = require("sinon"), Promise = require("bluebird"), fs = require("fs"); describe("Given the home page", function () { var rootPath = ".tmp/compile-home"; var doneStub, errorStub; before(function () { doneStub = sinon.stub(); errorStub = sinon.stub(); // create the root path [ ".tmp", ".tmp/compile-home" ].forEach(function (dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); // set-up folders: [ "/src", "/src/templates", "/src/templates/partials", "/build", "/build/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", "", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/partials/pagination.hbs", "", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/index.hbs", "
{{> loop}}{{> pagination}}
", { encoding: "utf8" }); }); after(function () { removeDir(rootPath); }); describe("When compiling the home page", function () { before(function (done) { 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\",\"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 = "
" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When an error occurs with the promises", function () { var promisesListStub, newCompileHome; beforeEach(function (done) { mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); mockery.deregisterAll(); promisesListStub = { filter: function () { return [Promise.reject("An error occurred")]; } }; mockery.registerMock("../lib/promises", promisesListStub); newCompileHome = require("../lib/compile-home"); newCompileHome(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 the home page excluding draft templates", function () { var minimistStub, newCompileHome; 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/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\",\"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 = "
" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/index.html", "utf8")).to.equal(expectedHtml); }); it("Should create the second paginated home page", function () { expect(fs.existsSync(rootPath + "/build/page/2/index.html")).to.be.true; }); it("Should create the third paginated home page", function () { expect(fs.existsSync(rootPath + "/build/page/3/index.html")).to.be.true; }); it("Should have the correct home page content for the second paginated page", function () { var expectedHtml = "
" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/page/2/index.html", "utf8")).to.equal(expectedHtml); }); it("Should have the correct home page content for the third paginated page", function () { var expectedHtml = "
" + "" + "" + "
"; expect(fs.readFileSync(rootPath + "/build/page/3/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When compiling the home page content with no front matter", function () { before(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{ \"title\": \"Test site\" }", { encoding: "utf8" }); [ "/src", "/src/templates", "/src/templates/partials", "/build", "/build/content" ].forEach(function (dir) { if (!fs.existsSync(rootPath + dir)) { fs.mkdirSync(rootPath + dir); } }); fs.writeFileSync(rootPath + "/src/templates/partials/loop.hbs", "{{#each pages}}
  • {{title}}
  • {{/each}}{{#each posts}}
  • {{title}}
  • {{/each}}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/index.hbs", "
      {{> loop}}
    ", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/test-page.json", "{\"body\":\"

    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

    " }); expect(result).to.equal("

    \"\"

    "); }); }); }); describe("When using the resolve helper", function () { beforeEach(function () { Handlebars.registerHelper("resolve", compileOptions(".").helpers.resolve); this.html = ""; }); it("Should return a resolved path", function () { var template = Handlebars.compile(this.html); var result = template({ resourcePath: ".." }); expect(result).to.equal(""); }); it("Should return a resolved path if no resourcePath is set", function () { var template = Handlebars.compile(this.html); var result = template({}); expect(result).to.equal(""); }); }); describe("When using the checkContent function", function () { it("Should check the content and add missing page meta data", function () { var fileData = { body: "

    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 = "
    {{#if (or val1 val2)}}Values matched{{else}}Values not matched{{/if}}
    "; }); it("Should output the correct html when the values exist", function () { var template = Handlebars.compile(this.html); var result = template({ val1: "Test" }); expect(result).to.equal("
    Values matched
    "); }); it("Should output the correct html when the values do not exist", function () { var template = Handlebars.compile(this.html); var result = template({}); expect(result).to.equal("
    Values not matched
    "); }); }); }); ================================================ FILE: gulp/tests/compile-pages.spec.js ================================================ "use strict"; var compilePages = require("../lib/compile-pages"), removeDir = require("../lib/remove-dir"), expect = require("chai").expect, mockery = require("mockery"), Promise = require("bluebird"), sinon = require("sinon"), fs = require("fs"); describe("Given pages and posts", function () { var rootPath = ".tmp/compile-pages"; var doneStub, errorStub; before(function () { doneStub = sinon.stub(); errorStub = sinon.stub(); // create the root path [ ".tmp", ".tmp/compile-pages" ].forEach(function (dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); // set-up folders: [ "/src", "/src/templates", "/src/templates/partials", "/build", "/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\",\"testSiteVar\":\"my test value\"}", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/page.hbs", "

    {{post.title}}

    {{{post.body}}}

    {{site.testSiteVar}}

    {{post.site.testSiteVar}}

    ", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/post.hbs", "

    {{post.title}}

    {{{post.body}}}

    {{site.testSiteVar}}

    {{post.site.testSiteVar}}

    ", { encoding: "utf8" }); }); after(function () { removeDir(rootPath); }); describe("When compiling a page", function () { before(function (done) { fs.writeFileSync(rootPath + "/build/content/pages/test-page.json", "{\"slug\":\"test-page\",\"title\":\"Test page\",\"template\":\"page.hbs\",\"body\":\"

    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

    Test page content

    my test value

    my test value

    " ); }); }); describe("When compiling a page with no slug", function () { before(function (done) { fs.writeFileSync(rootPath + "/build/content/pages/test-page-no-slug.json", "{\"title\":\"Test page no slug\",\"template\":\"page.hbs\",\"body\":\"

    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 no slug

    Test page content

    my test value

    my test value

    " ); }); }); describe("When compiling a post", function () { before(function (done) { fs.writeFileSync(rootPath + "/build/content/posts/test-post.json", "{\"slug\":\"test-post\",\"title\":\"Test post\",\"date\":\"2014-06-11\",\"template\":\"post.hbs\",\"body\":\"

    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

    Test post content

    my test value

    my test value

    " ); }); }); describe("When compiling a post with no slug", function () { before(function (done) { fs.writeFileSync(rootPath + "/build/content/posts/test-post-no-slug.json", "{\"title\":\"Test post no slug\",\"date\":\"2014-06-11\",\"template\":\"post.hbs\",\"body\":\"

    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 no slug

    Test post content

    my test value

    my test value

    " ); }); }); describe("When an error occurs with the promises", function () { var promisesListStub, newCompilePages; beforeEach(function (done) { fs.writeFileSync(rootPath + "/build/content/pages/test-page.json", "{\"slug\":\"test-page\",\"title\":\"Test page\",\"template\":\"page.hbs\",\"body\":\"

    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", "

    {{post.title}}

    {{{post.body}}}
    ", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/build/content/posts/test-tagged-post.json", "{\"slug\":\"test-tagged-post\",\"title\":\"Test tagged post\",\"date\":\"2014-06-11\",\"template\":\"post.hbs\",\"tags\":\"tag1 tag2\",\"body\":\"

    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

    Test tagged post content

    " ); }); }); describe("When there are no posts or pages to compile", function () { beforeEach(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); compilePages(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, newCompilePages; 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); newCompilePages = require( "../lib/compile-pages"); newCompilePages(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-rss.spec.js ================================================ "use strict"; var compileRss = require("../lib/compile-rss"), removeDir = require("../lib/remove-dir"), expect = require("chai").expect, mockery = require("mockery"), sinon = require("sinon"), fs = require("fs"); describe("Given the RSS feed", function () { var rootPath = ".tmp/compile-rss"; var doneStub, errorStub; before(function () { doneStub = sinon.stub(); errorStub = sinon.stub(); // create the root path [ ".tmp", ".tmp/compile-rss" ].forEach(function (dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); // set-up folders: [ "/build", "/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" }); }); describe("When compiling the rss feed", function () { before(function (done) { 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\",\"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", "", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/partials/pagination.hbs", "", { encoding: "utf8" }); fs.writeFileSync(rootPath + "/src/templates/index.hbs", "
    {{> loop}}{{> pagination}}
    ", { encoding: "utf8" }); }); after(function () { removeDir(rootPath); }); describe("When compiling tag pages", function () { before(function (done) { 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\",\"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 = "
    " + "" + "" + "
    "; expect(fs.readFileSync(rootPath + "/build/tag/mocha/index.html", "utf8")).to.equal(expectedHtml); }); it("Should create the second paginated mocha tag page", function () { expect(fs.existsSync(rootPath + "/build/tag/mocha/page/2/index.html")).to.be.true; }); it("Should create the third paginated mocha tag page", function () { expect(fs.existsSync(rootPath + "/build/tag/mocha/page/3/index.html")).to.be.true; }); it("Should have the correct content for the second paginated page", function () { var expectedHtml = "
    " + "" + "" + "
    "; expect(fs.readFileSync(rootPath + "/build/tag/mocha/page/2/index.html", "utf8")).to.equal(expectedHtml); }); it("Should have the correct content for the third paginated page", function () { var expectedHtml = "
    " + "" + "" + "
    "; expect(fs.readFileSync(rootPath + "/build/tag/mocha/page/3/index.html", "utf8")).to.equal(expectedHtml); }); }); describe("When there are no posts for the tag pages", function () { beforeEach(function (done) { removeDir(rootPath); fs.mkdirSync(rootPath); fs.writeFileSync(rootPath + "/site.json", "{\"title\":\"Test site\"}", { encoding: "utf8" }); compileTags(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, newCompileTags; 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); newCompileTags = require("../lib/compile-tags"); newCompileTags(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/dates.spec.js ================================================ "use strict"; var dates = require("../lib/dates"), expect = require("chai").expect; describe("When parsing dates", function () { beforeEach(function () { this.dateMonth = "2014-06"; this.dateStr = "June 2014"; this.posts = [ { date: "2014-06-11" }, { date: "2014-12-11" }, { date: "2014-03-11" }, { date: "2014-06-11" } ]; }); it("Should sort dates correctly", function () { this.posts.sort(dates.sortFunc); expect(this.posts).to.deep.equal([ { date: "2014-12-11" }, { date: "2014-06-11" }, { date: "2014-06-11" }, { date: "2014-03-11" } ]); }); it("Should create date link", function () { expect(dates.getDateAsLink(".", this.dateMonth, this.dateStr)).to.equal("June 2014"); }); it("Should return undefined if no date exists", function () { expect(dates.getDateAsLink(".", null)).to.be.undefined; }); it("Should get all dates as links", function () { expect(dates.getAllDatesAsLinks(".", this.posts)).to.deep.equal([ { dateMonth: "2014-03", dateStr: "March 2014", dateLink: "./date/2014-03" }, { dateMonth: "2014-06", dateStr: "June 2014", dateLink: "./date/2014-06" }, { dateMonth: "2014-12", dateStr: "December 2014", dateLink: "./date/2014-12" } ]); }); }); ================================================ FILE: gulp/tests/downzero.spec.js ================================================ "use strict"; var downzero = require("../lib/downzero"), expect = require("chai").expect; describe("When extracting html tags with double quotes", function () { beforeEach(function () { this.html = "

    This is the first paragraph.

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

    "); }); }); describe("When extracting html tags with single quotes", function () { beforeEach(function () { this.html = "

    This is the \"first paragraph\".

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

    "); }); }); describe("When extracting html tags with comments", function () { beforeEach(function () { this.html = "

    This is the first paragraph.

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

    "); }); }); ================================================ FILE: gulp/tests/drafts.spec.js ================================================ "use strict"; var compileDrafts, mockery = require("mockery"), expect = require("chai").expect; describe("When working with compile options", function () { var minimistStub; describe("When not working in draft mode", function () { before(function () { mockery.enable({ warnOnReplace: false, useCleanCache: true }); minimistStub = function () { return { compile: "published" }; }; mockery.registerAllowable("../lib/drafts"); mockery.registerMock("minimist", minimistStub); compileDrafts = require("../lib/drafts"); }); after(function () { mockery.disable(); }); it("Should return false if not compiling drafts", function () { expect(compileDrafts()).to.be.false; }); }); describe("When working in draft mode", function () { before(function () { mockery.enable({ warnOnReplace: false, useCleanCache: true }); minimistStub = function () { return { compile: "drafts" }; }; mockery.registerAllowable("../lib/drafts"); mockery.registerMock("minimist", minimistStub); compileDrafts = require("../lib/drafts"); }); after(function () { mockery.disable(); }); it("Should return true if compiling drafts", function () { expect(compileDrafts()).to.be.true; }); }); }); ================================================ FILE: gulp/tests/paths.spec.js ================================================ "use strict"; var paths = require("../lib/paths"), expect = require("chai").expect; describe("When replacing paths", function () { beforeEach(function () { this.html = "

    This is a sentence with a link.

    This sentence contains an \"\" image.

    "; }); it("Should replace the link paths with relative links", function () { expect(paths.resolve(this.html, "../..")).to.match(/\.\.\/\.\.\/my-test-page/); }); it("Should replace the image paths with relative links", function () { expect(paths.resolve(this.html, "../..")).to.match(/\.\.\/\.\.\/images\/test\.png/); }); }); ================================================ FILE: gulp/tests/remove-dir.spec.js ================================================ "use strict"; var removeDir = require("../lib/remove-dir"), expect = require("chai").expect, mockery = require("mockery"), fs = require("fs"); describe("When removing directories", function () { beforeEach(function () { // create the root path [ ".tmp", ".tmp/remove-dir-test" ].forEach(function (dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); var files = { ".tmp/remove-dir-test/file1": "This is test content", ".tmp/remove-dir-test/file2": "This is test content", ".tmp/remove-dir-test/file3": "This is test content" }; for (var file in files) { if (files.hasOwnProperty(file)) { fs.writeFileSync(file, files[file], { encoding: "utf8" }); } } }); after(function() { removeDir(".tmp/remove-dir-test"); }); describe("When the temp directory structure is created", function () { it("Should have a temp directory structure", function () { expect(fs.existsSync(".tmp/remove-dir-test")).to.be.true; }); it("Should have a temp file in the directory", function () { expect(fs.existsSync(".tmp/remove-dir-test/file1")).to.be.true; }); }); describe("When removing the temp directory", function () { beforeEach(function () { removeDir(".tmp/remove-dir-test"); }); it("Should remove the temp directory structure", function () { expect(fs.existsSync(".tmp/remove-dir-test")).to.be.false; }); }); describe("When removing the contents of the temp directory", function () { beforeEach(function () { removeDir(".tmp/remove-dir-test", false); }); it("Should not remove the temp directory structure", function () { expect(fs.existsSync(".tmp/remove-dir-test")).to.be.true; }); it("Should remove the temp file in the directory", function () { expect(fs.existsSync(".tmp/remove-dir-test/file1")).to.be.false; }); }); describe("When a nested directory of files exists", function () { beforeEach(function () { if (!fs.existsSync(".tmp/remove-dir-test/nested-dir")) { fs.mkdirSync(".tmp/remove-dir-test/nested-dir"); } fs.writeFileSync(".tmp/remove-dir-test/nested-dir/file1", "This is test content", { encoding: "utf8" }); }); it("Should have a nested directory structure", function () { expect(fs.existsSync(".tmp/remove-dir-test/nested-dir/file1")).to.be.true; }); describe("When the nested directory is removed", function () { beforeEach(function () { removeDir(".tmp/remove-dir-test"); }); it("Should remove the temp directory structure", function () { expect(fs.existsSync(".tmp/remove-dir-test")).to.be.false; }); }); }); describe("When the readdirSync function throws an error", function () { var fsStub, newRemoveDir, error; before(function () { removeDir(".tmp/remove-dir-test"); mockery.enable({ warnOnReplace: false, warnOnUnregistered: false, useCleanCache: true }); fsStub = { readdirSync: function () { throw new Error("readdirSync error!"); } }; mockery.registerMock("fs", fsStub); newRemoveDir = require("../lib/remove-dir"); error = newRemoveDir(".tmp/remove-dir-test"); }); it("Should throw an error", function () { expect(error instanceof Error).to.be.true; }); it("Should throw a specific error", function () { expect(error.message).to.equal("readdirSync error!"); }); after(function () { mockery.disable(); }); }); }); ================================================ FILE: gulp/tests/tags.spec.js ================================================ "use strict"; var tags = require("../lib/tags"), expect = require("chai").expect; describe("When parsing tags", function () { beforeEach(function () { this.tagStr = "content gulp testing"; this.tagArray = ["content", "gulp", "testing"]; this.posts = [ { tagStr: "content" }, { tagStr: "gulp" }, { tagStr: "testing" } ]; }); it("Should create tag classes", function () { expect(tags.getTagClasses(this.tagStr)).to.equal(" tag-content tag-gulp tag-testing"); }); it("Should return undefined if no tags exist", function () { expect(tags.getTagClasses(null)).to.be.undefined; }); it("Should support tags as Array", function () { expect(tags.getTagClasses(this.tagArray)).to.equal(" tag-content tag-gulp tag-testing"); }); it("Should create tag links from the root path", function () { var tagLinks = tags.getTagsAsLinks("", this.tagStr); expect(tagLinks).to.equal("contentgulptesting"); }); it("Should create tag links from a relative path", function () { var tagLinks = tags.getTagsAsLinks("../..", this.tagStr); expect(tagLinks).to.equal("contentgulptesting"); }); it("Should return undefined if no tags exist", function () { expect(tags.getTagsAsLinks("", null)).to.be.undefined; }); it("Should create tag link from the root path", function () { expect(tags.getTagAsLink("", "single")).to.equal("single"); }); it("Should create tag link from a relative path", function () { expect(tags.getTagAsLink("../..", "single")).to.equal("single"); }); it("Should return undefined if no tag exists", function () { expect(tags.getTagAsLink("", null)).to.be.undefined; }); it("Should get all tags as links from the root path", function () { expect(tags.getAllTagsAsLinks("", this.posts)).to.deep.equal([ { tag: "content", tagLink: "tag/content" }, { tag: "gulp", tagLink: "tag/gulp" }, { tag: "testing", tagLink: "tag/testing" } ]); }); it("Should get all tags as links from a relative path", function () { expect(tags.getAllTagsAsLinks("../..", this.posts)).to.deep.equal([ { tag: "content", tagLink: "../../tag/content" }, { tag: "gulp", tagLink: "../../tag/gulp" }, { tag: "testing", tagLink: "../../tag/testing" } ]); }); }); ================================================ FILE: gulpfile.js ================================================ "use strict"; var requireDir = require("require-dir"); // require all tasks in gulp/tasks, including sub-folders requireDir("./gulp/tasks", { recurse: true }); ================================================ FILE: install/content/pages/about.md ================================================ --- slug: about title: About us template: page.hbs --- Sweet sweet roll sweet roll dragée soufflé tootsie roll ice cream toffee. Jelly-o cake fruitcake tart. Bear claw oat cake cookie ice cream gummies. Gummies gummies fruitcake chupa chups sweet roll cake oat cake muffin cake. Pastry bear claw jelly beans marzipan cake jujubes jelly-o pudding. Fruitcake marshmallow dragée carrot cake gummi bears pie. Apple pie danish tiramisu muffin chocolate tiramisu topping pudding powder. Chocolate cake carrot cake dessert dessert apple pie gingerbread icing. Icing chupa chups muffin cake pastry. Apple pie cake gummi bears gummi bears marshmallow pudding liquorice. ================================================ FILE: install/content/posts/sample-blog-post.md ================================================ --- slug: sample-blog-post title: Sample blog post template: post.hbs date: 2015-02-14 author: Matt Levy --- ![](/images/sample-blog-post.jpg) 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. Sweet roll jelly beans danish tootsie roll icing sugar plum. Marshmallow danish marshmallow powder cake macaroon candy gummies. Cake gingerbread liquorice. Candy canes brownie oat cake gummi bears cupcake powder donut icing cake. ================================================ FILE: install/content/template.md ================================================ --- slug: i-am-a-template title: I am a template template: post.hbs date: 2014-10-07 author: Matt Levy status: draft --- ![](/images/i-am-a-template.jpg) This is the template full of markdown text! ================================================ FILE: install/files.json ================================================ { "files": [ { "file": "gulpfile.js", "encoding": "utf8" }, { "file": "package.json", "encoding": "utf8" }, { "file": "site.json", "encoding": "utf8" }, { "file": ".editorconfig", "encoding": "utf8" }, { "file": "content/template.md", "encoding": "utf8", "ignoreIfMissing": true }, { "file": "content/pages/about.md", "encoding": "utf8" }, { "file": "content/posts/sample-blog-post.md", "encoding": "utf8" }, { "file": "sass/style.scss", "encoding": "utf8" }, { "file": "images/sample-blog-post.jpg", "encoding": "binary" }, { "file": "templates/index.hbs", "encoding": "utf8" }, { "file": "templates/page.hbs", "encoding": "utf8" }, { "file": "templates/post.hbs", "encoding": "utf8" }, { "file": "templates/partials/footer.hbs", "encoding": "utf8" }, { "file": "templates/partials/header.hbs", "encoding": "utf8" }, { "file": "templates/partials/loop.hbs", "encoding": "utf8" }, { "file": "templates/partials/navigation.hbs", "encoding": "utf8" }, { "file": "templates/partials/pagination.hbs", "encoding": "utf8" }, { "file": "templates/partials/sidebar.hbs", "encoding": "utf8" } ] } ================================================ FILE: install/lib/json-file.js ================================================ var fs = require("fs"); module.exports.writeJsonFile = function(file, obj, options) { options = options || {}; var spaces = typeof options === "object" && options !== null ? "spaces" in options ? options.spaces : this.spaces : this.spaces; var str = JSON.stringify(obj, options.replacer, spaces) + "\n"; // not sure if fs.writeFileSync returns anything, but just in case return fs.writeFileSync(file, str, options); }; ================================================ FILE: install/lib/version-compare.js ================================================ "use strict"; module.exports = function(v1, v2) { var v1parts = ("" + v1).replace(/~|\^|>=|>|<=|<|\.x/i, "").split("."), v2parts = ("" + v2).replace(/~|\^|>=|>|<=|<|\.x/i, "").split("."), minLength = Math.min(v1parts.length, v2parts.length), p1, p2, i; // Compare tuple pair-by-pair. for (i = 0; i < minLength; i++) { // Convert to integer if possible, because "8" > "10". p1 = parseInt(v1parts[i], 10); p2 = parseInt(v2parts[i], 10); if (isNaN(p1)) { return NaN; } if (isNaN(p2)) { return NaN; } if (p1 === p2) { continue; } else if (p1 > p2) { return 1; } else if (p1 < p2) { return -1; } } // The longer tuple is always considered "greater" if (v1parts.length === v2parts.length) { return 0; } return (v1parts.length < v2parts.length) ? -1 : 1; }; ================================================ FILE: install/sass/style.scss ================================================ @mixin clearfix() { &:after { content: " "; display: block; clear: both; } } body { font-family: sans-serif; } .container { @include clearfix(); } .top-nav { } .post { .post-meta { } } .sidebar { ol { list-style: none; margin-left: 0; padding-left: 0; } } .row { @include clearfix(); } .pagination { @include clearfix(); &.pagination-none { display: none; } } @media screen and (min-width: 768px) { .main, article { float: left; width: 61%; } aside { float: right; width: 35%; } } @media screen and (min-width: 992px) { html { background-color: #eee; } body { max-width: 992px; margin: 0 auto; background-color: #fff; padding: 15px; } } ================================================ FILE: install/templates/index.hbs ================================================ {{> header}} {{> navigation}}

    {{site.title}}

    {{#if tag}}

    Posts tagged: {{tag}}

    {{else}} {{#if dateStr}}

    Posts dated: {{dateStr}}

    {{else}}

    {{site.description}}

    {{/if}} {{/if}}
    {{> loop}} {{> pagination}}
    {{> footer}} ================================================ FILE: install/templates/page.hbs ================================================ {{> header}} {{> navigation}}
    {{#post}}

    {{title}}

    {{{body}}}
    {{/post}}
    {{> footer}} ================================================ FILE: install/templates/partials/footer.hbs ================================================ ================================================ FILE: install/templates/partials/header.hbs ================================================ {{meta_title}} ================================================ FILE: install/templates/partials/loop.hbs ================================================ {{#each pages}}

    {{title}}

    {{{excerpt characters=230}}}...

    {{/each}} {{#each posts}}

    {{title}}

    {{#if date}} {{/if}}

    {{{excerpt characters=230}}}...

    {{/each}} ================================================ FILE: install/templates/partials/navigation.hbs ================================================ ================================================ FILE: install/templates/partials/pagination.hbs ================================================ ================================================ FILE: install/templates/partials/sidebar.hbs ================================================ {{#if allDates}} {{/if}} {{#if allTags}} {{/if}} ================================================ FILE: install/templates/post.hbs ================================================ {{> header}} {{> navigation}}
    {{#post}}

    {{title}}

    {{{body}}}
    {{/post}}
    {{> footer}} ================================================ FILE: install/tests/version-compare.spec.js ================================================ "use strict"; var versionCompare = require("../lib/version-compare"), expect = require("chai").expect; describe("version compare", function() { it("not compare invalid versions", function() { expect(versionCompare("a.v.s", "a.v.s")).to.be.NaN; expect(versionCompare("1.v.d", "a.v.s")).to.be.NaN; }); it("compare dotted version strings", function() { expect(versionCompare("1.8", "1.8.1")).to.equal(-1); expect(versionCompare("1.8.3", "1.8.1")).to.equal(1); expect(versionCompare("1.8", "1.10")).to.equal(-1); expect(versionCompare("1.10.1", "1.10.1")).to.equal(0); }); it("compare longer is considered greater", function() { expect(versionCompare("1.10.1.0", "1.10.1")).to.equal(1); expect(versionCompare("1.10.1", "1.10.1.0")).to.equal(-1); }); it("compare string pairs", function() { expect(versionCompare("1.x", "1.x")).to.equal(0); expect(versionCompare("1.10.x", "1.10.x")).to.equal(0); expect(versionCompare("~1.10", "1.10")).to.equal(0); expect(versionCompare("^1.10", "1.10")).to.equal(0); expect(versionCompare(">1.10", "1.10")).to.equal(0); expect(versionCompare(">=1.10", "1.10")).to.equal(0); expect(versionCompare("<1.10", "1.10")).to.equal(0); expect(versionCompare("<=1.10", "1.10")).to.equal(0); }); it("compare mixed ints and string pairs", function() { expect(versionCompare("1.8", "1")).to.equal(1); }); it("compare plain numbers", function() { expect(versionCompare("4", "3")).to.equal(1); }); }); ================================================ FILE: install.js ================================================ "use strict"; /* Simple install script for the gulp site generator * Copies the gulpfile.js, package.json and site.json files to the project root * Creates an src directory * Creates the following directories: src/content, src/templates, src/sass, src/content, src/content/pages src/content/posts * Creates the following files: src/content/*.md, src/templates/*.hbs, src/sass/*.scss, src/images/*.jpg */ var fs = require("fs"), path = require("path"), jsonFile = require("./install/lib/json-file"); function extend(destination, source) { for (var property in source) { if (source[property] && source[property].constructor && source[property].constructor === Object) { destination[property] = destination[property] || {}; destination[property] = extend(destination[property], source[property]); } else { destination[property] = source[property]; } } return destination; } var rootPath = process.cwd(), rootPathName = path.basename(__dirname), filesInstalled = 0, directoriesCreated = 0; var fileIndex = fs.readFileSync(__dirname + "/install/files.json", { encoding: "utf8" }); var fileList = JSON.parse(fileIndex); if (!fs.existsSync(rootPath + "/src")) { fs.mkdirSync(rootPath + "/src"); directoriesCreated++; } fileList.files.forEach(function (file) { var contents = null, isRootFile = file.file.indexOf("/") === -1; var filePath = (isRootFile ? rootPath + "/" + file.file : rootPath + "/src/" + file.file); if (!fs.existsSync(filePath)) { if (!isRootFile) { // create the directory if required var dir = path.dirname(file.file), dirPath = path.join(rootPath + "/src/" + dir); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); directoriesCreated++; } contents = fs.readFileSync(__dirname + "/install/" + file.file, file.encoding); fs.writeFileSync(filePath, contents, { encoding: file.encoding }); filesInstalled++; } else { contents = fs.readFileSync(__dirname + "/" + file.file, file.encoding); // replace the path to the gulp tasks if (file.file === "gulpfile.js") { contents = contents.replace(/\.\/gulp\/tasks/, "./" + rootPathName + "/gulp/tasks/"); } fs.writeFileSync(filePath, contents, { encoding: file.encoding }); filesInstalled++; } } }); // package.json changes var packages = null, packagesContent = fs.readFileSync(rootPath + "/package.json", { encoding: "utf8" }); try { packages = JSON.parse(packagesContent); // cleanup package.json var dependencies = packages.dependencies; packages.devDependencies = {}; extend(packages.devDependencies, dependencies); delete packages.dependencies; delete packages.keywords; delete packages.scripts; packages.version = "0.1.0"; packages.name = ""; packages.description = ""; packages.author = ""; // write out the new package.json jsonFile.writeJsonFile(rootPath + "/package.json", packages, { spaces: 4, encoding: "utf8" }); } catch (err) { console.error(err); } console.info("%d %s created and %d %s installed!", directoriesCreated, (directoriesCreated === 1 ? "directory": "directories"), filesInstalled, (filesInstalled === 1 ? "file" : "files")); if (directoriesCreated && filesInstalled) { console.info("Run 'npm install' then 'gulp' to get started"); } ================================================ FILE: package.json ================================================ { "name": "gulp-site-generator", "version": "0.3.2", "description": "Static site generator using Gulp", "main": "gulpfile.js", "scripts": { "test": "gulp test", "debug": "mocha --debug-brk gulp/tests/*.spec.js" }, "keywords": [ "gulp", "static", "generator" ], "author": "Matt Levy ", "license": "ISC", "dependencies": { "bluebird": "3.5.0", "downsize": "0.0.8", "glob": "7.1.2", "gulp": "3.9.1", "gulp-compile-handlebars": "0.6.1", "gulp-concat": "2.6.1", "gulp-connect": "5.0.0", "gulp-if": "2.0.2", "gulp-markdown-to-json": "1.0.3", "gulp-minify-css": "1.2.4", "gulp-minify-html": "1.0.6", "gulp-open": "2.0.0", "gulp-rename": "1.2.2", "gulp-replace": "0.6.1", "gulp-sass": "3.1.0", "gulp-uglify": "3.0.0", "gulp-uncss": "1.0.6", "gulp-util": "3.0.8", "handlebars": "4.0.10", "imagemin-mozjpeg": "5.1.0", "imagemin-optipng": "4.3.0", "imagemin-pngquant": "4.2.0", "lodash": "4.17.4", "markdown-it": "8.4.0", "marked": "0.3.6", "minimist": "1.2.0", "moment": "2.18.1", "mout": "1.0.0", "relative": "3.0.2", "require-dir": "0.3.2", "rss": "1.2.2", "run-sequence": "2.1.0" }, "devDependencies": { "browser-sync": "2.18.13", "chai": "4.1.1", "eslint": "4.5.0", "github-changes": "1.1.0", "gulp-coveralls": "0.1.4", "gulp-istanbul": "1.1.2", "gulp-mocha": "3.0.1", "mocha": "3.5.0", "mockery": "2.1.0", "sinon": "3.2.1" } } ================================================ FILE: site.json ================================================ { "title": "", "description": "", "url": "", "rss": "/rss.xml", "maxItems": 6, "concatJs": [ "./src/js/*.js" ], "styleSheet": "style.css", "authors": { "Matt Levy": { "name": "Matt Levy", "bio": "Practicing the art of front-end development" } }, "imageCompression": true } ================================================ FILE: update.js ================================================ "use strict"; /* Simple update script for the gulp site generator * Updates the package.json file in the project root * Copies any missing or new install files */ var fs = require("fs"), path = require("path"), jsonFile = require("./install/lib/json-file"), versionCompare = require("./install/lib/version-compare"); var rootPath = process.cwd(), rootPathName = path.basename(__dirname), filesInstalled = 0, directoriesCreated = 0; // install missing files var fileIndex = fs.readFileSync(__dirname + "/install/files.json", { encoding: "utf8" }); var fileList = JSON.parse(fileIndex); if (!fs.existsSync(rootPath + "/src")) { fs.mkdirSync(rootPath + "/src"); directoriesCreated++; } fileList.files.forEach(function (file) { var contents = null, isRootFile = file.file.indexOf("/") === -1; var filePath = (isRootFile ? rootPath + "/" + file.file : rootPath + "/src/" + file.file); if (file.hasOwnProperty("ignoreIfMissing") && file["ignoreIfMissing"] && !fs.existsSync(filePath)) { return; } if (!fs.existsSync(filePath)) { if (!isRootFile) { // create the directory if required var dir = path.dirname(file.file), dirPath = path.join(rootPath + "/src/" + dir); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath); directoriesCreated++; } contents = fs.readFileSync(__dirname + "/install/" + file.file, file.encoding); fs.writeFileSync(filePath, contents, { encoding: file.encoding }); filesInstalled++; } else { contents = fs.readFileSync(__dirname + "/" + file.file, file.encoding); // replace the path to the gulp tasks if (file.file === "gulpfile.js") { contents = contents.replace(/\.\/gulp\/tasks/, "./" + rootPathName + "/gulp/tasks/"); } fs.writeFileSync(filePath, contents, { encoding: file.encoding }); filesInstalled++; } } }); if (directoriesCreated || filesInstalled) { console.info("%d %s created and %d %s installed!", directoriesCreated, (directoriesCreated === 1 ? "directory": "directories"), filesInstalled, (filesInstalled === 1 ? "file" : "files")); } // update package.json file if (fs.existsSync(rootPath + "/package.json")) { var updatedModules = [], packages, newPackages; var packagesContent = fs.readFileSync(rootPath + "/package.json", { encoding: "utf8" }); var newPackageContents = fs.readFileSync(__dirname + "/package.json", { encoding: "utf8" }); try { packages = JSON.parse(packagesContent); newPackages = JSON.parse(newPackageContents); } catch (err) { throw err; } var packagesToUpdate = newPackages.dependencies; // compare each version and only update where the version to be installed is greater than the installed one if (Object.keys(packagesToUpdate).length > 0) { for (var moduleName in packagesToUpdate) { if (packagesToUpdate.hasOwnProperty(moduleName)) { var newModuleVersion = packagesToUpdate[moduleName]; if (!packages.devDependencies.hasOwnProperty(moduleName) || versionCompare(packages.devDependencies[moduleName], newModuleVersion) < 0) { var oldModuleVersion = packages.devDependencies[moduleName]; packages.devDependencies[moduleName] = newModuleVersion; updatedModules.push({ name: moduleName, newVersion: newModuleVersion, oldVersion: oldModuleVersion }); } } } } // write out the new package.json jsonFile.writeJsonFile(rootPath + "/package.json", packages, { spaces: 4, encoding: "utf8" }); var updatedModuleCount = Object.keys(updatedModules).length; if (updatedModuleCount) { console.info("%d %s updated in package.json!", updatedModuleCount, (updatedModuleCount === 1 ? "module" : "modules")); console.info("Run 'npm install'"); } }