Skip to content

Commit 00ad919

Browse files
committed
Initial commit, it’s doing what it’s supposed to do.
0 parents  commit 00ad919

22 files changed

+436
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
node_modules/

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# elevenisland
2+
3+
Transform a directory of templates into HTML.
4+
5+
Works with:
6+
7+
* EJS (.ejs)
8+
* Mustache (.mustache)
9+
* Handlebars (.hbs)
10+
* Markdown (.md)
11+
* Haml (.haml)
12+
* Pug (formerly Jade, .pug)
13+
* Nunjucks (.njk)
14+
15+
## Usage
16+
17+
```
18+
elevenisland
19+
20+
# Watch
21+
elevenisland --watch
22+
```
23+
24+
### Advanced
25+
26+
* Modify `data.json` to set global static data available to templates.
27+
* Modify template format whitelist in `config.json`, the first one listed there is the default templating engine (default: `ejs`) and will be used to pre-process `data.json`.
28+
* Markdown doesn’t render `data` and is also pre-processed using the default tempalting engine (default: `ejs`).
29+
30+
## Tests
31+
32+
```
33+
npm run test
34+
```

cmd.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env node
2+
const watch = require("glob-watcher");
3+
const argv = require( "minimist" )( process.argv.slice(2) );
4+
const TemplateWriter = require("./src/TemplateWriter");
5+
6+
const cfg = require("./config.json");
7+
// argv._ ? argv._ :
8+
const dir = cfg.dir.templates;
9+
10+
let writer = new TemplateWriter(
11+
cfg.templateFormats.map(function(extension) {
12+
return dir + "/*" + extension;
13+
}),
14+
cfg.dataFileName
15+
);
16+
17+
writer.write();
18+
19+
if( argv.watch ) {
20+
console.log( "Watching…" );
21+
var watcher = watch(["./" + cfg.dir.templates + "/**", cfg.dataFileName]);
22+
watcher.on("change", function(path, stat) {
23+
console.log("File changed:", path);
24+
writer.write();
25+
});
26+
27+
watcher.on("add", function(path, stat) {
28+
console.log("File added:", path);
29+
writer.write();
30+
});
31+
}

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"dataFileName": "./data.json",
3+
"templateFormats": ["ejs", "md", "hbs", "mustache", "haml", "pug", "njk"],
4+
"dir": {
5+
"templates": "templates",
6+
"output": "dist"
7+
}
8+
}

data.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"title": "title",
3+
"globaltitle": "This is a globaltitle.",
4+
"version": "<%= _package.version %>"
5+
}

package.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "elevenisland",
3+
"version": "1.0.0",
4+
"description": "Transform a directory of templates into HTML.",
5+
"main": "cmd.js",
6+
"scripts": {
7+
"default": "node cmd.js",
8+
"watch": "node cmd.js --watch",
9+
"test": "ava"
10+
},
11+
"author": {
12+
"name": "Zach Leatherman",
13+
"email": "zachleatherman@gmail.com",
14+
"url": "https://zachleat.com/"
15+
},
16+
"bin": {
17+
"elevenisland": "./cmd.js"
18+
},
19+
"devDependencies": {
20+
"ava": "^0.23.0"
21+
},
22+
"dependencies": {
23+
"consolidate": "^0.15.0",
24+
"ejs": "^2.5.7",
25+
"fs-extra": "^4.0.2",
26+
"globby": "^7.1.1",
27+
"glob-watcher": "^4.0.0",
28+
"gray-matter": "^3.1.1",
29+
"hamljs": "^0.6.2",
30+
"handlebars": "^4.0.11",
31+
"markdown-it": "^8.4.0",
32+
"minimist": "^1.2.0",
33+
"mustache": "^2.3.0",
34+
"nunjucks": "^3.0.1",
35+
"parse-filepath": "^1.0.1",
36+
"promise": "^8.0.1",
37+
"pug": "^2.0.0-rc.4"
38+
}
39+
}

src/Layout.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const CFG = require( "../config.json" );
2+
const fs = require("fs-extra");
3+
4+
function Layout( name, dir ) {
5+
this.dir = dir;
6+
this.name = name;
7+
this.filename = this.findFileName();
8+
this.fullPath = this.dir + "/" + this.filename;
9+
}
10+
11+
Layout.prototype.getFullPath = function() {
12+
return this.fullPath;
13+
};
14+
15+
Layout.prototype.findFileName = function() {
16+
let file;
17+
if( !fs.existsSync(this.dir) ) {
18+
throw Error( "Layout directory does not exist: " + this.dir );
19+
}
20+
CFG.templateFormats.forEach(function( extension ) {
21+
let filename = this.name + "." + extension;
22+
if(!file && fs.existsSync( this.dir + "/" + filename)) {
23+
file = filename;
24+
}
25+
}.bind(this));
26+
27+
return file;
28+
};
29+
30+
module.exports = Layout;

src/Template.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const ejs = require("ejs");
2+
const fs = require("fs-extra");
3+
const parsePath = require('parse-filepath');
4+
const matter = require('gray-matter');
5+
const TemplateRender = require( "./TemplateRender" );
6+
const Layout = require( "./Layout" );
7+
8+
const cfg = require("../config.json");
9+
10+
function Template( path, globalData ) {
11+
this.path = path;
12+
this.inputContent = this.getInput();
13+
this.parsed = parsePath( path );
14+
this.dir = this.parsed.dir.replace( new RegExp( "^" + cfg.dir.templates ), "" );
15+
this.frontMatter = this.getMatter();
16+
this.data = this.mergeData( globalData, this.frontMatter.data );
17+
this.outputPath = this.getOutputPath();
18+
}
19+
Template.prototype.getOutputPath = function() {
20+
return cfg.dir.output + "/" + ( this.dir ? this.dir + "/" : "" ) + this.parsed.name + ".html";
21+
};
22+
Template.prototype.getInput = function() {
23+
return fs.readFileSync(this.path, "utf-8");
24+
};
25+
Template.prototype.getMatter = function() {
26+
return matter( this.inputContent );
27+
};
28+
Template.prototype.mergeData = function( globalData, pageData ) {
29+
let data = {};
30+
for( let j in globalData ) {
31+
data[ j ] = globalData[ j ];
32+
}
33+
for( let j in pageData ) {
34+
data[ j ] = pageData[ j ];
35+
}
36+
return data;
37+
};
38+
Template.prototype.getPreRender = function() {
39+
return this.frontMatter.content;
40+
};
41+
Template.prototype.renderLayout = function(tmpl, data) {
42+
let layoutPath = (new Layout( tmpl.data.layout, cfg.dir.templates + "/_layouts" )).getFullPath();
43+
44+
console.log( "Reading layout " + tmpl.data.layout + ":", layoutPath );
45+
let layout = new Template( layoutPath, {} );
46+
let layoutData = this.mergeData( layout.data, data );
47+
layoutData._layoutContent = this.renderContent( tmpl.getPreRender(), data );
48+
let rendered = layout.renderContent( layout.getPreRender(), layoutData );
49+
if( layout.data.layout ) {
50+
return this.renderLayout( layout, layoutData );
51+
}
52+
53+
return rendered;
54+
};
55+
Template.prototype.renderContent = function( str, data ) {
56+
return ( new TemplateRender( this.path )).getRenderFunction()( str, data );
57+
};
58+
Template.prototype.render = function() {
59+
if( this.data.layout ) {
60+
return this.renderLayout(this, this.data);
61+
} else {
62+
return this.renderContent(this.getPreRender(), this.data);
63+
}
64+
};
65+
Template.prototype.write = function() {
66+
let err = fs.outputFileSync(this.outputPath, this.render());
67+
if(err) {
68+
throw err;
69+
}
70+
console.log( "Writing", this.outputPath );
71+
};
72+
73+
module.exports = Template;

src/TemplateRender.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const parsePath = require('parse-filepath');
2+
const ejs = require( "ejs" );
3+
const md = require('markdown-it')();
4+
const Handlebars = require('handlebars');
5+
const Mustache = require('mustache');
6+
const haml = require('hamljs');
7+
const pug = require('pug');
8+
const nunjucks = require('nunjucks');
9+
10+
function TemplateRender( path ) {
11+
this.parsed = path ? parsePath( path ) : undefined;
12+
}
13+
14+
TemplateRender.prototype.getRenderFunction = function() {
15+
if( !this.parsed || this.parsed.ext === ".ejs" ) {
16+
return ejs.render;
17+
} else if( this.parsed.ext === ".md" ) {
18+
return function(str, data) {
19+
var render = (new TemplateRender()).getRenderFunction();
20+
return md.render(render(str, data)).trim();
21+
};
22+
} else if( this.parsed.ext === ".hbs" ) {
23+
return function(str, data) {
24+
return Handlebars.compile(str)(data).trim();
25+
}
26+
} else if( this.parsed.ext === ".mustache" ) {
27+
return function(str, data) {
28+
return Mustache.render(str, data).trim();
29+
}
30+
} else if( this.parsed.ext === ".haml" ) {
31+
return function(str, data) {
32+
return haml.compile(str)(data).trim();
33+
};
34+
} else if( this.parsed.ext === ".pug" ) {
35+
return function(str, data) {
36+
return pug.compile(str)(data).trim();
37+
};
38+
} else if( this.parsed.ext === ".njk" ) {
39+
return function(str, data) {
40+
nunjucks.configure({ autoescape: false });
41+
return nunjucks.renderString(str, data);
42+
};
43+
}
44+
};
45+
46+
module.exports = TemplateRender;

src/TemplateWriter.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const fs = require("fs-extra");
2+
const globby = require('globby');
3+
4+
const Template = require( "./Template" );
5+
const TemplateRender = require( "./TemplateRender" );
6+
const PKG = require("../package.json");
7+
8+
function TemplateWriter(files, globalDataPath) {
9+
this.files = files;
10+
this.globalRenderFunction = (new TemplateRender()).getRenderFunction();
11+
12+
this.globalDataPath = globalDataPath;
13+
this.globalData = this.mergeDataImports(this.readJsonAsTemplate(globalDataPath));
14+
}
15+
16+
TemplateWriter.prototype.mergeDataImports = function(data) {
17+
data._package = PKG;
18+
return data;
19+
};
20+
21+
TemplateWriter.prototype.readJsonAsTemplate = function( path ) {
22+
return JSON.parse( this.globalRenderFunction( fs.readFileSync( path, "utf-8" ), this.mergeDataImports({})));
23+
};
24+
25+
TemplateWriter.prototype.write = function() {
26+
let self = this;
27+
globby(this.files).then(function(templates) {
28+
templates.forEach(function(path) {
29+
console.log( "Reading", path );
30+
let tmpl = new Template( path, self.globalData );
31+
tmpl.write();
32+
});
33+
});
34+
};
35+
36+
module.exports = TemplateWriter;

0 commit comments

Comments
 (0)