Skip to content

Commit

Permalink
Merge pull request #6 from cloudfour/feature-data
Browse files Browse the repository at this point in the history
Data and File Parsing
  • Loading branch information
lyzadanger committed Mar 4, 2016
2 parents 4e730ee + 38376cf commit 5c72725
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 61 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"dependencies": {
"bluebird": "^3.3.1",
"globby": "^4.0.0",
"handlebars": "^4.0.5"
"handlebars": "^4.0.5",
"js-yaml": "^3.5.3"
},
"devDependencies": {
"babel-cli": "^6.5.1",
Expand Down
36 changes: 15 additions & 21 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,27 @@ var parseOptions = require('./options');
var prepareTemplates = require('./template').prepareTemplates;
var utils = require('./utils');


/**
* Build a data/context object for use by the builder
* @TODO This may move into its own module if it seems appropriate
*
* @param {Object} options
* @return {Promise} resolving to {Object} of keyed file data
*/
function prepareData (options) {
// Data data
return utils.readFilesKeyed(options.data.src, {
contentFn: options.data.parseFn
});
}
/**
* Build the drizzle output
*
* @return {Promise}; resolves to options {object} (for now)
* @return {Promise}; resolves to [dataObj, Handlebars] for now
*/
function drizzle (options) {
const opts = parseOptions(options);

// const buildData = new Object();
// const readLayouts = utils.readFilesKeyed(opts.templates.layouts)
// .then(fileData => buildData.layouts = fileData);
// const readDocs = utils.readFilesKeyed(opts.docs)
// .then(fileData => {
// for (var file in fileData) {
// fileData[file].name = utils.toTitleCase(file);
// fileData[file].content = 'todo'; // markdown file.content
// }
// return fileData;
// });
// const readData = utils.readFilesKeyed(opts.data).then(fileData => {
// for (var file in fileData) {
// fileData[file].contents = 'todo'; // yaml load contents
// }
// return fileData;
// });
return prepareTemplates(opts).then(handlebars => opts);
return Promise.all([prepareData(opts), prepareTemplates(opts)]);
}

export default drizzle;
20 changes: 18 additions & 2 deletions src/options.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import Handlebars from 'handlebars';
import yaml from 'js-yaml';
import { merge } from './utils';

const defaults = {
data: {
src: ['src/data/**/*.yaml'],
parseFn: (contents, path) => yaml.safeLoad(contents)
},
templates: {
handlebars: Handlebars,
helpers : {},
Expand Down Expand Up @@ -30,8 +35,8 @@ function mergeDefaults (options = {}) {
* @return {object} User options
*/
function translateOptions (options = {}) {
/* eslint-disable prefer-const */
const {
data,
handlebars,
helpers,
layouts,
Expand All @@ -48,8 +53,19 @@ function translateOptions (options = {}) {
partials
}
};
// @TODO: Is there are more concise way of handling this?
// If you use the pattern above, an object value for `data`
// will get improperly nested/trounced
if (data) {
if (typeof data === 'string') {
result.data = {
src: data
};
} else {
result.data = data;
}
}
return result;
/* eslint-enable prefer-const */
}

const parseOptions = options => mergeDefaults(translateOptions(options));
Expand Down
103 changes: 80 additions & 23 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,45 @@ import {readFile as readFileCB} from 'fs';
var readFile = Promise.promisify(readFileCB);

/* Helper functions */
const basename = filepath => path.basename(filepath, path.extname(filepath));
const dirname = filepath => path.normalize(path.dirname(filepath));
const parentDirname = filepath => dirname(filepath).split(path.sep).pop();
const removeNumbers = str => str.replace(/^[0-9|\.\-]+/, '');
const getFiles = glob => globby(glob, {nodir: true });

/**
* Return extension-less basename of filepath
* @param {String} filepath
* @example basename('foo/bar/baz.txt'); // -> 'baz'
*/
function basename (filepath) {
return path.basename(filepath, path.extname(filepath));
}

/**
* Return normalized (no '..', '.') full dirname of filepath
* @param {String} filepath
* @example dirname('../ding/foo.txt'); // -> '/Users/shiela/ding/'
*/
function dirname (filepath) {
return path.normalize(path.dirname(filepath));
}

/**
* Return the name of this files immediate parent directory
* @param {String} filepath
* @example basename('foo/bar/baz.txt'); // -> 'bar'
*/
function parentDirname (filepath) {
return dirname(filepath).split(path.sep).pop();
}


function removeLeadingNumbers (str) {
return str.replace(/^[0-9|\.\-]+/, '');
}
/**
* @param {glob} glob
* @return {Promise} resolving to {Array} of files matching glob
*/
function getFiles (glob) {
return globby(glob, { nodir: true });
}

/**
* Utility function to test if a value COULD be a glob. A single string or
Expand All @@ -28,33 +62,56 @@ function isGlob (candidate) {
}

/**
* Take a glob; read the files. Return a Promise that ultimately resolves
* to an Array of objects:
* [{ path: original filepath,
* contents: utf-8 file contents}...]
* Take a glob; read the files, optionally running a `contentFn` over
* the contents of the file.
*
* @param {glob} glob of files to read
* @param {Object} Options:
* - {Function} contentFn(content, path): optional function to run over content
* in files; defaults to a no-op
* - {String} encoding
*
* @return {Promise} resolving to Array of Objects:
* - {String} path
* - {String || Mixed} contents: contents of file after contentFn
*/
function readFiles (glob) {
function readFiles (glob, {
contentFn = (content, path) => content,
encoding = 'utf-8'
} = {}) {
return getFiles(glob).then(paths => {
var fileReadPromises = paths.map(path => {
return readFile(path, 'utf-8')
.then(contents => ({ path, contents }));
});
return Promise.all(fileReadPromises);
return Promise.all(paths.map(path => {
return readFile(path, encoding)
.then(contents => {
contents = contentFn(contents, path);
return { path, contents };
});
}));
});
}

/**
* Read the files from a glob, but then instead of resolving the
* Promise with an Array of objects (@see readFiles), resolve with a
* single object; each file's contents is keyed by its filename run
* through keyname().
* through optional `keyFn(filePath, options)`` (default: keyname).
* Will pass other options on to readFiles and keyFn
*
* @param {glob}
* @param {Object} options (all optional):
* - keyFn
* - contentFn
* - stripNumbers
* @return {Promise} resolving to {Object} of keyed file contents
*/
function readFilesKeyed (glob, preserveNumbers = false) {
return readFiles(glob).then(allFileData => {
function readFilesKeyed (glob, options = {}) {
const {
keyFn = (path, options) => keyname(path, options)
} = options;
return readFiles(glob, options).then(allFileData => {
const keyedFileData = new Object();
for (var aFile of allFileData) {
keyedFileData[keyname(aFile.path, preserveNumbers)] = aFile.contents;
keyedFileData[keyFn(aFile.path, options)] = aFile.contents;
}
return keyedFileData;
});
Expand All @@ -65,15 +122,15 @@ function readFilesKeyed (glob, preserveNumbers = false) {
* partials, etc, based on a filepath:
* - replace whitespace characters with `-`
* - use only the basename, no extension
* - unless preserveNumbers, remove numbers from the string as well
* - unless stripNumbers option false, remove numbers from the string as well
*
* @param {String} str filepath
* @param {Boolean} preserveNumbers
* @param {Object} options
* @return {String}
*/
function keyname (str, preserveNumbers = false) {
function keyname (str, { stripNumbers = true } = {}) {
const name = basename(str).replace(/\s/g, '-');
return (preserveNumbers) ? name : removeNumbers(name);
return (stripNumbers) ? removeLeadingNumbers(name) : name;
}

/**
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/data/05-another-data.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ding:
- dong
- dell
- 'cat is in the well'
- 5
forestry:
fob: 'key'
bork: 'bing'
4 changes: 4 additions & 0 deletions test/fixtures/data/data-as-json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"foo" : "bar",
"fortunately": 5
}
4 changes: 4 additions & 0 deletions test/fixtures/data/sample-data.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
foo:
- bar
- baz
elfin: 'small things'
26 changes: 15 additions & 11 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
var chai = require('chai');
var expect = chai.expect;
var builder = require('../dist/');
var path = require('path');

const options = {
templates: {
partials: `${__dirname}/fixtures/partials/*`,
helpers: `${__dirname}/fixtures/helpers/*.js`
}
};

describe ('drizzle builder integration', () => {
it ('should return opts used for building', () => {
builder(options).then(opts => {
expect(opts).to.be.an.object;
expect(opts.templates).to.be.an.object;
expect(opts.templates.handlebars).to.be.an.object;
const options = {
data: {
src: path.join(__dirname, 'fixtures/data/*.yaml')
},
templates: {
helpers: path.join(__dirname, 'fixtures/helpers/**/*.js'),
partials: path.join(__dirname, 'fixtures/partials/*.hbs')
}
};
it ('should return data and context', done => {
builder(options).then(drizzleData => {
expect(drizzleData[0]).to.contain.keys('another-data', 'sample-data');
expect(drizzleData[0]['another-data']).to.be.an('object');
done();
});
});
});
8 changes: 7 additions & 1 deletion test/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ describe ('drizzle-builder', () => {
});
it ('should translate template options', () => {
var opts = parseOptions({
data: 'foo/bar/baz.yml',
layoutIncludes: 'a path',
views: 'a path to views'
});
expect(opts).to.be.an('object');
expect(opts.data).to.be.an('object');
expect(opts.data.src).to.be.a('string');
expect(opts.data.src).to.equal('foo/bar/baz.yml');
expect(opts.views).not.to.be;
expect(opts.layoutIncludes).not.to.be;
expect(opts.templates).to.be.an('object');
Expand All @@ -41,11 +45,13 @@ describe ('drizzle-builder', () => {

it ('should provide default templating options', () => {
var opts = parseOptions();
expect(opts).to.contain.keys('templates');
expect(opts).to.contain.keys('templates', 'data');
expect(opts.templates).to.be.an('object');
expect(opts.templates).to.have.keys('handlebars', 'helpers',
'layouts', 'pages', 'partials');
expect(opts.templates.handlebars).to.be.an('object');
expect(opts.data).to.have.keys('src', 'parseFn');
expect(opts.data.parseFn).to.be.a('function');
});
});
});
Expand Down
Loading

0 comments on commit 5c72725

Please sign in to comment.