diff --git a/example/layouts/nav.sy b/example/layouts/nav.sy
new file mode 100644
index 0000000..d860464
--- /dev/null
+++ b/example/layouts/nav.sy
@@ -0,0 +1,13 @@
+---
+{
+ "type": "layout"
+}
+---
+
+
{{ site.title }}
+
+
diff --git a/example/site.css b/example/site.css
index 03926a7..dcb5e21 100644
--- a/example/site.css
+++ b/example/site.css
@@ -18,6 +18,7 @@ html, body {
right: 0;
z-index: -1;
background-color: #3e3e3e;
+ background: linear-gradient(to bottom right, #3e3e3e, #1a1a1a);
overflow: hidden;
overflow-x: hidden;
overflow-y: hidden;
diff --git a/lib/defaultPlugins.js b/lib/defaultPlugins.js
new file mode 100644
index 0000000..68b97e2
--- /dev/null
+++ b/lib/defaultPlugins.js
@@ -0,0 +1,69 @@
+const fs = require('fs');
+const path = require('path');
+const { promisify } = require('util');
+
+const { render, parse } = require('./util');
+
+const readFile = promisify(fs.readFile);
+
+module.exports.includes = {
+ parse: async function(filePath, content) {
+ const reg = /{{-- includes (.+?) --}}/g;
+
+ if(content.match(reg)) {
+ const found = [];
+ let block;
+ while ((block = reg.exec(content)) != null) {
+ const oldArgument = block[1];
+ const newArgument = path.resolve(path.dirname(filePath), oldArgument);
+
+ content = content.replace(`{{-- includes ${oldArgument} --}}`, `{{-- includes ${newArgument} --}}`);
+ found.push(newArgument);
+ }
+ return {
+ content,
+ found
+ };
+ }
+ return false;
+ },
+ render: async function(plugins, filePath, content, templates, data, found) {
+ const start = process.hrtime();
+ const ext = found.substring(found.lastIndexOf('.') + 1, found.length);
+ const name = found.substring(found.lastIndexOf('/') + 1, found.length - 3);
+ const _content = await readFile(found, 'utf8');
+ const depends = [];
+
+ if(ext === 'sy') {
+ const passedData = Object.assign(data, {
+ layout: '',
+ depends: [],
+ includes: []
+ });
+
+ // if the template isn't registered (ie it is somewhere else on disk and not in the site directory) go and parse that file
+ const _template = templates[name] || await parse(plugins, found);
+ const output = await render(plugins, templates, _template, passedData);
+
+ // ensure this template gets added to the dependency tree
+ depends.push(output);
+
+ content = content.replace(`{{-- includes ${found} --}}`, output.rendered);
+ }
+
+ if(ext === 'css') {
+ content = content.replace(`{{-- includes ${found} --}}`, ``);
+
+ // ensure this content gets added to the dependency tree
+ depends.push({
+ filePath: found,
+ time: process.hrtime(start)[1]/1000000
+ });
+ }
+
+ return {
+ depends,
+ content
+ };
+ }
+};
diff --git a/lib/site.js b/lib/site.js
index ff447f3..2c8c2e6 100644
--- a/lib/site.js
+++ b/lib/site.js
@@ -6,7 +6,9 @@ const writeFile = promisify(fs.writeFile);
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);
-const { parse, render, ensureDirectoryExists } = require('./util');
+const { parse, render, ensureDirectoryExists, merge, getTotalTimeOfDepends } = require('./util');
+
+const defaultPlugins = require('./defaultPlugins');
class Site {
/**
@@ -14,7 +16,7 @@ class Site {
* @class Site
* @param {Object} config - config options for the site
*/
- constructor(config) {
+ constructor(config={}) {
this.source = config.source || process.cwd();
this.output = config.output || path.resolve(process.cwd(), 'site');
// holds all template files and their parsed data
@@ -24,18 +26,22 @@ class Site {
// this is what is used as a top level object for each file being rendered
// files content will also be interpolated into this value
this.config = config || {};
+ this.plugins = config.plugins ? merge(config.plugins, defaultPlugins) : defaultPlugins;
}
async crawl(directory) {
- let files = await readdir(directory);
+ // by default we want to crawl the source directory
+ if(!directory) directory = this.source;
+
+ const files = await readdir(directory);
for(var i = 0; i < files.length; i++) {
- let file = files[i];
- let stats = await stat(`${directory}/${file}`);
+ const file = files[i];
+ const stats = await stat(`${directory}/${file}`);
if(stats.isDirectory()) {
await this.crawl(`${directory}/${file}`);
}
if(stats.isFile() && file.substr(file.lastIndexOf('.'), file.length) == '.sy') {
- this.files.push(await parse(`${directory}/${file}`));
+ this.files.push(await parse(this.plugins, `${directory}/${file}`));
}
}
}
@@ -48,7 +54,7 @@ class Site {
*/
get data() {
const { files, config } = this;
- let d = {};
+ const d = {};
files.forEach((file) => {
const { options } = file;
@@ -67,18 +73,18 @@ class Site {
* @return {Object} - returns an object with the keys `layout` and `pages` that are hashmaps
*/
categorize(files) {
- let layouts = {};
- let pages = {};
+ const layouts = {};
+ const pages = {};
files.forEach((file) => {
- const { type, path, name } = file;
+ const { type, filePath, name } = file;
switch(type) {
case 'layout':
// we are using the name of the file instead of the path for easier search and reference
layouts[name] = file;
break;
default:
- pages[path] = file;
+ pages[filePath] = file;
break;
}
});
@@ -100,16 +106,16 @@ class Site {
this.files = [];
this.rendered = [];
- const { source, output, files, config } = this;
+ const { output, files, config } = this;
await ensureDirectoryExists(output);
- await this.crawl(source);
+ await this.crawl();
const { layouts, pages } = this.categorize(files);
const { data } = this;
for(var key of Object.keys(pages)) {
- let page = pages[key];
+ const page = pages[key];
// TODO: add how long the page took to render that could be used by the dev server as an overlay
@@ -118,11 +124,17 @@ class Site {
page.content = config.render(page.type, page.content);
}
- let options = await render(layouts, page, data);
+ const options = await render(this.plugins, layouts, page, data);
+
+ const outputFilePath = path.resolve(output, `${page.name}.html`);
- this.rendered.push(options);
+ this.rendered.push({
+ filePath: outputFilePath,
+ time: getTotalTimeOfDepends(options),
+ depends: options
+ });
- await writeFile(path.resolve(output, `${page.name}.html`), options.rendered);
+ await writeFile(outputFilePath, options.rendered);
}
}
}
diff --git a/lib/util.js b/lib/util.js
index 4aaee3f..5463826 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -18,7 +18,7 @@ const readdir = promisify(fs.readdir);
* @param {String} directory - the given path
*/
async function ensureDirectoryExists(directory) {
- let parts = directory.split('/').slice(1);
+ const parts = directory.split('/').slice(1);
let currentDirectory = '';
while (parts.length > 0) {
currentDirectory += `/${parts.shift()}`;
@@ -45,9 +45,10 @@ async function copyDirectory(source, destination) {
const files = await readdir(source);
- files.forEach(async function(childItemName) {
+ for(var i = 0; i < files.length; i++) {
+ const childItemName = files[i];
await copyDirectory(path.join(source, childItemName), path.join(destination, childItemName));
- });
+ }
} else {
await writeFile(destination, await readFile(source));
}
@@ -60,21 +61,17 @@ async function copyDirectory(source, destination) {
* @return {Object} - config object
*/
async function getConfig(directory) {
- async function resolve(config) {
+ try {
+ await stat(`${directory}/.sweeney`);
+
+ const config = require(`${directory}/.sweeney`);
+
switch (typeof config) {
case 'object':
- if (config instanceof Promise) {
- return await config();
- }
return config;
case 'function':
return config();
}
- }
-
- try {
- await stat(`${directory}/.sweeney`);
- return await resolve(require(`${directory}/.sweeney`));
} catch (ex) {
// propogate message to the user, this is something with the config
if (ex.message.indexOf('no such file or directory') === -1) {
@@ -91,36 +88,28 @@ async function getConfig(directory) {
* @param {String} content - Template string
* @return {Object} - parsed template output
*/
-function parseString(filePath, content) {
- let config = {};
+async function parseString(plugins, filePath, content) {
+ const config = {};
// this is the name of the file without the extension, used for internal use or during the output process
config.name = filePath.substring(filePath.lastIndexOf('/') + 1, filePath.length - 3);
- // let's check for any special sweeney tags
- // the first capture group is the block and the second is the argument
- if (/{{-- (.+?) (.+?) --}}/g.test(content)) {
- let reg = new RegExp(/{{-- (.+?) (.+?) --}}/g);
- let block;
- while ((block = reg.exec(content)) != null) {
- let type = block[1];
- let argument = block[2];
- if (!config[type]) config[type] = [];
- // let's normalize the path if given a relative one
- // TODO: this should be something that we can extend and deal with at the config / plugin level
- if (type === 'includes') {
- let oldArgument = JSON.parse(JSON.stringify(argument));
- argument = path.resolve(path.dirname(filePath), argument);
- // let's update the content of the file to reflect the need for the specific file
- content = content.replace(`{{-- ${type} ${oldArgument} --}}`, `{{-- ${type} ${argument} --}}`);
- }
- config[type].push(argument);
+ // let's run through the plugins
+ const pluginNames = Object.keys(plugins);
+ for(const name of pluginNames) {
+ const plugin = plugins[name];
+
+ const output = await plugin.parse(filePath, content);
+ if(output) {
+ if(output.content !== content) content = output.content; // update the content with the augmented one
+ if(output.found.length > 0) config[name] = output.found;
}
}
+
if (/^---\n/.test(content)) {
var end = content.search(/\n---\n/);
if (end != -1) {
- let parsed = JSON.parse(content.slice(4, end + 1));
+ const parsed = JSON.parse(content.slice(4, end + 1));
// this is the top level attribute in the render function that will allow users to do things like
// {{ posts.forEach((post) => {...}) }} _In this case posts will be an array_
config.collection = parsed.collection || 'page';
@@ -132,6 +121,7 @@ function parseString(filePath, content) {
if (parsed.layout) config.layout = parsed.layout;
}
}
+
return config;
}
@@ -141,16 +131,17 @@ function parseString(filePath, content) {
* @param {String} content - file contents that could potentially contain options
* @return {Object} - with the attributes options and content
*/
-async function parse(filePath) {
- let content = await readFile(filePath);
+async function parse(plugins, filePath) {
+ const content = await readFile(filePath, 'utf8');
- let config = {
- path: filePath,
+ const config = {
+ filePath,
options: {},
content: content
};
- return Object.assign(config, parseString(filePath, content.toString('utf8')));
+
+ return Object.assign(config, await parseString(plugins, filePath, content));
}
/**
@@ -178,24 +169,7 @@ function ms(ms) {
if (ms >= s) {
return `${Math.floor(ms / s)}s`;
}
- return ms + 'ms';
-}
-
-/**
- * from a single depend object, will recursively find the other dependencies and add them to a single array value
- * @param {Object} depend - a dependency object
- * @return {Array} - all dependency objects
- */
-function getRenderedDepends(depend) {
- let depends = [];
-
- depends.push(depend);
-
- if(depend.depends) {
- depends.concat(getRenderedDepends(depend.depends));
- }
-
- return depends;
+ return ms.toFixed(4).replace(/\.0000$/, '') + 'ms';
}
/**
@@ -206,44 +180,36 @@ function getRenderedDepends(depend) {
* @param {Object} additional - additional data that needs to be merged into template data
* @return {String} - rendered template to string
*/
-async function render(templates, template, additional = {}) {
- let start = process.hrtime();
- let { includes = [], content = '', path } = template;
+async function render(plugins, templates, template, additional = {}) {
+ const start = process.hrtime();
+
+ const { filePath } = template;
+ let { content = '' } = template;
+
// combine the data from the template and whatever is passed in
- let data = merge(additional, template);
- if (!content) return;
+ const data = merge(additional, template);
+ if(!content) return;
try {
- if (includes.length > 0) {
- // loop through the includes and figure out what we need to do
- for (var i = 0; i < includes.length; i++) {
- let include = includes[i];
- let ext = include.substring(include.lastIndexOf('.') + 1, include.length);
- let name = include.substring(include.lastIndexOf('/') + 1, include.length - 3);
- let _content = await readFile(include);
-
- switch (ext) {
- case 'sy':
- content = content.replace(`{{-- includes ${include} --}}`, await render(templates, templates[name], data));
- break;
- case 'css':
- content = content.replace(`{{-- includes ${include} --}}`, ``);
- break;
- case 'png':
- case 'jpeg':
- case 'gif':
- case 'jpg':
- case 'svg':
- // TODO: we probably want to copy the asset somewhere close by and reference it in the output correctly
- content = content.replace(`{{-- includes ${include} --}}`, '');
- break;
- default:
- // we are just going to read the file's content and inject it into the template
- content = content.replace(`{{-- includes ${include} --}}`, _content.toString('utf8'));
- break;
+ // let's run through rendering any plugin data that was parsed out
+ const pluginNames = Object.keys(plugins);
+ const tempDepends = [];
+ for(var p = 0; p < pluginNames.length; p++) {
+ const name = pluginNames[p];
+ const plugin = plugins[name];
+
+ if(data[name] && data[name].length > 0) {
+ for(const found of data[name]) {
+ const output = await plugin.render(plugins, filePath, content, templates, data, found);
+
+ if(output.content) content = output.content;
+ if(output.depends) tempDepends.push(output.depends);
}
}
}
+ if(tempDepends.length > 0) {
+ data.depends = [].concat.apply([], tempDepends);
+ }
const templ = content.replace(/[\r\t\n]/g, ' ');
const re = /{{[\s]*(.+?)[\s]*}}/g;
@@ -265,41 +231,43 @@ async function render(templates, template, additional = {}) {
add(templ.substr(cursor, templ.length - cursor));
code += 'return r.join("");';
- let rendered = new Function(`
+ const rendered = new Function(`
with(this) {
${code.replace(/[\r\t\n]/g, '')}
}
`).bind(data)();
// if this template has a layout file we must render this then the layout file
- let layout = template.layout && templates[template.layout];
+ const layout = template.layout && templates[template.layout];
+ // why are we doing this, well we pass around data a fairbit and we want to snapshot the state of the depends field at this point
+ const depends = data.depends ? JSON.parse(JSON.stringify(data.depends)) : [];
if (layout) {
// we have to delete the old layout
delete data['layout'];
- let output = await render(templates, layout, Object.assign(data, {
- child: rendered
- }));
-
- let depends = getRenderedDepends(output);
+ const output = await render(plugins, templates, layout, Object.assign({
+ child: rendered,
+ depends: [] // pass in an empty data to stop from creating duplicate data
+ }, data));
return {
- path,
- depends,
+ filePath,
+ depends: output,
rendered: output.rendered,
time: process.hrtime(start)[1]/1000000
};
} else {
return {
- path,
+ filePath,
rendered,
+ depends, // this is if any plugin has added dependency information to the template
time: process.hrtime(start)[1]/1000000
};
}
} catch (ex) {
throw new Error(JSON.stringify({
- error: `Error building template ${path}`,
+ error: `Error building template ${filePath}`,
content: content,
stack: ex.stack
}));
@@ -333,16 +301,48 @@ function merge(target, source) {
return target;
}
-function renderSubDepends(item, level) {
+/**
+ * recursively goes through render item to find the total time of rendered dependencies
+ * @param {Object} item - rendered item output by render method
+ * @return {Number} - time in milleseconds
+ */
+function getTotalTimeOfDepends(item) {
+ let time = 0;
+
+ if(Array.isArray(item)) {
+ time += item.map((i) => getTotalTimeOfDepends(i)).reduce((a, b) => a + b, 0);
+ }
+
+ if(!Array.isArray(item) && typeof item === 'object') {
+ time += item.time;
+ }
+
+ // recursively find the depends values
+ if(item.depends) time += getTotalTimeOfDepends(item.depends);
+
+ return time;
+}
+
+/**
+ * renders a dependency tree from the given render item
+ * @param {Object} item - render item
+ * @param {Number} level - the level of the tree that the element is a part of (by default is 0)
+ * @return {String} - ascii representation of render tree for the given item
+ */
+function renderSubDepends(item, level=0) {
let output = '';
+
if(Array.isArray(item)) {
- output += item.map((i) => '\n' + renderSubDepends(i, level)).join('');
+ output += item.map((i) => renderSubDepends(i, level)).join('');
}
+
if(!Array.isArray(item) && typeof item === 'object') {
- output += `${level === 0 ? '' : ' '.repeat(level * 4) + '-'} ${item.path} [${ms(item.time)}]`;
+ output += '\n' + `${level === 0 ? '' : ' '.repeat(level * 2) + '-'} ${item.filePath} [${ms(item.time)}]`;
}
+
if(item.depends) output += renderSubDepends(item.depends, level + 1);
- return output;
+
+ return output ;
}
module.exports = {
@@ -354,5 +354,6 @@ module.exports = {
getConfig,
ensureDirectoryExists,
copyDirectory,
- renderSubDepends
+ renderSubDepends,
+ getTotalTimeOfDepends
};
diff --git a/package.json b/package.json
index c2c56e9..87602c8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "sweeney",
- "version": "1.0.2",
+ "version": "1.1.0",
"description": "💈 A static site generator that cuts the way you want it to",
"author": "Gabriel J. Csapo
",
"license": "Apache-2.0",
@@ -15,11 +15,20 @@
"homepage": "https://www.gabrielcsapo.com/sweeney",
"scripts": {
"lint": "eslint .",
- "test": "tape test/util.js",
- "coverage": "tap test --coverage --coverage-report=lcov",
+ "pretest": "rm -rf test/tmp",
+ "test": "tape test/*.js | tap-diff",
+ "coverage": "npm run pretest && tap test --coverage --coverage-report=lcov",
"generate-docs": "jsdoc -c .jsdoc.json",
"generate-example": "bin/sweeney.js build --output ./docs/example --source ./example"
},
+ "nyc": {
+ "exclude": [
+ "docs/**",
+ "example/**",
+ "coverage/**"
+ ],
+ "all": true
+ },
"bin": {
"sweeney": "./bin/sweeney.js"
},
@@ -29,6 +38,7 @@
"markdown-it": "^8.4.0",
"minami": "^1.2.3",
"tap": "^11.0.0",
+ "tap-diff": "^0.1.1",
"tape": "^4.8.0"
},
"dependencies": {
diff --git a/test/defaultPlugins.js b/test/defaultPlugins.js
new file mode 100644
index 0000000..4f2aa71
--- /dev/null
+++ b/test/defaultPlugins.js
@@ -0,0 +1,76 @@
+const test = require('tape');
+const path = require('path');
+
+const defaultPlugins = require('../lib/defaultPlugins');
+
+test('@defaultPlugins', (t) => {
+ t.plan(1);
+
+ t.test('@includes', (t) => {
+ t.plan(2);
+
+ t.test('should parse file with css and sy paths correctly', async (t) => {
+ const parse = defaultPlugins['includes'].parse;
+
+ const output = await parse(path.resolve(__dirname, 'fixtures', 'includes.sy'), `
+
+
+
+
+
+
+
+ {{ options.title || site.title }}
+ {{-- includes ../site.css --}}
+
+
+
+
+
+ {{-- includes ./nav.sy --}}
+
+
+
+
+
+ `);
+
+ t.deepEqual(output, {
+ content: '\n \n\n \n \n \n \n\n {{ options.title || site.title }}\n {{-- includes /Users/gcsapo/Documents/sweeney/test/site.css --}}\n \n \n\n\n \n {{-- includes /Users/gcsapo/Documents/sweeney/test/fixtures/nav.sy --}}\n\n \n \n
\n {{ child }}\n
\n\n \n
\n \n\n ',
+ found: [
+ path.resolve(__dirname, 'site.css'),
+ path.resolve(__dirname, 'fixtures', 'nav.sy'),
+ ]
+ });
+
+ t.end();
+ });
+
+ t.test('should be able to render parsed template', async (t) => {
+ const render = defaultPlugins['includes'].render;
+
+ const output = await render({
+ includes: defaultPlugins['includes']
+ },
+ path.resolve(__dirname, 'fixtures', 'includes.sy'),
+ '\n \n\n \n \n \n \n\n {{ options.title || site.title }}\n {{-- includes /Users/gcsapo/Documents/sweeney/test/fixtures/test.css --}}\n \n \n\n\n \n {{-- includes /Users/gcsapo/Documents/sweeney/test/fixtures/nav.sy --}}\n\n \n \n
\n {{ child }}\n
\n\n \n
\n \n\n ', [], {},
+ path.resolve(__dirname, 'fixtures', 'test.css')
+ );
+
+ t.deepEqual(output.depends.length, 1);
+ t.equal(output.depends[0].filePath, path.resolve(__dirname, 'fixtures', 'test.css'));
+
+ t.deepEqual(output.content, '\n \n\n \n \n \n \n\n {{ options.title || site.title }}\n \n \n \n\n\n \n {{-- includes /Users/gcsapo/Documents/sweeney/test/fixtures/nav.sy --}}\n\n \n \n
\n {{ child }}\n
\n\n \n
\n \n\n ');
+
+ t.end();
+ });
+ });
+});
diff --git a/test/fixtures/depend.sy b/test/fixtures/depend.sy
new file mode 100644
index 0000000..7a5768e
--- /dev/null
+++ b/test/fixtures/depend.sy
@@ -0,0 +1,9 @@
+---
+{
+ "type": "page"
+}
+---
+
+
+ {{-- includes ./render.sy --}}
+
diff --git a/test/fixtures/includes.sy b/test/fixtures/includes.sy
new file mode 100644
index 0000000..b679e1c
--- /dev/null
+++ b/test/fixtures/includes.sy
@@ -0,0 +1,36 @@
+---
+{
+ "type": "layout"
+}
+---
+
+
+
+
+
+
+
+
+
+ {{ options.title || site.title }}
+ {{-- includes ../site.css --}}
+
+
+
+
+
+ {{-- includes ./nav.sy --}}
+
+
+
+
+
+
diff --git a/test/fixtures/sub.sy b/test/fixtures/sub.sy
new file mode 100644
index 0000000..9576b19
--- /dev/null
+++ b/test/fixtures/sub.sy
@@ -0,0 +1,10 @@
+---
+{
+ "type": "page"
+}
+---
+
+
+ {{-- includes ./depend.sy --}}
+ {{-- includes ./render.sy --}}
+
diff --git a/test/fixtures/test.css b/test/fixtures/test.css
new file mode 100644
index 0000000..05f72b0
--- /dev/null
+++ b/test/fixtures/test.css
@@ -0,0 +1,226 @@
+* {
+ font-family: Baskerville;
+ font-weight: normal;
+}
+
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+.top-rect {
+ width: 100%;
+ padding-top: 300px;
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: -1;
+ background: linear-gradient(to bottom right, #3e3e3e, #1a1a1a);
+ overflow: hidden;
+ overflow-x: hidden;
+ overflow-y: hidden;
+}
+
+.top-rect:after {
+ content: "";
+ display: block;
+ background: #fff;
+ height: 300px;
+ width: 8000px;
+ position: absolute;
+ z-index: 1;
+ bottom: -50%;
+ right: 50%;
+ margin-right: -4000px;
+ -webkit-transform: rotate(6deg);
+ transform: rotate(6deg);
+}
+
+.sweeney {
+ font-family: Baskerville;
+ font-weight: 600;
+ font-style: italic;
+ margin: 5px;
+}
+
+.sweeney-title {
+ font-size: 40px;
+}
+
+.sweeney-version {
+ color: #797877;
+ text-decoration-color: #797877;
+}
+
+.sweeney-description {
+ font-size: 19px;
+ font-weight: 300;
+}
+
+.sweeney-logo {
+ max-height: 150px;
+}
+
+.page {
+ min-height: 100vh;
+ position: relative;
+}
+
+.page-padded-center {
+ width: 50%;
+ margin: 0 auto;
+ padding-bottom: 30px;
+}
+
+.page-centered-content {
+ color: #3e3e3e;
+ text-align: center;
+ width: 100%;
+ position: relative;
+ display: flex;
+ min-height: 100vh;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto;
+}
+
+.section {}
+.section-sub {
+ margin-left: 10px;
+ margin-top: 10px;
+}
+
+.header {
+ font-size: 23px;
+ font-weight: bold;
+ color: black;
+ text-decoration: none;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ display: block;
+}
+
+.nav {
+ margin: 15px;
+ position: absolute;
+ z-index: 1000;
+ top: 0;
+ right: 0;
+}
+
+.nav > .button {
+ border-color: #fff;
+ color: #fff;
+}
+.nav > .button:hover {
+ border-color: #fff;
+ background-color: #fff;
+ color: #3e3e3e;
+}
+
+.button {
+ font-weight: 600;
+ display: inline-block;
+ padding: 8px 0;
+ width: 130px;
+ color: #3e3e3e;
+ border: 1px solid #3e3e3e;
+ border-radius: 5px;
+ text-decoration: none;
+ font-size: 20px;
+ transition: opacity 200ms;
+ text-align: center;
+ margin: 10px 5px;
+}
+
+.button-getting-started {
+ width: 160px;
+ margin-top: 15px;
+}
+
+.button:hover {
+ background: #3e3e3e;
+ color: #ededed;
+ border: 1px solid #3e3e3e;
+}
+
+.terminal {
+ border: 1px solid rgba(62, 62, 62, 0.56);
+ background-color: #3e3e3e;
+ color: #ededed;
+ border-radius: 5px;
+ padding: 10px;
+ overflow: scroll;
+}
+
+.terminal-install {
+ margin: 0 auto;
+ text-align: left;
+ width: 240px;
+ white-space: normal;
+ overflow: hidden;
+}
+
+.terminal code {
+ display: block;
+ font-size: 16px;
+ color: white;
+ font-weight: 300;
+}
+
+code {
+ color: #d75e4e;
+ font-weight: bold;
+}
+
+.code-example {
+ border: 1px solid rgba(62, 62, 62, 0.30);
+ border-radius: 5px;
+ padding: 10px;
+ word-wrap: break-word;
+}
+
+.footer {
+ width: 100%;
+ border-top: 1px solid rgba(62, 62, 62, 0.30);
+ text-align: center;
+ margin-top: 20px;
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.footer-link {
+ color: black;
+ font-weight: bold;
+ text-decoration: none;
+}
+
+b {
+ font-weight: bold;
+}
+
+pre, pre * {
+ font-family: monospace;
+}
+
+blockquote {
+ margin: 0;
+ border-left: 3px solid #3e3e3e;
+ padding-left: 5px;
+ font-weight: 100;
+}
+
+@media only screen and (max-width: 800px) {
+ body {
+ margin-top: 75px;
+ }
+ .nav > .button {
+ width: 125px;
+ font-size: 14px;
+ display: inline-block;
+ margin: 5px;
+ }
+}
diff --git a/test/site.js b/test/site.js
new file mode 100644
index 0000000..f09643b
--- /dev/null
+++ b/test/site.js
@@ -0,0 +1,202 @@
+const test = require('tape');
+const path = require('path');
+
+const Site = require('../lib/site');
+
+test('@site', (t) => {
+ t.plan(3);
+
+ t.test('should be able to get a instance of site without any data being passed to it', (t) => {
+ const site = new Site();
+ t.equal(site.source, path.resolve(__dirname, '..'));
+ t.equal(site.output, path.resolve(__dirname, '..', 'site'));
+ t.deepEqual(site.files, []);
+ t.deepEqual(site.rendered, []);
+ t.deepEqual(site.config, {});
+ t.ok(site.plugins['includes']);
+ t.equal(typeof site.plugins['includes']['parse'], 'function');
+ t.equal(typeof site.plugins['includes']['render'], 'function');
+
+ t.end();
+ });
+
+ t.test('@crawl', (t) => {
+ t.plan(1);
+
+ t.test('should be able to crawl fixtures directory properly', async (t) => {
+ const site = new Site({
+ source: path.resolve(__dirname, 'fixtures')
+ });
+
+ await site.crawl();
+
+ t.deepEqual(site.files, [{
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/depend.sy`,
+ 'options': {
+ 'type': 'page'
+ },
+ 'content': `\n\n {{-- includes ${path.resolve(__dirname, 'fixtures')}/render.sy --}}\n
\n`,
+ 'name': 'depend',
+ 'includes': [
+ `${path.resolve(__dirname, 'fixtures')}/render.sy`
+ ],
+ 'collection': 'page',
+ 'type': 'page'
+ },
+ {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/includes.sy`,
+ 'options': {
+ 'type': 'layout'
+ },
+ 'content': `\n\n\n\n \n \n \n \n\n {{ options.title || site.title }}\n {{-- includes ${__dirname}/site.css --}}\n \n \n\n\n \n {{-- includes ${path.resolve(__dirname, 'fixtures')}/nav.sy --}}\n\n \n \n
\n {{ child }}\n
\n\n \n
\n \n\n\n`,
+ 'name': 'includes',
+ 'includes': [
+ `${path.resolve(__dirname)}/site.css`,
+ `${path.resolve(__dirname, 'fixtures')}/nav.sy`
+ ],
+ 'collection': 'page',
+ 'type': 'layout'
+ },
+ {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/render.sy`,
+ 'options': {
+ 'title': 'Welcome to Sweeney!',
+ 'tags': [
+ 'sweeney',
+ 'example'
+ ]
+ },
+ 'content': '\n
\n {{ options.tags.map((tag) => `- ${tag}
`).join(\'\') }}\n
\n
\n',
+ 'name': 'render',
+ 'collection': 'page',
+ 'type': 'html'
+ },
+ {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/sub.sy`,
+ 'options': {
+ 'type': 'page'
+ },
+ 'content': `\n\n {{-- includes ${path.resolve(__dirname, 'fixtures')}/depend.sy --}}\n {{-- includes ${path.resolve(__dirname, 'fixtures')}/render.sy --}}\n
\n`,
+ 'name': 'sub',
+ 'includes': [
+ `${path.resolve(__dirname, 'fixtures')}/depend.sy`,
+ `${path.resolve(__dirname, 'fixtures')}/render.sy`
+ ],
+ 'collection': 'page',
+ 'type': 'page'
+ },
+ {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/test.sy`,
+ 'options': {
+ 'title': 'Welcome to Sweeney!',
+ 'tags': [
+ 'sweeney',
+ 'example'
+ ]
+ },
+ 'content': `\n\n\n{{-- includes ${__dirname}/partials/head.html --}}\n\n\n\n{{-- includes ${__dirname}/partials/header.html --}}\n\n\n\n{{-- includes ${__dirname}/partials/footer.html --}}\n\n\n\n\n`,
+ 'name': 'test',
+ 'includes': [
+ `${__dirname}/partials/head.html`,
+ `${__dirname}/partials/header.html`,
+ `${__dirname}/partials/footer.html`
+ ],
+ 'collection': 'page',
+ 'type': 'html'
+ },
+ {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/throws-error.sy`,
+ 'options': {
+ 'title': 'Welcome to Sweeney!',
+ 'tags': [
+ 'sweeney',
+ 'example'
+ ]
+ },
+ 'content': '\n
\n {{ tagss.map((tag) => `- ${tag}
`)}}\n
\n
\n',
+ 'name': 'throws-error',
+ 'collection': 'page',
+ 'type': 'html'
+ }
+ ]);
+
+ t.end();
+ });
+
+ });
+
+ t.test('@categorize', (t) => {
+ t.plan(1);
+
+ t.test('should be able to categorize a mix of pages and layouts', (t) => {
+ const site = new Site({
+ source: path.resolve(__dirname, 'fixtures')
+ });
+
+ const files = [{
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/depend.sy`,
+ 'options': {
+ 'type': 'page'
+ },
+ 'content': `\n\n {{-- includes ${path.resolve(__dirname, 'fixtures')}/render.sy --}}\n
\n`,
+ 'name': 'depend',
+ 'includes': [
+ `${path.resolve(__dirname, 'fixtures')}/render.sy`
+ ],
+ 'collection': 'page',
+ 'type': 'page'
+ },
+ {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/includes.sy`,
+ 'options': {
+ 'type': 'layout'
+ },
+ 'content': `\n\n\n\n \n \n \n \n\n {{ options.title || site.title }}\n {{-- includes ${__dirname}/site.css --}}\n \n \n\n\n \n {{-- includes ${path.resolve(__dirname, 'fixtures')}/nav.sy --}}\n\n \n \n
\n {{ child }}\n
\n\n \n
\n \n\n\n`,
+ 'name': 'includes',
+ 'includes': [
+ `${path.resolve(__dirname)}/site.css`,
+ `${path.resolve(__dirname, 'fixtures')}/nav.sy`
+ ],
+ 'collection': 'page',
+ 'type': 'layout'
+ }
+ ];
+
+ t.deepEqual(site.categorize(files), {
+ layouts: {
+ includes: {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/includes.sy`,
+ 'options': {
+ 'type': 'layout'
+ },
+ 'content': `\n\n\n\n \n \n \n \n\n {{ options.title || site.title }}\n {{-- includes ${__dirname}/site.css --}}\n \n \n\n\n \n {{-- includes ${path.resolve(__dirname, 'fixtures')}/nav.sy --}}\n\n \n \n
\n {{ child }}\n
\n\n \n
\n \n\n\n`,
+ 'name': 'includes',
+ 'includes': [
+ `${path.resolve(__dirname)}/site.css`,
+ `${path.resolve(__dirname, 'fixtures')}/nav.sy`
+ ],
+ 'collection': 'page',
+ 'type': 'layout'
+ }
+ },
+ pages: {
+ '/Users/gcsapo/Documents/sweeney/test/fixtures/depend.sy': {
+ 'filePath': `${path.resolve(__dirname, 'fixtures')}/depend.sy`,
+ 'options': {
+ 'type': 'page'
+ },
+ 'content': `\n\n {{-- includes ${path.resolve(__dirname, 'fixtures')}/render.sy --}}\n
\n`,
+ 'name': 'depend',
+ 'includes': [
+ `${path.resolve(__dirname, 'fixtures')}/render.sy`
+ ],
+ 'collection': 'page',
+ 'type': 'page'
+ }
+ }
+ });
+
+ t.end();
+ });
+ });
+});
diff --git a/test/util.js b/test/util.js
index cc4042f..febe7cd 100644
--- a/test/util.js
+++ b/test/util.js
@@ -4,8 +4,22 @@ const test = require('tape');
const { promisify } = require('util');
const stat = promisify(fs.stat);
-
-const { ms, merge, render, parse, parseString, getConfig, ensureDirectoryExists } = require('../lib/util');
+const readdir = promisify(fs.readdir);
+
+const {
+ ms,
+ merge,
+ render,
+ parse,
+ parseString,
+ getConfig,
+ ensureDirectoryExists,
+ renderSubDepends,
+ getTotalTimeOfDepends,
+ copyDirectory
+} = require('../lib/util');
+
+const defaultPlugins = require('../lib/defaultPlugins');
test('util', (t) => {
@@ -13,28 +27,57 @@ test('util', (t) => {
t.plan(2);
t.test('should be able to merge nested objects', (t) => {
- let merged = merge({ d: { t: 'hi' } }, { d: { f: 'b' } });
+ const merged = merge({
+ d: {
+ t: 'hi'
+ }
+ }, {
+ d: {
+ f: 'b'
+ }
+ });
- t.deepEqual(merged, { d: { t: 'hi', f: 'b' } });
+ t.deepEqual(merged, {
+ d: {
+ t: 'hi',
+ f: 'b'
+ }
+ });
t.end();
});
t.test('should be able to merge arrays of objects', (t) => {
- let merged = merge({ d: { t: 'hi', c: ['bob'] } }, { d: { f: 'b', c: ['foo', 'bar'] } });
+ const merged = merge({
+ d: {
+ t: 'hi',
+ c: ['bob']
+ }
+ }, {
+ d: {
+ f: 'b',
+ c: ['foo', 'bar']
+ }
+ });
- t.deepEqual(merged, { d: { t: 'hi', f: 'b', c: ['bob', 'foo', 'bar'] } });
+ t.deepEqual(merged, {
+ d: {
+ t: 'hi',
+ f: 'b',
+ c: ['bob', 'foo', 'bar']
+ }
+ });
t.end();
});
});
t.test('@render', (t) => {
- t.plan(2);
+ t.plan(3);
t.test('should throw an error with template', async (t) => {
- const parsed = await parse(path.resolve(__dirname, './fixtures/throws-error.sy'));
+ const parsed = await parse(defaultPlugins, path.resolve(__dirname, './fixtures/throws-error.sy'));
try {
- const rendered = await render([], parsed, {});
+ const rendered = await render(defaultPlugins, [], parsed, {});
t.fail(`${rendered} should not be rendered`);
} catch (ex) {
const error = JSON.parse(ex.message);
@@ -48,14 +91,35 @@ test('util', (t) => {
});
t.test('should render template properly', async (t) => {
- const parsed = await parse(path.resolve(__dirname, './fixtures/render.sy'));
- const rendered = await render([], parsed, {});
+ const parsed = await parse(defaultPlugins, path.resolve(__dirname, './fixtures/render.sy'));
+ const rendered = await render(defaultPlugins, [], parsed, {});
- t.deepEqual(Object.keys(rendered), ['path', 'rendered', 'time']);
+ t.deepEqual(Object.keys(rendered), ['filePath', 'rendered', 'depends', 'time']);
t.equal(typeof rendered.time, 'number');
+ t.deepEqual(rendered.depends, []);
t.equal(rendered.rendered, ' ');
- t.equal(rendered.path, path.resolve(__dirname, './fixtures/render.sy'));
+ t.equal(rendered.filePath, path.resolve(__dirname, './fixtures/render.sy'));
+
+ t.end();
+ });
+ t.test('should be able to properly add depends to template when rendering sub templates', async (t) => {
+ const parsed = await parse(defaultPlugins, path.resolve(__dirname, './fixtures/sub.sy'));
+ const rendered = await render(defaultPlugins, [], parsed, {});
+
+ t.equal(rendered.filePath, path.resolve(__dirname, './fixtures/sub.sy'));
+ t.equal(rendered.rendered, ' - sweeney
- example
- sweeney
- example
');
+ t.equal(rendered.depends.length, 2);
+ t.equal(rendered.depends[0].filePath, path.resolve(__dirname, './fixtures/depend.sy'));
+ t.equal(rendered.depends[0].rendered, ' ');
+ t.equal(rendered.depends[0].depends.length, 1);
+ t.equal(rendered.depends[0].depends[0].filePath, path.resolve(__dirname, './fixtures/render.sy'));
+ t.equal(rendered.depends[0].depends[0].rendered, ' ');
+ t.equal(rendered.depends[0].depends[0].depends.length, 0);
+
+ t.equal(rendered.depends[1].filePath, path.resolve(__dirname, './fixtures/render.sy'));
+ t.equal(rendered.depends[1].rendered, ' - sweeney
- example
- sweeney
- example
');
+ t.equal(rendered.depends[1].depends.length, 0);
t.end();
});
});
@@ -64,10 +128,10 @@ test('util', (t) => {
t.plan(1);
t.test('@parse: should be able to parse file with options', (async (t) => {
- const parsed = await parse(path.resolve(__dirname, './fixtures/test.sy'));
+ const parsed = await parse(defaultPlugins, path.resolve(__dirname, './fixtures/test.sy'));
t.deepEqual({
- path: path.resolve(__dirname, 'fixtures/test.sy'),
+ filePath: path.resolve(__dirname, 'fixtures/test.sy'),
options: {
title: 'Welcome to Sweeney!',
tags: ['sweeney', 'example']
@@ -88,10 +152,10 @@ test('util', (t) => {
});
t.test('@parseString', (t) => {
- t.plan(2);
+ t.plan(3);
- t.test('should be able to string with options', (t) => {
- let parsed = parseString('/foo/bar/something.sy', '---\n{ "layout": "post", "title": "Welcome to Jekyll!", "date": "2014-10-18 12:58:29", "categories": "jekyll update" }\n---\n# Hello world');
+ t.test('should be able to string with options', async (t) => {
+ const parsed = await parseString(defaultPlugins, '/foo/bar/something.sy', '---\n{ "layout": "post", "title": "Welcome to Jekyll!", "date": "2014-10-18 12:58:29", "categories": "jekyll update" }\n---\n# Hello world');
t.equal(parsed.name, 'something');
t.equal(parsed.layout, 'post');
@@ -106,8 +170,8 @@ test('util', (t) => {
t.end();
});
- t.test('should be able to parse sweeney tags', (t) => {
- let parsed = parseString('/foo/bar/default.sy', `---
+ t.test('should be able to parse sweeney tags', async (t) => {
+ const parsed = await parseString(defaultPlugins, '/foo/bar/default.sy', `---
{
"title": "Welcome to Sweeney!",
"tags": ["sweeney", "example"]
@@ -152,6 +216,40 @@ test('util', (t) => {
t.end();
});
+
+ t.test('should work with custom plugins object', async (t) => {
+ const parsed = await parseString({
+ test: {
+ parse(filePath, content) {
+ const reg = /{{-- test\('(.+?)'\) --}}/g;
+
+ if (content.match(reg)) {
+ const found = [];
+ let block;
+ while ((block = reg.exec(content)) != null) {
+ found.push(block[1]);
+ }
+ return {
+ content,
+ found
+ };
+ }
+ return false;
+ }
+ }
+ }, '/foo/bar/default.sy', `
+
+ {{-- test('should do something') --}}
+
+ `);
+
+ t.deepEqual(parsed, {
+ name: 'default',
+ test: ['should do something']
+ });
+
+ t.end();
+ });
});
t.test('@ensureDirectoryExists', (async (t) => {
@@ -160,7 +258,7 @@ test('util', (t) => {
await ensureDirectoryExists(dir);
try {
- stat(dir);
+ await stat(dir);
t.end();
} catch (ex) {
t.fail(ex);
@@ -171,39 +269,43 @@ test('util', (t) => {
t.plan(4);
t.test('should be able to read .sweeney file that exists', async (t) => {
- let dir = path.resolve(__dirname, 'fixtures', 'config', 'export');
- let config = await getConfig(dir);
+ const dir = path.resolve(__dirname, 'fixtures', 'config', 'export');
+ const config = await getConfig(dir);
t.equal(typeof config, 'object');
t.end();
});
t.test('should be able to read .sweeney file that returns a promise', async (t) => {
- let dir = path.resolve(__dirname, 'fixtures', 'config', 'promise');
- let config = await getConfig(dir);
+ const dir = path.resolve(__dirname, 'fixtures', 'config', 'promise');
+ const config = await getConfig(dir);
t.equal(typeof config, 'object');
- t.deepEqual(config, { foo: { hello: 'world' } });
+ t.deepEqual(config, {
+ foo: {
+ hello: 'world'
+ }
+ });
t.end();
});
t.test('should not throw an error if no config is found', async (t) => {
try {
- let config = await getConfig(__dirname);
+ const config = await getConfig(__dirname);
t.deepEqual(config, {});
t.end();
- } catch(ex) {
+ } catch (ex) {
t.fail(ex);
}
});
- t.test('should propogate error to user if the config has an error', async(t) => {
- let dir = path.resolve(__dirname, 'fixtures', 'config', 'throw');
+ t.test('should propogate error to user if the config has an error', async (t) => {
+ const dir = path.resolve(__dirname, 'fixtures', 'config', 'throw');
try {
- let config = await getConfig(dir);
+ const config = await getConfig(dir);
t.fail(config);
- } catch(ex) {
+ } catch (ex) {
t.equal(ex.message, 'I am broken!');
t.ok(ex.stack.indexOf(dir) > -1, 'ensure the path to the config is in the error (retain the error object, don\'t alter it)');
t.end();
@@ -211,25 +313,211 @@ test('util', (t) => {
});
});
- t.test('@ms', (t) => {
+ t.test('@copyDirectory', (t) => {
+ t.plan(1);
+
+ t.test('should be able to copy example directory and all its content', async (t) => {
+ const destination = path.resolve(__dirname, 'tmp', 'copy');
+ const source = path.resolve(__dirname, '..', 'example');
+ await copyDirectory(source, destination);
+
+ const files = await readdir(destination);
+
+ t.deepEqual(files, [
+ '.sweeney',
+ 'about.sy',
+ 'index.sy',
+ 'layouts',
+ 'posts',
+ 'posts.sy',
+ 'projects.sy',
+ 'site.css',
+ 'sweeney.svg'
+ ]);
+
+ // ensure sub directories have files
+ const subFiles = await readdir(path.resolve(destination, 'layouts'));
+
+ t.deepEqual(subFiles, [
+ 'default.sy',
+ 'nav.sy',
+ 'page.sy',
+ 'post.sy'
+ ]);
+
+ t.end();
+ });
+ });
+
+ t.test('@getTotalTimeOfDepends', (t) => {
+ t.plan(1);
+
+ const rendered = {
+ filePath: 'foo',
+ time: 100,
+ depends: [{
+ filePath: 'boo',
+ time: 150,
+ depends: [{
+ filePath: 'let',
+ time: 250,
+ depends: [{
+ filePath: 'hoooot',
+ time: 330
+ }]
+ }]
+ }, {
+ filePath: 'hoo',
+ time: 130,
+ depends: [{
+ filePath: 'hoot',
+ time: 50
+ }]
+ }]
+ };
+
+ t.test('should be able to get all of the times aggregated', (t) => {
+ const time = getTotalTimeOfDepends(rendered);
+ t.equal(time, 1010);
+ t.end();
+ });
+
+ });
+
+ t.test('@renderSubDepends', (t) => {
t.plan(4);
- t.test('@ms seconds', (t) => {
+ const rendered = {
+ filePath: 'foo',
+ time: 100,
+ depends: [{
+ filePath: 'boo',
+ time: 150,
+ depends: [{
+ filePath: 'let',
+ time: 250,
+ depends: [{
+ filePath: 'hoooot',
+ time: 330
+ }]
+ }]
+ }, {
+ filePath: 'hoo',
+ time: 130,
+ depends: [{
+ filePath: 'hoot',
+ time: 50
+ }]
+ }]
+ };
+
+ t.test('should be able to resolve recursive dependencies', (t) => {
+ const output = renderSubDepends(rendered, 0);
+
+ t.equal(output, '\n foo [100ms]\n - boo [150ms]\n - let [250ms]\n - hoooot [330ms]\n - hoo [130ms]\n - hoot [50ms]');
+
+ t.end();
+ });
+
+ t.test('should work when no starting level is given (default 0)', (t) => {
+ const output = renderSubDepends(rendered);
+
+ t.equal(output, '\n foo [100ms]\n - boo [150ms]\n - let [250ms]\n - hoooot [330ms]\n - hoo [130ms]\n - hoot [50ms]');
+
+ t.end();
+ });
+
+ t.test('should honor offset starting level at 1', (t) => {
+ const output = renderSubDepends(rendered, 1);
+
+ t.equal(output, '\n - foo [100ms]\n - boo [150ms]\n - let [250ms]\n - hoooot [330ms]\n - hoo [130ms]\n - hoot [50ms]');
+
+ t.end();
+ });
+
+ t.test('should work with array of files', (t) => {
+ const rendered = [{
+ filePath: '~/sweeney/example/about.sy',
+ depends: {
+ filePath: '~/sweeney/example/layouts/default.sy',
+ time: 0.63868
+ },
+ time: 0.932518
+ },
+ {
+ filePath: '~/sweeney/example/index.sy',
+ depends: {
+ filePath: '~/sweeney/example/layouts/default.sy',
+ time: 0.505146
+ },
+ time: 0.820841
+ },
+ {
+ filePath: '~/sweeney/example/posts/2017-11-20-welcome-to-sweeney.sy',
+ depends: {
+ filePath: '~/sweeney/example/layouts/post.sy',
+ depends: [{
+ filePath: '~/sweeney/example/index.sy',
+ depends: {
+ filePath: '~/sweeney/example/layouts/default.sy',
+ time: 0.505146
+ },
+ time: 0.820841
+ }],
+ time: 1.35372
+ },
+ time: 1.84443
+ },
+ {
+ filePath: '~/sweeney/example/posts.sy',
+ depends: {
+ filePath: '~/sweeney/example/layouts/default.sy',
+ time: 1.0139
+ },
+ time: 2.090657
+ },
+ {
+ filePath: '~/sweeney/example/projects.sy',
+ depends: {
+ filePath: '~/sweeney/example/layouts/default.sy',
+ time: 0.482915
+ },
+ time: 0.934869
+ }
+ ];
+
+ const output = renderSubDepends(rendered, 1);
+
+ t.equal(output, '\n - ~/sweeney/example/about.sy [0.9325ms]\n - ~/sweeney/example/layouts/default.sy [0.6387ms]\n - ~/sweeney/example/index.sy [0.8208ms]\n - ~/sweeney/example/layouts/default.sy [0.5051ms]\n - ~/sweeney/example/posts/2017-11-20-welcome-to-sweeney.sy [1.8444ms]\n - ~/sweeney/example/layouts/post.sy [1.3537ms]\n - ~/sweeney/example/index.sy [0.8208ms]\n - ~/sweeney/example/layouts/default.sy [0.5051ms]\n - ~/sweeney/example/posts.sy [2.0907ms]\n - ~/sweeney/example/layouts/default.sy [1.0139ms]\n - ~/sweeney/example/projects.sy [0.9349ms]\n - ~/sweeney/example/layouts/default.sy [0.4829ms]');
+
+ t.end();
+ });
+ });
+
+ t.test('@ms', (t) => {
+ t.plan(5);
+
+ t.test('milleseconds', (t) => {
+ t.equal(ms(600), '600ms');
+ t.end();
+ });
+
+ t.test('seconds', (t) => {
t.equal(ms(6000), '6s');
t.end();
});
- t.test('@ms minutes', (t) => {
+ t.test('minutes', (t) => {
t.equal(ms(600000), '10m');
t.end();
});
- t.test('@ms hours', (t) => {
+ t.test('hours', (t) => {
t.equal(ms(6000000), '1h');
t.end();
});
- t.test('@ms days', (t) => {
+ t.test('days', (t) => {
t.equal(ms(600000000), '6d');
t.end();
});