Skip to content

Commit

Permalink
feat(api): overhauled API with new opt handling concept
Browse files Browse the repository at this point in the history
BREAKING CHANGE: this is a completely different approach than previously
used by this library. See the readme for the new API and an explanation.
  • Loading branch information
zkat committed Mar 16, 2018
1 parent 4bd8c13 commit e6cc929
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 128 deletions.
51 changes: 26 additions & 25 deletions README.md
Expand Up @@ -6,7 +6,7 @@

## Install

`$ npm install --save figgy-pudding`
`$ npm install figgy-pudding`

## Table of Contents

Expand All @@ -19,9 +19,9 @@
### Example

```javascript
const figgyPudding = require('figgyPudding')
const puddin = require('figgyPudding')

const RequestOpts = figgyPudding({
const RequestOpts = puddin({
follow: {
default: true
},
Expand All @@ -33,7 +33,7 @@ const RequestOpts = figgyPudding({
}
})

const MyAppOpts = figgyPudding({
const MyAppOpts = puddin({
log: {
default: require('npmlog')
},
Expand All @@ -44,8 +44,8 @@ const MyAppOpts = figgyPudding({

function start (opts) {
opts = MyAppOpts(opts)
initCache(opts.cache)
opts.streaming // => undefined
initCache(opts.get('cache'))
opts.get('streaming') // => undefined
reqStuff('https://npm.im/figgy-pudding', opts)
}

Expand All @@ -57,46 +57,46 @@ function reqStuff (uri, opts) {

### Features

* Top-down options
* Hide options from layer that didn't ask for it
* Shared multi-layer options
* Immutable by default

### Guide

#### Introduction

### API

#### <a name="figgy-pudding"></a> `> figgyPudding({ key: { default: val }})`
#### <a name="figgy-pudding"></a> `> figgyPudding({ key: { default: val } | String }, [opts])`

Defines an Options object that can be used to collect only the needed options.
Defines an Options constructor that can be used to collect only the needed
options.

An optional `default` property for specs can be used to specify default values
if nothing was passed in.

If the value for a spec is a string, it will be treated as an alias to that
other key.

##### Example

```javascript
const MyAppOpts = figgyPudding({
lg: 'log',
log: {
default: require('npmlog')
default: () => require('npmlog')
},
cache: {}
})
```

#### <a name="opts"></a> `> Opts(options, metaOpts)`
#### <a name="opts"></a> `> Opts(...providers)`

Instantiates an options object defined by `figgyPudding()`. The returned object
will be immutable and non-extensible, and will only include properties defined
in the Opts spec.
Instantiates an options object defined by `figgyPudding()`, which uses
`providers`, in order, to find requested properties.

The returned opts object can be made mutable by making `metaOpts.mutable` true.
Each provider can be either a plain object, a `Map`-like object (that is, one
with a `.get()` method) or another figgyPudding `Opts` object.

`options` can be either a plain object or another `Opts` object. In the latter
case, the original root options (a plain object) will be used as a fallback
for properties missing from `options`
When nesting `Opts` objects, their properties will not become available to the
new object, but any further nested `Opts` that reference that property _will_ be
able to read from their grandparent, as long as they define that key. Default
values for nested `Opts` parents will be used, if found.

##### Example

Expand All @@ -110,11 +110,12 @@ const opts = ReqOpts({
log: require('npmlog')
})

opts.follow // => true
opts.log // => false (not defined by ReqOpts)
opts.get('follow') // => true
opts.get('log') // => Error: ReqOpts does not define `log`

const MoreOpts = figgyPudding({
log: {}
})
MoreOpts(opts).log // => npmlog object (passed in from original plain obj)
MoreOpts(opts).get('follow') // => Error: MoreOpts does not define `follow`
```
87 changes: 46 additions & 41 deletions index.js
@@ -1,51 +1,56 @@
'use strict'

module.exports = figgyPudding
function figgyPudding (spec) {
var optSpec = new OptSpec(spec)
function factory (opts, metaOpts) {
return new (optSpec.Opts)(opts, metaOpts)
class FiggyPudding {
constructor (specs, opts, providers) {
this.specs = specs || {}
this.opts = opts || (() => false)
this.providers = providers
this.isFiggyPudding = true
}
get (key) {
return pudGet(this, key, true)
}
factory.derive = deriveOpts
return factory
}

function OptSpec (spec) {
this.isOptSpec = true
this.spec = spec
this.derivatives = []
this.Opts = buildOpts(this)
}

function buildOpts (spec) {
return function Opts (opts, pudOpts) {
opts = opts || {}
pudOpts = pudOpts || {}
var pud = this
Object.defineProperty(pud, '__root__', {
enumerable: false,
value: opts.__root__ || opts
})
Object.keys(spec.spec).forEach(function (key) {
pud[key] = processKey(key, spec.spec, opts)
})
Object.preventExtensions(pud)
if (!pudOpts.mutable) {
Object.freeze(pud)
function pudGet (pud, key, validate) {
let spec = pud.specs[key]
if (typeof spec === 'string') {
key = spec
spec = pud.specs[key]
}
if (validate && !spec && (!pud.opts.other || !pud.opts.other(key))) {
throw new Error(`invalid config key requested: ${key}`)
} else {
if (!spec) { spec = {} }
let ret
for (let p of pud.providers) {
if (p.isFiggyPudding) {
ret = pudGet(p, key, false)
} else if (typeof p.get === 'function') {
ret = p.get(key)
} else {
ret = p[key]
}
if (ret !== undefined) {
break
}
}
if (ret === undefined && spec.default !== undefined) {
if (typeof spec.default === 'function') {
return spec.default()
} else {
return spec.default
}
} else {
return ret
}
}
}

function processKey (key, spec, opts, root) {
var val = opts[key] !== undefined
? opts[key]
: opts.__root__ && opts.__root__[key] !== undefined
? opts.__root__[key]
: spec[key].default
return val
}

function deriveOpts (pudding) {
this.derivatives.push(pudding)
return this
module.exports = figgyPudding
function figgyPudding (specs, opts) {
function factory () {
return new FiggyPudding(specs, opts, [].slice.call(arguments))
}
return factory
}

0 comments on commit e6cc929

Please sign in to comment.