Skip to content

Commit

Permalink
Merge pull request #99 from scottnath/hotfix/rm-node-config
Browse files Browse the repository at this point in the history
Hotfix/rm node config
  • Loading branch information
Snugug authored Sep 1, 2016
2 parents 4ca61f1 + c60ace1 commit adffae1
Show file tree
Hide file tree
Showing 18 changed files with 326 additions and 92 deletions.
27 changes: 19 additions & 8 deletions lib/content-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,51 @@ const raw = loader;
* Merge content type object with input plugin data
*
* @param {array} content content types array
* @param {object} config - configuration object
*
* @returns {array} content types array that has been merged with input plugins and user settings
*/
const merged = (content) => {
const merged = (content, config) => {
let mergedTypes = _.cloneDeep(content);

if (content) {
return merge(mergedTypes);
}

return raw().then(types => {
return raw(config).then(types => {
mergedTypes = _.cloneDeep(types);

return merge(mergedTypes);
return merge(mergedTypes, config);
});
};

const onlyCT = (type, config, loadedTypes) => {
const userConfig = config || {};
/**
* Returns only one content type, merged with input plugins and values
*
* @param {string} type - id of a content type
* @param {object} overrides - contains values for inputs
* @param {array} loadedTypes - array of merged content type objects
* @param {object} config - configuration object
*
* @returns {promise} resolves with promise from `only` function
*/
const onlyCT = (type, overrides, loadedTypes, config) => {
const overs = overrides || {};

let onlyTypes = _.cloneDeep(loadedTypes);

if (loadedTypes) {
const ct = findCT(type, onlyTypes);

return only(ct, userConfig);
return only(ct, overs, config);
}

return merged().then(types => {
return merged(null, config).then(types => {
onlyTypes = _.cloneDeep(types);

const ct = findCT(type, onlyTypes);

return only(ct, userConfig);
return only(ct, overs, config);
});
};

Expand Down
58 changes: 32 additions & 26 deletions lib/content-types/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,34 @@
const dir = require('node-dir');
const yaml = require('js-yaml');
const slugify = require('lodash/kebabCase');
const globalConfig = require('config');
const path = require('path');
const utils = require('../utils');


/**
* Gets all Content Types
*
* @param {object} config - configuration object
*
* @returns {Promise} - An {Array} containing {Object} for each content type, including `name` {string}, `id` name {string}, and full config {object} loaded from YAML config file
*/
const load = () => {
const load = (config) => {
const configuration = config || {};
const types = [];
const ids = [];
let contentPath = path.join(process.cwd(), 'content-types');

if (globalConfig.hasOwnProperty('content')) {
if (globalConfig.content.hasOwnProperty('directory')) {
contentPath = globalConfig.content.directory;
return new Promise((resolve, reject) => {
if (typeof configuration !== 'object') {
reject(new Error('Configuration parameter must be an object'));
}

if (configuration.hasOwnProperty('content')) {
if (configuration.content.hasOwnProperty('directory')) {
contentPath = configuration.content.directory;
}
}
}

return new Promise((resolve, reject) => {
dir.readFiles(contentPath, {
match: /.yml$/,
exclude: /^\./,
Expand All @@ -33,50 +39,50 @@ const load = () => {
reject(err);
}

const config = yaml.safeLoad(content);
const type = yaml.safeLoad(content);

if (!config.hasOwnProperty('name')) {
if (!type.hasOwnProperty('name')) {
reject(new Error('Content types require a name'));
}

if (!config.hasOwnProperty('id')) {
if (!type.hasOwnProperty('id')) {
reject(new Error('Content types require an id'));
}

if (config.id !== slugify(config.id)) {
reject(new Error(`${config.id} needs to be written in kebab case (e.g. ${slugify(config.id)}`));
if (type.id !== slugify(type.id)) {
reject(new Error(`${type.id} needs to be written in kebab case (e.g. ${slugify(type.id)}`));
}

if (ids.indexOf(config.id) === -1) {
ids.push(config.id);
types.push(config);
if (ids.indexOf(type.id) === -1) {
ids.push(type.id);
types.push(type);
}
else {
reject(new Error(`Content type ${config.id} is duplicated!`));
reject(new Error(`Content type ${type.id} is duplicated!`));
}

if (!config.hasOwnProperty('identifier')) {
reject(new Error(`Identifier missing in content type '${config.name}'.`));
if (!type.hasOwnProperty('identifier')) {
reject(new Error(`Identifier missing in content type '${type.name}'.`));
}

if (config.hasOwnProperty('identifier')) {
if (typeof config.identifier !== 'string') {
reject(new Error(`Identifier in content type '${config.name}' must be a string`));
if (type.hasOwnProperty('identifier')) {
if (typeof type.identifier !== 'string') {
reject(new Error(`Identifier in content type '${type.name}' must be a string`));
}
}

// find attribute which is the identifier
const attr = config.attributes.find(a => {
return a.id === config.identifier;
const attr = type.attributes.find(a => {
return a.id === type.identifier;
});

if (!attr) {
reject(new Error(`Identifier '${config.identifier}' is not an attribute in content type '${config.name}'.`));
if (typeof attr === undefined) {
reject(new Error(`Identifier '${type.identifier}' is not an attribute in content type '${type.name}'.`));
}

// check attribute only has one input
if (attr.inputs && Array.isArray(Object.keys(attr.inputs)) && Object.keys(attr.inputs).length > 1) {
reject(new Error(`Attribute '${config.identifier}' in content type '${config.name}' cannot be the identifier. Only attributes with one input can be the identifier.`));
reject(new Error(`Attribute '${type.identifier}' in content type '${type.name}' cannot be the identifier. Only attributes with one input can be the identifier.`));
}

next();
Expand Down
25 changes: 10 additions & 15 deletions lib/content-types/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,6 @@ const merge = require('deepmerge');
const uuid = require('uuid');
const _ = require('lodash');
const slugify = require('lodash/kebabCase');
const globalConfig = require('config');

const configPlugins = {};

/**
* Retrieve Input Plugins
*
* {Object} - [Plugabilly](https://github.com/Snugug/plugabilly) object
*/
configPlugins.search = _.get(globalConfig, 'content.plugins.directory', []);
if (typeof configPlugins.search === 'string') {
configPlugins.search = [configPlugins.search];
}
const plugins = plugabilly(_.cloneDeep(configPlugins)).name().containsSync('input-plugin-');

/*
* Determine required level
Expand All @@ -42,10 +28,19 @@ const requiredLevel = (level) => {
* Combine content types configurations with input plugins
*
* @param {array} types - content types configurations
* @param {object} config - configuration object
*
* @returns {promise} - combined content type with input plugin configs
*/
const squish = (types) => {
const squish = (types, config) => {
const configPlugins = {};
configPlugins.search = _.get(config, 'content.plugins.directory', []);
if (typeof configPlugins.search === 'string') {
configPlugins.search = [configPlugins.search];
}

const plugins = plugabilly(_.cloneDeep(configPlugins)).name().containsSync('input-plugin-');

return new Promise((resolve, reject) => {
if (!Array.isArray(types)) {
reject(new Error('Content types must be an array'));
Expand Down
28 changes: 18 additions & 10 deletions lib/content-types/only.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
const merge = require('deepmerge');
const utils = require('../utils');

const only = (type, configuration) => {
/**
* Retrieves one content type's config object, merged with input plugins and a form's values
*
* @param {object} type merged content type object
* @param {object} overrides values for inputs
*
* @returns {object} content type object merged with input plugins and overrides
*/
const only = (type, overrides) => {
return new Promise((resolve) => {
const mergedType = type;
const config = configuration;
const configured = Object.keys(config);
const overs = overrides;
const configured = Object.keys(overs);
const attrs = mergedType.attributes.map(attribute => {
const attr = attribute;
let inputs;
Expand All @@ -19,19 +27,19 @@ const only = (type, configuration) => {
inputs = Object.keys(attr.inputs);
}

// If there isn't a configuration for this input, move along
// If there isn't a value for this input, move along
if (configured.indexOf(attr.id) === -1) {
return attr;
}

// check if repeatable but not an array
if (attr.hasOwnProperty('repeatable') && typeof attr.repeatable === 'object' && !Array.isArray(config[attr.id])) {
config[attr.id] = [config[attr.id]];
if (attr.hasOwnProperty('repeatable') && typeof attr.repeatable === 'object' && !Array.isArray(overs[attr.id])) {
overs[attr.id] = [overs[attr.id]];
}

// Flattens multiple instances and updates ids and names
if (Array.isArray(config[attr.id])) {
attr.inputs = config[attr.id].map((data, index) => {
if (Array.isArray(overs[attr.id])) {
attr.inputs = overs[attr.id].map((data, index) => {
const instance = merge(attr.inputs[0], data);
inputs.forEach(input => {
instance[input].validation = attribute.inputs[0][input].validation;
Expand All @@ -44,8 +52,8 @@ const only = (type, configuration) => {
});
}
else {
// Merge configuration for the inputs and the configuration
attr.inputs = merge(attr.inputs, config[attr.id]);
// Merge configuration for the inputs and the overs
attr.inputs = merge(attr.inputs, overs[attr.id]);

// Override the validation, type, and ID for all inputs that matter
inputs.forEach(input => {
Expand Down
14 changes: 11 additions & 3 deletions lib/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ const html = require('./form/html');
const scripts = require('./form/scripts');
const validate = require('./form/validate');

// const css = require('./form/css');
const form = (type, errors) => {
/**
* Form configuration creator!
*
* @param {InputType} type - Input type being rendered
* @param {FormInputValues} errors - Errors associated with form inputs
* @param {object} config - application configuration
*
* @returns {promise} - promise from scripts
*/
const form = (type, errors, config) => {
const rendered = {};

return html(type, errors).then(result => {
rendered.html = result;

return scripts(type);
return scripts(type, config);
}).then(result => {
rendered.scripts = result;
}).then(() => {
Expand Down
15 changes: 10 additions & 5 deletions lib/form/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const utils = require('../utils');
const globalConfig = require('config');
const formJS = fs.readdirSync(path.join(__dirname, 'js')).map(script => {
return fs.readFileSync(path.join(__dirname, 'js', script));
}).join('\n\n');

// const util = require('../util');

const rendered = (type) => {
/**
* Form configuration creator!
*
* @param {InputType} type - Input type being rendered
* @param {object} config - application configuration
*
* @returns {string} - form script code
*/
const rendered = (type, config) => {
return new Promise((res, rej) => {
const b = browserify();
const s = new stream.Readable();
Expand All @@ -25,7 +30,7 @@ const rendered = (type) => {
let plugins = type.attributes.map(plugin => {
return plugin.type;
});
const existing = utils.getPlugins(globalConfig);
const existing = utils.getPlugins(config);

plugins = plugins.filter((item, pos, self) => {
return self.indexOf(item) === pos;
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"license": "Apache-2.0",
"dependencies": {
"browserify": "^13.0.1",
"config": "^1.20.4",
"deepmerge": "^0.2.10",
"js-yaml": "^3.5.3",
"lodash": "^4.14.1",
Expand Down
28 changes: 25 additions & 3 deletions tests/content-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import test from 'ava';
import cloneDeep from 'lodash/cloneDeep';
import types from '../lib/content-types';
import only from '../lib/content-types/only.js';
import barInput from './fixtures/objects//bar-input.js';
import barExpected from './fixtures/objects//bar-expected.js';

import config from './fixtures/config/default';
import barInput from './fixtures/objects/bar-input.js';
import barExpected from './fixtures/objects/bar-expected.js';

const correctCT = [{
name: 'Foo',
Expand Down Expand Up @@ -78,9 +80,29 @@ test('Content Types', t => {
});
});

test('merged', t => {
test('returns content types from default directory when no parameters', t => {
return types()
.then(result => {
t.true(Array.isArray(result), 'Should return an array');
t.is(result.length, 1, 'Should only have one content type');
t.is(result[0].name, 'Content Type FOO', 'Get first content type name');
t.is(result[0].description, 'A non-traditionally placed fixture to test the default content-types directory', 'Get first content type desc');
t.is(result[0].id, 'default-config-foo', 'Get first content type id');
});
});

test('rejects when config is not an object', t => {
return types('', 'config')
.catch(err => {
t.is(err.message, 'Configuration parameter must be an object', 'Should return an error with non-object config');
});
});

test('returns all types from configured content type directory', t => {
return types('', config)
.then(result => {
t.true(Array.isArray(result), 'Should return an array');
t.is(result.length, 3, 'Should only have one content type');
t.is(result[0].name, 'Content Type BAR', 'Get first content type name');
t.is(result[0].description, 'Bar Baz Foo', 'Get first content type desc');
t.is(result[0].id, 'bar', 'Get first content type id');
Expand Down
8 changes: 8 additions & 0 deletions tests/content-types/default-config-foo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: Content Type FOO
description: A non-traditionally placed fixture to test the default content-types directory
id: default-config-foo
identifier: title
attributes:
- type: text
name: Title
id: title
Loading

0 comments on commit adffae1

Please sign in to comment.