Skip to content

Commit

Permalink
Merge pull request #3 from tyler-johnson/2.0
Browse files Browse the repository at this point in the history
2.0
  • Loading branch information
tyler-johnson authored Mar 24, 2017
2 parents 7048ba8 + e8d1b5e commit 00d3f6d
Show file tree
Hide file tree
Showing 15 changed files with 626 additions and 325 deletions.
24 changes: 0 additions & 24 deletions Makefile

This file was deleted.

51 changes: 38 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,62 @@ npm i form-blueprint --save
## Usage

```js
import * as bpTools from "form-blueprint";
import createBlueprint, { Blueprint, Schema, Field, defaultSchema } from "form-blueprint";
```

### validate()
### createBlueprint()

```text
bpTools.validate( blueprint )
createBlueprint( blueprint [, schema ] )
```

Validates a blueprint object. An error is thrown if the value is invalid.
Creates a new blueprint object with a schema. If a schema is not provided, the builtin default schema is used.

### merge()

### Blueprint#root

```text
blueprint.root
```

The blueprint's root field object.

### Blueprint#schema

```text
blueprint.schema
```

The blueprint's schema object.

### Blueprint#getField()

```text
blueprint.getField( key )
```

Get a field in a blueprint by key. The key can be complex to get deep values (eg. `a[0].b.c`).

### Blueprint#transform()

```text
bpTools.merge( blueprint1, blueprint2 [, ... blueprintN ] )
blueprint.transform( [ value ] )
```

Merges blueprint several objects together into a new blueprint. The last blueprint wins when there are section and option conflicts.
Transforms a value according to the blueprint and schema rules. Value does not need to be provided, in which case default values are returned.

### defaults()
### Blueprint#normalize()

```text
bpTools.defaults( blueprint )
blueprint.normalize()
```

Returns an object of default blueprint values.
Normlaize a blueprint using schema rules. This generally doesn't need to be called as this run when a blueprint is created.

### applyDefaults()
### Blueprint#join()

```text
bpTools.applyDefaults( blueprint, object )
blueprint.join( blueprint1 [, blueprint2 [, ... ] ] )
```

Applies a blueprint's default values to an object. The default values are only set if the object's value is undefined or an empty string.
Joins one or more blueprints together into a single blueprint. The blueprint that join is called on is considered the master blueprint. The master's schema is used to join and upon conflicts, the master's copy will always win.
24 changes: 16 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,36 @@
"main": "index.js",
"scripts": {
"lint": "eslint src/ test/",
"build": "make clean && make",
"test": "make test",
"clean": "shx rm -rf index.js test.js",
"build": "npm-run-all clean build-lib",
"build-lib": "rollup src/index.js -c > index.js",
"build-test": "rollup test/index.js -c > test.js",
"test": "npm-run-all build-test test-node test-browser",
"test-node": "node test.js",
"test-browser": "browserify test.js --debug | tape-run",
"prepublish": "npm run build",
"autorelease": "autorelease pre && npm publish && autorelease post"
},
"dependencies": {
"lodash": "^4.8.2"
"immutable": "^3.8.1",
"lodash.topath": "^4.5.2"
},
"devDependencies": {
"autorelease": "^1.6.0",
"autorelease-github": "^1.1.1",
"autorelease-travis": "^1.2.1",
"babel-eslint": "^6.0.2",
"babel-eslint": "^7.2.1",
"babel-plugin-transform-object-rest-spread": "^6.6.5",
"babel-preset-es2015-rollup": "^1.1.1",
"browserify": "^13.0.0",
"babel-preset-es2015-rollup": "^3.0.0",
"browserify": "^14.1.0",
"eslint": "^3.0.0",
"rollup": "^0.34.0",
"npm-run-all": "^4.0.2",
"rollup": "^0.41.6",
"rollup-plugin-babel": "^2.4.0",
"rollup-plugin-json": "^2.0.0",
"shx": "^0.2.2",
"tape": "^4.5.1",
"tape-run": "^2.1.3",
"tape-run": "^3.0.0",
"uglify-js": "^2.6.2"
},
"keywords": [],
Expand Down
57 changes: 57 additions & 0 deletions src/blueprint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Record } from "immutable";
import toPath from "lodash.topath";
import Schema from "./schema";
import Field from "./field";

const DEFAULTS = {
root: null,
schema: null
};

export default class Blueprint extends Record(DEFAULTS) {
static create(props = {}) {
if (Blueprint.isBlueprint(props)) {
return props;
}

props.root = Field.create(props.root);
props.schema = Schema.create(props.schema);

return new Blueprint(props).normalize();
}

static isBlueprint(b) {
return b instanceof Blueprint;
}

static kind() {
return "blueprint";
}

getField(key) {
const path = toPath(key);
let field = this.root;

while (path.length && field) {
field = field.getChildField(path.shift());
}

return field;
}

transform(value) {
return this.schema.transform(value, this.root);
}

normalize() {
return this.merge({
root: this.schema.normalize(this.root)
});
}

join(...blueprints) {
const fields = [this].concat(blueprints).map(b => b.root);
const root = this.schema.join(...fields);
return this.merge({ root });
}
}
58 changes: 58 additions & 0 deletions src/field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Record, Map, List } from "immutable";

const DEFAULTS = {
key: null,
type: null,
children: null,
props: null
};

export default class Field extends Record(DEFAULTS) {
static create(field = {}) {
if (Field.isField(field)) {
return field;
}

if (typeof field !== "object" || field == null) {
throw new Error("Expecting object for field.");
}

let {
key=null,
type=null,
children,
...props
} = field;

children = Field.createList(children);
props = Map.isMap(props) ? props : Map(props);

return new Field({ key, type, props, children });
}

static createList(fields) {
if (List.isList(fields)) return fields;

if (Array.isArray(fields)) {
fields = fields.map(Field.create);
} else if (typeof fields === "object" && fields != null) {
fields = Object.keys(fields).map(key => {
return Field.create({ ...fields[key], key });
});
}

return List(fields);
}

static isField(b) {
return b instanceof Field;
}

static kind() {
return "field";
}

getChildField(key) {
return this.children.find(o => o.key === key);
}
}
108 changes: 15 additions & 93 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,94 +1,16 @@
import {assign,omit,has,clone,reduce,assignWith} from "lodash";

// throw an error if blueprint is not valid
export function validate(bp) {
if (typeof bp !== "object" || bp == null) {
throw new Error("Expecting object for blueprint.");
}

for (let section_key in bp) {
if (!has(bp, section_key)) continue;
let section = bp[section_key];
if (!validSection(section)) {
throw new Error(`Invalid blueprint section '${section_key}'.`);
}

let options = section.options;
for (let option_key in options) {
if (!has(options, option_key)) continue;
if (!validOption(options[option_key])) {
throw new Error(`Invalid blueprint option '${section_key}.${option_key}'.`);
}
}
}
}

// returns boolean for if section object is valid
export function validSection(section) {
return (typeof section === "object" && section != null) &&
(typeof section.options === "object" && section.options != null);
}

// returns boolean for if section option object is valid
export function validOption(option) {
return (typeof option === "object" && option != null) &&
(typeof option.type === "string" && option.type);
}

// merge several blueprints together
export function merge(...args) {
return args.reduce((m, obj) => {
if (!obj) return m;

for (let section_key in obj) {
if (!has(obj, section_key)) continue;
let section = obj[section_key];
if (!validSection(section)) continue;

if (!has(m, section_key)) m[section_key] = { options: {} };
assign(m[section_key], omit(section, "options"));
let options = section.options;

for (let option_key in options) {
if (!has(options, option_key)) continue;
let option = options[option_key];
if (!validOption(option)) continue;

m[section_key].options[option_key] = clone(option);
}
}

return m;
},{});
}

// extract default values from a blueprint
export function defaults(bp={}) {
return reduce(bp, (m, s, k) => {
m[k] = reduce(s.options, (m, o, k) => {
m[k] = o.default;
return m;
}, {});
return m;
}, {});
}

// apply blueprint defaults onto an object
export function applyDefaults(bp, obj) {
let defaultOpts = defaults(bp);

for (let k in defaultOpts) {
if (!has(defaultOpts, k) || (obj[k] && !has(obj, k))) continue;

if (obj[k] == null) obj[k] = {};
if (typeof obj[k] !== "object") continue;

assignWith(obj[k], defaultOpts[k], (cur, def) => {
return (typeof def !== "undefined") &&
(typeof cur === "undefined" || cur === "") ?
def : cur;
});
}

return obj;
import Blueprint from "./blueprint";
import Schema, {defaultSchema} from "./schema";
import Field from "./field";
import * as rules from "./rules/index";

export {
Blueprint,
Schema,
Field,
defaultSchema,
rules
};

export default function(field, schema=defaultSchema) {
return Blueprint.create({ root: field, schema });
}
18 changes: 18 additions & 0 deletions src/rules/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// rule for defaults

function match(field) {
return field.props.has("default");
}

function transform(value, field) {
const def = field.props.get("default");

return (typeof def !== "undefined") &&
(typeof value === "undefined" || value === "") ?
def : value;
}

export default {
match,
transform
};
Loading

0 comments on commit 00d3f6d

Please sign in to comment.