├── test
├── fixtures
│ ├── pages
│ │ ├── about.hbs
│ │ ├── home.hbs
│ │ └── blog.hbs
│ └── layouts
│ │ ├── default.hbs
│ │ ├── base.hbs
│ │ └── post.hbs
└── main.js
├── .gitattributes
├── .jshintrc
├── .verbrc.md
├── .gitignore
├── docs
└── layouts.md
├── LICENSE-MIT
├── package.json
├── README.md
└── index.js
/test/fixtures/pages/about.hbs:
--------------------------------------------------------------------------------
1 | ---
2 | title: About Page
3 | ---
4 | {{> info }}
--------------------------------------------------------------------------------
/test/fixtures/pages/home.hbs:
--------------------------------------------------------------------------------
1 | ---
2 | title: Home Page
3 | ---
4 | {{> info }}
--------------------------------------------------------------------------------
/test/fixtures/layouts/default.hbs:
--------------------------------------------------------------------------------
1 | ---
2 | layout: base
3 | ---
4 | {{> header }}
5 | {{ body }}
6 | {{> footer }}
--------------------------------------------------------------------------------
/test/fixtures/layouts/base.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{site.title}}
6 |
7 |
8 | {{body}}
9 |
10 |
--------------------------------------------------------------------------------
/test/fixtures/pages/blog.hbs:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | show-full: false
4 | title: Blog Page
5 | ---
6 | {{> info }}
7 |
8 | {{#each posts}}
9 | {{> post this}}
10 | {{/each}}
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Enforce Unix newlines
2 | *.* text eol=lf
3 | *.css text eol=lf
4 | *.html text eol=lf
5 | *.js text eol=lf
6 | *.json text eol=lf
7 | *.less text eol=lf
8 | *.md text eol=lf
9 | *.yml text eol=lf
10 |
11 | *.jpg binary
12 | *.gif binary
13 | *.png binary
14 | *.jpeg binary
--------------------------------------------------------------------------------
/test/fixtures/layouts/post.hbs:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | ---
4 |
5 | {{body}}
6 |
7 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true,
3 | "boss": true,
4 | "curly": true,
5 | "eqeqeq": true,
6 | "eqnull": true,
7 | "immed": true,
8 | "latedef": true,
9 | "newcap": true,
10 | "noarg": true,
11 | "node": true,
12 | "sub": true,
13 | "undef": true,
14 | "unused": true,
15 | "globals": {
16 | "define": true,
17 | "before": true,
18 | "after": true,
19 | "describe": true,
20 | "it": true
21 | }
22 | }
--------------------------------------------------------------------------------
/.verbrc.md:
--------------------------------------------------------------------------------
1 | ---
2 | tags: ['verb-tag-jscomments']
3 | ---
4 | # {%= name %} {%= badge('fury') %}
5 |
6 | > {%= description %}
7 |
8 | ## Install
9 | {%= include("install") %}
10 |
11 | ## API
12 | {%= jscomments("index.js") %}
13 |
14 | ## Authors
15 | {%= include("authors", {
16 | authors: [
17 | {
18 | name: 'Brian Woodward',
19 | username: 'doowb'
20 | },
21 | {
22 | name: 'Jon Schlinkert',
23 | username: 'jonschlinkert'
24 | }
25 | ]
26 | }) %}
27 |
28 | ## License
29 | {%= copyright() %}
30 | {%= license() %}
31 |
32 | ***
33 |
34 | {%= include("footer") %}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Numerous always-ignore extensions
2 | *.csv
3 | *.dat
4 | *.diff
5 | *.err
6 | *.gz
7 | *.log
8 | *.orig
9 | *.out
10 | *.pid
11 | *.rej
12 | *.seed
13 | *.swo
14 | *.swp
15 | *.vi
16 | *.yo-rc.json
17 | *.zip
18 | *~
19 | .ruby-version
20 | lib-cov
21 |
22 | # OS or Editor folders
23 | *.esproj
24 | *.sublime-project
25 | *.sublime-workspace
26 | ._*
27 | .cache
28 | .DS_Store
29 | .idea
30 | .project
31 | .settings
32 | .tmproj
33 | nbproject
34 | Thumbs.db
35 |
36 | # Komodo
37 | *.komodoproject
38 | .komodotools
39 |
40 | # grunt-html-validation
41 | validation-status.json
42 | validation-report.json
43 |
44 | # Vendor packages
45 | node_modules
46 | bower_components
47 | vendor
48 |
49 | # General folders and files to ignore
50 | _gh_pages
51 | tmp
52 | temp
53 | TODO.md
--------------------------------------------------------------------------------
/docs/layouts.md:
--------------------------------------------------------------------------------
1 | Default settings for body regex/delimiters:
2 |
3 | ```js
4 | var options = {
5 | delims: ['{{', '}}'], // start and end delimiters for body tag
6 | expression: '{{ body }}', // default body tag for empty layouts
7 | matter: '\\s*body\\s*', // inner contents of body tag regex
8 | };
9 | ```
10 |
11 | Assuming `parsedLayouts` have been read from the file system and parsed, we can now add them to the `layouts` cache:
12 |
13 | ```js
14 | var parsedLayouts = glob.sync('layouts/*.hbs');
15 | parsedLayouts.forEach(function (layout) {
16 | // `layout` must have at `data` and `content` properties
17 | layouts.set(layout.name, layout);
18 | });
19 | ```
20 |
21 | ## Render the stack
22 |
23 | Render the entire layout stack for a specific page object:
24 |
25 | ```js
26 | var page = {data: {a: 'b', layout: 'default'}, content: 'Howdy {{name}}!'};
27 | var template = layouts.render(page);
28 | ```
29 |
30 | ### page object
31 |
32 | The `page` object must have `data` and `content` properties!
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Brian Woodward, contributors.
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "assemble-layouts",
3 | "description": "Assemble plugin for rendering nested template layouts.",
4 | "version": "0.1.8",
5 | "homepage": "https://github.com/assemble/assemble-layouts",
6 | "author": {
7 | "name": "Brian Woodward",
8 | "url": "https://github.com/doowb"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/assemble/assemble-layouts.git"
13 | },
14 | "bugs": {
15 | "url": "https://github.com/assemble/assemble-layouts/issues"
16 | },
17 | "licenses": [
18 | {
19 | "type": "MIT",
20 | "url": "https://github.com/assemble/assemble-layouts/blob/master/LICENSE-MIT"
21 | }
22 | ],
23 | "keywords": [
24 | "layout",
25 | "nested",
26 | "layouts",
27 | "template",
28 | "templates",
29 | "assemble",
30 | "assembleplugin"
31 | ],
32 | "main": "index.js",
33 | "engines": {
34 | "node": ">=0.8"
35 | },
36 | "scripts": {
37 | "test": "mocha -R spec"
38 | },
39 | "devDependencies": {
40 | "verb": "~0.2.0",
41 | "verb-tag-jscomments": "^0.1.2",
42 | "chai": "~1.9.1",
43 | "mocha": "~1.18.2",
44 | "fs-utils": "^0.4.3",
45 | "gray-matter": "^0.4.2"
46 | },
47 | "dependencies": {
48 | "delims": "^0.1.4",
49 | "falsey": "^0.1.0",
50 | "vinyl": "^0.2.3",
51 | "xtend": "^3.0.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Layouts = require('../');
4 | var file = require('fs-utils');
5 | var matter = require('gray-matter');
6 | var File = require('vinyl');
7 |
8 | function loadLayouts (layouts) {
9 | file.find('test/fixtures/layouts/*.hbs').forEach(function (filepath) {
10 | var obj = matter.read(filepath);
11 | var layout = new File({contents: new Buffer(obj.content)});
12 | layout.data = obj.data;
13 | layout.orig = new Buffer(obj.original);
14 | layouts.set(file.basename(filepath), layout);
15 | });
16 | }
17 |
18 | function loadPages () {
19 | return file.find('test/fixtures/pages/*.hbs').map(function (filepath) {
20 | var obj = matter(file.readFileSync(filepath));
21 | var page = new File({contents: new Buffer(obj.content)});
22 | page.data = obj.data;
23 | page.orig = new Buffer(obj.original);
24 | return page;
25 | });
26 | }
27 |
28 | describe('Layouts', function () {
29 | it('should create a layout stack', function () {
30 | var layouts = new Layouts({layout: 'default'});
31 | loadLayouts(layouts);
32 | console.log(layouts);
33 | console.log();
34 |
35 | var stack = layouts.createStack({layout: 'default'});
36 | console.log('stack', stack);
37 | console.log();
38 |
39 | var pages = loadPages();
40 | console.log('pages', pages);
41 | console.log();
42 |
43 | pages.forEach(function (page) {
44 | var template = layouts.flatten(page);
45 | console.log('template', template.contents.toString());
46 | });
47 |
48 | });
49 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # assemble-layouts [](http://badge.fury.io/js/assemble-layouts)
2 |
3 | > Assemble plugin for rendering nested template layouts.
4 |
5 | ## Install
6 | Install with [npm](npmjs.org):
7 |
8 | ```bash
9 | npm i assemble-layouts --save-dev
10 | ```
11 |
12 | ## API
13 | ### Layouts
14 |
15 | Create a new instance of `Layouts` to generate flattened layout stacks.
16 |
17 | **Example:**
18 |
19 | ```js
20 | var layouts = new Layouts(options);
21 | ```
22 |
23 | Default settings for body regex/delimiters:
24 |
25 | ```js
26 | var options = {
27 | delims: ['{{', '}}'], // start and end delimiters for body tag
28 | expression: '{{ body }}', // default body tag for empty layouts
29 | matter: '\\s*body\\s*', // inner contents of body tag regex
30 | };
31 | ```
32 |
33 | Assuming `parsedLayouts` have been read from the file system and parsed, we can now add them to the `layouts` cache:
34 |
35 | ```js
36 | var parsedLayouts = glob.sync('layouts/*.hbs');
37 | parsedLayouts.forEach(function (layout) {
38 | // `layout` must have at `data` and `content` properties
39 | layouts.set(layout.name, layout);
40 | });
41 | ```
42 |
43 | ### Render the stack
44 |
45 | Render the entire layout stack for a specific page object:
46 |
47 | ```js
48 | var page = {data: {a: 'b', layout: 'default'}, content: 'Howdy {{name}}!'};
49 | var template = layouts.render(page);
50 | ```
51 |
52 | #### page object
53 |
54 | The `page` object must have `data` and `content` properties!
55 |
56 | * `options` {Object}: global options for how to determine layouts.
57 |
58 |
59 | ### .flatten
60 |
61 | Flatten the entire layout stack based on the `file` and `options`
62 | and how the layout stack is defined.
63 |
64 | * `file` {Object}: object containing `data` and `contents` properties.
65 | * `options` {Object}: additional options to override `global` and/or `file` options
66 |
67 |
68 | ### .set
69 |
70 | Store a layout.
71 |
72 | * `name` {String}: name of the layout to store.
73 | * `layout` {Object}: object containing `data` and `content` properties.
74 |
75 |
76 | ### .get
77 |
78 | Return a stored layout.
79 |
80 | * `name` {String}: name of the layout
81 |
82 |
83 | ### .createStack
84 |
85 | Create a layout stack based on options and layout data. Returned stack is
86 | an array with the layouts to use going from the top level parent to the
87 | lowest level child.
88 |
89 | * `options` {Object}: used to determine the layout to use.
90 |
91 |
92 | ### .useLayout
93 |
94 | Return a valid layout name if one should be used, otherwise, returns `null`
95 | to indicate a layout should not be used.
96 |
97 | * `layout` {String}: layout to use, or a negative value to not use a layout
98 |
99 | ## Authors
100 |
101 | **Brian Woodward**
102 |
103 | + [github/doowb](https://github.com/doowb)
104 | + [twitter/doowb](http://twitter.com/doowb)
105 |
106 | **Jon Schlinkert**
107 |
108 | + [github/jonschlinkert](https://github.com/jonschlinkert)
109 | + [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
110 |
111 |
112 | ## License
113 | Copyright (c) 2014 Brian Woodward, contributors.
114 | Released under the MIT license
115 |
116 | ***
117 |
118 | _This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on July 24, 2014._
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var delims = require('delims');
4 | var extend = require('xtend');
5 | var isFalsey = require('falsey');
6 | var File = require('vinyl');
7 |
8 |
9 | /**
10 | * ## Layouts
11 | *
12 | * Create a new instance of `Layouts` to generate flattened layout stacks.
13 | *
14 | * **Example:**
15 | *
16 | * ```js
17 | * var layouts = new Layouts(options);
18 | * ```
19 | *
20 | * {%= docs("layouts") %}
21 | *
22 | * @class `Layouts`
23 | * @param {Object} `options` global options for how to determine layouts.
24 | * @returns {Layouts} new `Layouts` instance
25 | * @constructor
26 | */
27 |
28 | function Layouts(options) {
29 | if (!this instanceof Layouts) {
30 | return new Layouts(options);
31 | }
32 |
33 | var defaults = {
34 | delims: ['{{', '}}'],
35 | tag: '{{ body }}',
36 | re: '\\s*body\\s*',
37 | flags: 'gi',
38 | beginning: '',
39 | end: '',
40 | body: ''
41 | };
42 |
43 | this.options = extend(defaults, options);
44 | this.options.matter = this.options.re;
45 | this.templates = {};
46 | this.tag = this.options.tag;
47 | this.regex = delims(this.options.delims, this.options).evaluate;
48 | }
49 |
50 |
51 | /**
52 | * ## .flatten
53 | *
54 | * Flatten the entire layout stack based on the `file` and `options`
55 | * and how the layout stack is defined.
56 | *
57 | * @param {Object} `file` object containing `data` and `contents` properties.
58 | * @param {Object} `options` additional options to override `global` and/or `file` options
59 | * @returns {String} flattened template
60 | */
61 |
62 | Layouts.prototype.flatten = function (file, options) {
63 | var opts = extend({}, this.options, file.data, options);
64 | var stack = this.createStack(opts.layout);
65 |
66 | // Setup the object to be returned, and store file.contents on `orig`
67 | var results = new File({contents: new Buffer(this.tag)});
68 | results.data = file.data;
69 | results.orig = file.contents;
70 |
71 | // loop over the layout stack building the context and content
72 | results = stack.reduce(function (acc, name) {
73 | var layout = this.get(name);
74 | acc.data = extend(acc.data, layout.data);
75 | acc.contents = this._inject(acc.contents, layout.contents);
76 | return acc;
77 | }.bind(this), results);
78 |
79 | // Pass the accumlated, final results
80 | results.data = extend(results.data, file.data);
81 | results.contents = this._inject(results.contents, file.contents);
82 | return results;
83 | };
84 |
85 |
86 | /**
87 | * ## ._inject
88 | *
89 | * Injects the inner content into the outer content based on the body regex
90 | *
91 | * @param {String} `outer` content that wraps the inner content
92 | * @param {String} `inner` content to be injected into the outer content
93 | * @returns {String} resulting content
94 | * @api private
95 | */
96 |
97 | Layouts.prototype._inject = function (outer, inner) {
98 | return new Buffer(outer.toString('utf8').replace(this.regex, inner.toString('utf8')));
99 | };
100 |
101 |
102 | /**
103 | * ## .set
104 | *
105 | * Store a layout.
106 | *
107 | * @param {String} `name` name of the layout to store.
108 | * @param {Object} `layout` object containing `data` and `content` properties.
109 | */
110 |
111 | Layouts.prototype.set = function (name, layout) {
112 | this.templates[name] = layout;
113 | return this;
114 | };
115 |
116 |
117 | /**
118 | * ## .get
119 | *
120 | * Return a stored layout.
121 | *
122 | * @param {String} `name` name of the layout
123 | * @returns {Object} object containing `data` and `content` properties.
124 | */
125 |
126 | Layouts.prototype.get = function (name) {
127 | return this.templates[name];
128 | };
129 |
130 |
131 | /**
132 | * ## .createStack
133 | *
134 | * Create a layout stack based on options and layout data. Returned stack is
135 | * an array with the layouts to use going from the top level parent to the
136 | * lowest level child.
137 | *
138 | * @param {Object} `options` used to determine the layout to use.
139 | * @returns {Array} `stack` parent => child layouts.
140 | */
141 |
142 | Layouts.prototype.createStack = function (name) {
143 | name = this.useLayout(name);
144 | var template = null;
145 | var stack = [];
146 | while (name && (template = this.get(name))) {
147 | stack.unshift(name);
148 | name = this.useLayout(template.data && template.data.layout);
149 | }
150 | return stack;
151 | };
152 |
153 |
154 | /**
155 | * ## .useLayout
156 | *
157 | * Return a valid layout name if one should be used, otherwise, returns `null`
158 | * to indicate a layout should not be used.
159 | *
160 | * @param {String} `layout` layout to use, or a negative value to not use a layout
161 | * @returns {*} either a valid layout name or null
162 | */
163 |
164 | Layouts.prototype.useLayout = function (layout) {
165 | if (!layout || isFalsey(layout)) {
166 | return null;
167 | }
168 | return layout;
169 | };
170 |
171 | module.exports = Layouts;
--------------------------------------------------------------------------------