├── .gitignore ├── .github └── FUNDING.yml ├── .travis.yml ├── appveyor.yml ├── LICENSE.md ├── test ├── aliases.js ├── serialization.js ├── iteration.js └── index.js ├── package.json ├── CHANGELOG.md ├── index.js ├── README.md └── CODE_OF_CONDUCT.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.nyc_output 3 | /test/cache 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [zkat] 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "10" 5 | - "9" 6 | - "8" 7 | - "6" 8 | cache: 9 | directories: 10 | - /home/travis/.npm 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: "10" 4 | - nodejs_version: "9" 5 | - nodejs_version: "8" 6 | - nodejs_version: "6" 7 | 8 | platform: 9 | - x64 10 | 11 | install: 12 | - ps: Install-Product node $env:nodejs_version $env:platform 13 | - npm config set spin false 14 | - npm install 15 | 16 | test_script: 17 | - npm test 18 | 19 | matrix: 20 | fast_finish: true 21 | 22 | build: off 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) npm, Inc. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for 6 | any purpose with or without fee is hereby granted, provided that the 7 | above copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE COPYRIGHT HOLDER DISCLAIMS 10 | ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 | COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR 13 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 14 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 15 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE 16 | USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /test/aliases.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tap').test 4 | const puddin = require('../') 5 | 6 | test('basic aliases', t => { 7 | const testOpts = puddin({ 8 | a: {}, 9 | b: 'a', 10 | c: {}, 11 | d: 'b' 12 | }) 13 | const opts = testOpts({a: 1, c: 2}) 14 | t.equal(opts.get('a'), 1, 'base opt fetched normally') 15 | t.equal(opts.get('b'), 1, 'opt fetchable through alias') 16 | t.equal(opts.get('d'), 1, 'aliases chain') 17 | t.equal(opts.get('c'), 2, 'other opt unaffected') 18 | t.equal(testOpts({b: 3}).get('a'), 3, 'reverse alias works') 19 | t.throws(() => { 20 | puddin({ 21 | b: 'a' 22 | })({}) 23 | }, /invalid key: a -> b/) 24 | t.done() 25 | }) 26 | 27 | test('transitive nested aliases', t => { 28 | const testOpts = puddin({ 29 | a: 'b', 30 | b: {} 31 | }) 32 | const nestedOpts = puddin({ 33 | c: 'b', 34 | b: {} 35 | }) 36 | const opts = testOpts({c: 2}).concat(nestedOpts({c: 1})) 37 | t.deepEqual(opts.toJSON(), { 38 | a: 1, 39 | b: 1 40 | }, 'nested transitive aliases work') 41 | t.done() 42 | }) 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figgy-pudding", 3 | "version": "3.5.1", 4 | "description": "Delicious, festive, cascading config/opts definitions", 5 | "main": "index.js", 6 | "files": [ 7 | "*.js", 8 | "lib" 9 | ], 10 | "scripts": { 11 | "prerelease": "npm t", 12 | "postrelease": "npm publish && git push --follow-tags", 13 | "pretest": "standard", 14 | "release": "standard-version -s", 15 | "test": "tap -J --100 --coverage test/*.js", 16 | "update-coc": "weallbehave -o . && git add CODE_OF_CONDUCT.md && git commit -m 'docs(coc): updated CODE_OF_CONDUCT.md'", 17 | "update-contrib": "weallcontribute -o . && git add CONTRIBUTING.md && git commit -m 'docs(contributing): updated CONTRIBUTING.md'" 18 | }, 19 | "repository": "https://github.com/zkat/figgy-pudding", 20 | "keywords": [ 21 | "config", 22 | "options", 23 | "yummy" 24 | ], 25 | "author": "Kat Marchán ", 26 | "license": "ISC", 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "standard": "^11.0.1", 30 | "standard-version": "^4.4.0", 31 | "tap": "^12.0.1", 32 | "weallbehave": "^1.2.0", 33 | "weallcontribute": "^1.0.8" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/serialization.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tap').test 4 | const puddin = require('../') 5 | const util = require('util') 6 | 7 | function entries (obj) { 8 | return Object.keys(obj).map(k => [k, obj[k]]) 9 | } 10 | 11 | test('basic toJSON', t => { 12 | const testOpts = puddin({ 13 | a: {} 14 | }) 15 | const opts = testOpts({a: 1, b: 2}) 16 | t.deepEqual(opts.toJSON(), { 17 | a: 1 18 | }, 'only declared properties included') 19 | t.equal( 20 | JSON.stringify(opts), 21 | JSON.stringify({a: 1}), 22 | 'works with JSON.stringify()' 23 | ) 24 | t.done() 25 | }) 26 | 27 | test('toJSON for puddings with `opts.other`', t => { 28 | const testOpts = puddin({ 29 | a: {} 30 | }, { 31 | other (key) { return /^special-/.test(key) } 32 | }) 33 | const opts = testOpts({ 34 | 'special-a': 3, 35 | a: 1, 36 | b: 2, 37 | 'special-b': 4, 38 | 'a-special': 5 39 | }) 40 | t.deepEqual(entries(opts.toJSON()), [ 41 | ['a', 1], 42 | ['special-a', 3], 43 | ['special-b', 4] 44 | ], 'serializes special opts.other keys') 45 | t.done() 46 | }) 47 | 48 | test('toJSON for nested puddings with opts.other', t => { 49 | const testOpts = puddin({ 50 | a: {} 51 | }, { 52 | other (key) { return /^special-/.test(key) } 53 | }) 54 | const nestedOpts = puddin({ 55 | b: {} 56 | }) 57 | const opts = testOpts({ 58 | 'special-b': 4 59 | }).concat({a: 3}, nestedOpts({ 60 | a: 1, 61 | 'a-special': 5 62 | }).concat(nestedOpts({ 63 | b: 2, 64 | 'special-a': 3 65 | }))) 66 | t.deepEqual(entries(opts.toJSON()), [ 67 | ['a', 1], 68 | ['special-a', 3], 69 | ['special-b', 4] 70 | ], 'expected order even with nested opts.others') 71 | t.done() 72 | }) 73 | 74 | test('util.inspect support', t => { 75 | const testOpts = puddin({ 76 | a: {} 77 | }) 78 | const opts = testOpts({ 79 | a: 1 80 | }) 81 | t.equal( 82 | util.inspect(opts, {color: false}), 83 | 'FiggyPudding { a: 1 }', 84 | 'has a custom util.inspect method' 85 | ) 86 | t.done() 87 | }) 88 | -------------------------------------------------------------------------------- /test/iteration.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tap').test 4 | const puddin = require('../') 5 | 6 | test('forEach', t => { 7 | const testOpts = puddin({ 8 | a: {}, 9 | b: {} 10 | }) 11 | const arr = [] 12 | let thisArg 13 | const expectedThis = {} 14 | const opts = testOpts({a: 1, b: 2, c: 3}) 15 | opts.forEach(function (...args) { 16 | thisArg = this 17 | arr.push(args) 18 | }, expectedThis) 19 | t.equal(thisArg, expectedThis, 'correct thisArg') 20 | t.deepEqual(arr, [ 21 | [1, 'a', opts], 22 | [2, 'b', opts] 23 | ], 'correct arguments, and only declared props, in declared order') 24 | t.done() 25 | }) 26 | 27 | test('entries', t => { 28 | const testOpts = puddin({ 29 | a: {}, 30 | b: {} 31 | }) 32 | const arr = [] 33 | const opts = testOpts({a: 1, b: 2, c: 3}) 34 | for (let [key, value] of opts.entries()) { 35 | arr.push([key, value]) 36 | } 37 | t.deepEqual(arr, [ 38 | ['a', 1], 39 | ['b', 2] 40 | ], 'correct arguments, and only declared props, in declared order') 41 | t.done() 42 | }) 43 | 44 | test('entries over nested puddings', t => { 45 | const testOpts = puddin({ 46 | a: {}, 47 | b: {} 48 | }) 49 | const nestedOpts = puddin({}) // actual values declared should not matter 50 | const arr = [] 51 | const opts = testOpts({a: 1}).concat( 52 | {b: 3}, 53 | nestedOpts({}).concat(nestedOpts({b: 2})) 54 | ) 55 | for (let [key, value] of opts.entries()) { 56 | arr.push([key, value]) 57 | } 58 | t.deepEqual(arr, [ 59 | ['a', 1], 60 | ['b', 2] 61 | ], 'reaches into nested puddings even if they don\'t declare a key') 62 | t.done() 63 | }) 64 | 65 | test('Symbol.iterator', t => { 66 | const testOpts = puddin({ 67 | a: {}, 68 | b: {} 69 | }) 70 | const arr = [] 71 | const opts = testOpts({a: 1, b: 2, c: 3}) 72 | for (let [key, value] of opts) { 73 | arr.push([key, value]) 74 | } 75 | t.deepEqual(arr, [ 76 | ['a', 1], 77 | ['b', 2] 78 | ], 'pudding itself is an iterator') 79 | t.done() 80 | }) 81 | 82 | test('keys', t => { 83 | const testOpts = puddin({ 84 | a: {}, 85 | b: {} 86 | }) 87 | const arr = [] 88 | const opts = testOpts({a: 1, b: 2, c: 3}) 89 | for (let key of opts.keys()) { 90 | arr.push(key) 91 | } 92 | t.deepEqual(arr, [ 93 | 'a', 'b' 94 | ], '.keys() iterates over keys') 95 | t.done() 96 | }) 97 | 98 | test('values', t => { 99 | const testOpts = puddin({ 100 | a: {}, 101 | b: {} 102 | }) 103 | const arr = [] 104 | const opts = testOpts({a: 1, b: 2, c: 3}) 105 | for (let key of opts.values()) { 106 | arr.push(key) 107 | } 108 | t.deepEqual(arr, [ 109 | 1, 2 110 | ], '.values() iterates over values') 111 | t.done() 112 | }) 113 | 114 | test('opts.other iteration', t => { 115 | const testOpts = puddin({ 116 | a: {} 117 | }, { 118 | other (key) { return /^special-/.test(key) } 119 | }) 120 | const arr = [] 121 | const opts = testOpts({ 122 | 'special-a': 3, 123 | a: 1, 124 | b: 2, 125 | 'special-b': 4, 126 | 'a-special': 5 127 | }) 128 | for (let [key, value] of opts.entries()) { 129 | arr.push([key, value]) 130 | } 131 | t.deepEqual(arr, [ 132 | ['a', 1], 133 | ['special-a', 3], 134 | ['special-b', 4] 135 | ], 'iterates over opts.other keys after primary keys') 136 | t.done() 137 | }) 138 | 139 | test('opts.other iteration over nested puddings', t => { 140 | const testOpts = puddin({ 141 | a: {} 142 | }, { 143 | other (key) { return /^special-/.test(key) } 144 | }) 145 | const nestedOpts = puddin({ 146 | b: {} 147 | }) 148 | const arr = [] 149 | const opts = testOpts({ 150 | 'special-b': 4 151 | }).concat({a: 3}, nestedOpts({ 152 | a: 1, 153 | 'a-special': 5 154 | }).concat(nestedOpts({ 155 | b: 2, 156 | 'special-a': 3 157 | }))) 158 | for (let [key, value] of opts.entries()) { 159 | arr.push([key, value]) 160 | } 161 | t.deepEqual(arr, [ 162 | ['a', 1], 163 | ['special-a', 3], 164 | ['special-b', 4] 165 | ], 'expected order even with nested opts.others') 166 | t.done() 167 | }) 168 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [3.5.1](https://github.com/zkat/figgy-pudding/compare/v3.5.0...v3.5.1) (2018-08-25) 7 | 8 | 9 | 10 | 11 | # [3.5.0](https://github.com/zkat/figgy-pudding/compare/v3.4.1...v3.5.0) (2018-08-25) 12 | 13 | 14 | ### Bug Fixes 15 | 16 | * **node:** get rid of Object.entries to add node6 support back ([074f779](https://github.com/zkat/figgy-pudding/commit/074f779)) 17 | 18 | 19 | ### Features 20 | 21 | * **node:** add node@10 to CI config ([78b8937](https://github.com/zkat/figgy-pudding/commit/78b8937)) 22 | 23 | 24 | 25 | 26 | ## [3.4.1](https://github.com/zkat/figgy-pudding/compare/v3.4.0...v3.4.1) (2018-08-16) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **forEach:** get forEach to behave like a normal forEach ([c064755](https://github.com/zkat/figgy-pudding/commit/c064755)) 32 | * **has:** get `in` keyword working right ([fafc5a8](https://github.com/zkat/figgy-pudding/commit/fafc5a8)) 33 | * **iteration:** fix and test iteration of opts.other keys ([7a76217](https://github.com/zkat/figgy-pudding/commit/7a76217)) 34 | * **iteration:** use proper args for forEach/toJSON ([974e879](https://github.com/zkat/figgy-pudding/commit/974e879)) 35 | * **proxy:** make sure proxy corner-cases work ok ([8c66e45](https://github.com/zkat/figgy-pudding/commit/8c66e45)) 36 | * **set:** fix and test the exceptions to writing ([206793b](https://github.com/zkat/figgy-pudding/commit/206793b)) 37 | 38 | 39 | 40 | 41 | # [3.4.0](https://github.com/zkat/figgy-pudding/compare/v3.3.0...v3.4.0) (2018-08-16) 42 | 43 | 44 | ### Features 45 | 46 | * **iterator:** allow iteration over "other" keys ([3c53323](https://github.com/zkat/figgy-pudding/commit/3c53323)) 47 | 48 | 49 | 50 | 51 | # [3.3.0](https://github.com/zkat/figgy-pudding/compare/v3.2.1...v3.3.0) (2018-08-16) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * **props:** allow symbols to pass through ([97b3464](https://github.com/zkat/figgy-pudding/commit/97b3464)) 57 | 58 | 59 | ### Features 60 | 61 | * **pudding:** iteration and serialization support ([0aaa50d](https://github.com/zkat/figgy-pudding/commit/0aaa50d)) 62 | 63 | 64 | 65 | 66 | ## [3.2.1](https://github.com/zkat/figgy-pudding/compare/v3.2.0...v3.2.1) (2018-08-15) 67 | 68 | 69 | ### Bug Fixes 70 | 71 | * **aliases:** make reverse aliases work correctly ([76a255e](https://github.com/zkat/figgy-pudding/commit/76a255e)) 72 | 73 | 74 | 75 | 76 | # [3.2.0](https://github.com/zkat/figgy-pudding/compare/v3.1.0...v3.2.0) (2018-07-26) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * **concat:** have concat spit out a proxy, too ([64e3495](https://github.com/zkat/figgy-pudding/commit/64e3495)) 82 | 83 | 84 | ### Features 85 | 86 | * **default:** pass the pudding itself to default fns ([d9d9e09](https://github.com/zkat/figgy-pudding/commit/d9d9e09)) 87 | 88 | 89 | 90 | 91 | # [3.1.0](https://github.com/zkat/figgy-pudding/compare/v3.0.0...v3.1.0) (2018-04-08) 92 | 93 | 94 | ### Features 95 | 96 | * **opts:** allow direct option fetching without .get() ([ca77aad](https://github.com/zkat/figgy-pudding/commit/ca77aad)) 97 | 98 | 99 | 100 | 101 | # [3.0.0](https://github.com/zkat/figgy-pudding/compare/v2.0.1...v3.0.0) (2018-04-06) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * **ci:** oops -- forgot to update CI config ([7a40563](https://github.com/zkat/figgy-pudding/commit/7a40563)) 107 | * **get:** make provider lookup order like Object.assign ([33ff89b](https://github.com/zkat/figgy-pudding/commit/33ff89b)) 108 | 109 | 110 | ### Features 111 | 112 | * **concat:** add .concat() method to opts ([d310fce](https://github.com/zkat/figgy-pudding/commit/d310fce)) 113 | 114 | 115 | ### meta 116 | 117 | * drop support for node@4 and node@7 ([9f8a61c](https://github.com/zkat/figgy-pudding/commit/9f8a61c)) 118 | 119 | 120 | ### BREAKING CHANGES 121 | 122 | * node@4 and node@7 are no longer supported 123 | * **get:** shadow order for properties in providers is reversed 124 | 125 | 126 | 127 | 128 | ## [2.0.1](https://github.com/zkat/figgy-pudding/compare/v2.0.0...v2.0.1) (2018-03-16) 129 | 130 | 131 | ### Bug Fixes 132 | 133 | * **opts:** ignore non-object providers ([7b9c0f8](https://github.com/zkat/figgy-pudding/commit/7b9c0f8)) 134 | 135 | 136 | 137 | 138 | # [2.0.0](https://github.com/zkat/figgy-pudding/compare/v1.0.0...v2.0.0) (2018-03-16) 139 | 140 | 141 | ### Features 142 | 143 | * **api:** overhauled API with new opt handling concept ([e6cc929](https://github.com/zkat/figgy-pudding/commit/e6cc929)) 144 | * **license:** relicense to ISC ([87479aa](https://github.com/zkat/figgy-pudding/commit/87479aa)) 145 | 146 | 147 | ### BREAKING CHANGES 148 | 149 | * **license:** the license has been changed from CC0-1.0 to ISC. 150 | * **api:** this is a completely different approach than previously 151 | used by this library. See the readme for the new API and an explanation. 152 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class FiggyPudding { 4 | constructor (specs, opts, providers) { 5 | this.__specs = specs || {} 6 | Object.keys(this.__specs).forEach(alias => { 7 | if (typeof this.__specs[alias] === 'string') { 8 | const key = this.__specs[alias] 9 | const realSpec = this.__specs[key] 10 | if (realSpec) { 11 | const aliasArr = realSpec.aliases || [] 12 | aliasArr.push(alias, key) 13 | realSpec.aliases = [...(new Set(aliasArr))] 14 | this.__specs[alias] = realSpec 15 | } else { 16 | throw new Error(`Alias refers to invalid key: ${key} -> ${alias}`) 17 | } 18 | } 19 | }) 20 | this.__opts = opts || {} 21 | this.__providers = reverse((providers).filter( 22 | x => x != null && typeof x === 'object' 23 | )) 24 | this.__isFiggyPudding = true 25 | } 26 | get (key) { 27 | return pudGet(this, key, true) 28 | } 29 | get [Symbol.toStringTag] () { return 'FiggyPudding' } 30 | forEach (fn, thisArg = this) { 31 | for (let [key, value] of this.entries()) { 32 | fn.call(thisArg, value, key, this) 33 | } 34 | } 35 | toJSON () { 36 | const obj = {} 37 | this.forEach((val, key) => { 38 | obj[key] = val 39 | }) 40 | return obj 41 | } 42 | * entries (_matcher) { 43 | for (let key of Object.keys(this.__specs)) { 44 | yield [key, this.get(key)] 45 | } 46 | const matcher = _matcher || this.__opts.other 47 | if (matcher) { 48 | const seen = new Set() 49 | for (let p of this.__providers) { 50 | const iter = p.entries ? p.entries(matcher) : entries(p) 51 | for (let [key, val] of iter) { 52 | if (matcher(key) && !seen.has(key)) { 53 | seen.add(key) 54 | yield [key, val] 55 | } 56 | } 57 | } 58 | } 59 | } 60 | * [Symbol.iterator] () { 61 | for (let [key, value] of this.entries()) { 62 | yield [key, value] 63 | } 64 | } 65 | * keys () { 66 | for (let [key] of this.entries()) { 67 | yield key 68 | } 69 | } 70 | * values () { 71 | for (let [, value] of this.entries()) { 72 | yield value 73 | } 74 | } 75 | concat (...moreConfig) { 76 | return new Proxy(new FiggyPudding( 77 | this.__specs, 78 | this.__opts, 79 | reverse(this.__providers).concat(moreConfig) 80 | ), proxyHandler) 81 | } 82 | } 83 | try { 84 | const util = require('util') 85 | FiggyPudding.prototype[util.inspect.custom] = function (depth, opts) { 86 | return ( 87 | this[Symbol.toStringTag] + ' ' 88 | ) + util.inspect(this.toJSON(), opts) 89 | } 90 | } catch (e) {} 91 | 92 | function BadKeyError (key) { 93 | throw Object.assign(new Error( 94 | `invalid config key requested: ${key}` 95 | ), {code: 'EBADKEY'}) 96 | } 97 | 98 | function pudGet (pud, key, validate) { 99 | let spec = pud.__specs[key] 100 | if (validate && !spec && (!pud.__opts.other || !pud.__opts.other(key))) { 101 | BadKeyError(key) 102 | } else { 103 | if (!spec) { spec = {} } 104 | let ret 105 | for (let p of pud.__providers) { 106 | ret = tryGet(key, p) 107 | if (ret === undefined && spec.aliases && spec.aliases.length) { 108 | for (let alias of spec.aliases) { 109 | if (alias === key) { continue } 110 | ret = tryGet(alias, p) 111 | if (ret !== undefined) { 112 | break 113 | } 114 | } 115 | } 116 | if (ret !== undefined) { 117 | break 118 | } 119 | } 120 | if (ret === undefined && spec.default !== undefined) { 121 | if (typeof spec.default === 'function') { 122 | return spec.default(pud) 123 | } else { 124 | return spec.default 125 | } 126 | } else { 127 | return ret 128 | } 129 | } 130 | } 131 | 132 | function tryGet (key, p) { 133 | let ret 134 | if (p.__isFiggyPudding) { 135 | ret = pudGet(p, key, false) 136 | } else if (typeof p.get === 'function') { 137 | ret = p.get(key) 138 | } else { 139 | ret = p[key] 140 | } 141 | return ret 142 | } 143 | 144 | const proxyHandler = { 145 | has (obj, prop) { 146 | return prop in obj.__specs && pudGet(obj, prop, false) !== undefined 147 | }, 148 | ownKeys (obj) { 149 | return Object.keys(obj.__specs) 150 | }, 151 | get (obj, prop) { 152 | if ( 153 | typeof prop === 'symbol' || 154 | prop.slice(0, 2) === '__' || 155 | prop in FiggyPudding.prototype 156 | ) { 157 | return obj[prop] 158 | } 159 | return obj.get(prop) 160 | }, 161 | set (obj, prop, value) { 162 | if ( 163 | typeof prop === 'symbol' || 164 | prop.slice(0, 2) === '__' 165 | ) { 166 | obj[prop] = value 167 | return true 168 | } else { 169 | throw new Error('figgyPudding options cannot be modified. Use .concat() instead.') 170 | } 171 | }, 172 | deleteProperty () { 173 | throw new Error('figgyPudding options cannot be deleted. Use .concat() and shadow them instead.') 174 | } 175 | } 176 | 177 | module.exports = figgyPudding 178 | function figgyPudding (specs, opts) { 179 | function factory (...providers) { 180 | return new Proxy(new FiggyPudding( 181 | specs, 182 | opts, 183 | providers 184 | ), proxyHandler) 185 | } 186 | return factory 187 | } 188 | 189 | function reverse (arr) { 190 | const ret = [] 191 | arr.forEach(x => ret.unshift(x)) 192 | return ret 193 | } 194 | 195 | function entries (obj) { 196 | return Object.keys(obj).map(k => [k, obj[k]]) 197 | } 198 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tap').test 4 | const puddin = require('../') 5 | 6 | test('basic property fetching', t => { 7 | const testOpts = puddin({ 8 | a: {} 9 | }) 10 | const opts = testOpts({a: 1, b: 2}) 11 | t.equal(opts.get('a'), 1, 'defined opt fetched') 12 | t.equal(opts.a, 1, 'works through a proxy too') 13 | t.ok('a' in opts, 'supports `in` keyword') 14 | t.notOk('b' in opts, '`in` false for non-declared props') 15 | t.throws(() => { 16 | opts.get('b') 17 | }, /invalid config key requested: b/i) 18 | t.throws(() => { 19 | opts.b // eslint-disable-line 20 | }, /invalid config key requested: b/i) 21 | t.throws(() => { 22 | delete opts.a 23 | }, /cannot be deleted/) 24 | // NOTE: The following doesn't seem to work, and it seems to be a Node/V8 bug 25 | // t.deepEqual(Object.keys(opts), ['a'], 'got keys correctly') 26 | t.done() 27 | }) 28 | 29 | test('read-only for most opts', t => { 30 | const testOpts = puddin({ 31 | a: {} 32 | }) 33 | const opts = testOpts({a: 1}) 34 | t.throws(() => { 35 | opts.a = 2 36 | }, /cannot be modified/) 37 | t.doesNotThrow(() => { 38 | opts.__a = 'a' 39 | }, '__-prefixed can be written') 40 | t.doesNotThrow(() => { 41 | opts[Symbol('hi')] = 'hi' 42 | }, 'Symbols can be written') 43 | t.done() 44 | }) 45 | 46 | test('Map-like support', t => { 47 | const testOpts = puddin({ 48 | a: {} 49 | }) 50 | const opts = testOpts(new Map([['a', 1], ['b', 2]])) 51 | t.equal(opts.get('a'), 1, 'defined .get opt fetched') 52 | t.throws(() => { 53 | opts.get('b') 54 | }, /invalid config key requested: b/i) 55 | t.done() 56 | }) 57 | 58 | test('passing through no args is ok', t => { 59 | const testOpts = puddin() 60 | const opts = testOpts({b: 2}) 61 | t.throws(() => { 62 | opts.get('b') 63 | }, /invalid config key requested: b/i) 64 | t.done() 65 | }) 66 | 67 | test('passing in null providers is ok', t => { 68 | const testOpts = puddin({ 69 | a: {} 70 | }) 71 | const opts = testOpts(null, {a: 1, b: 2}, false, undefined) 72 | t.equal(opts.get('a'), 1, 'defined opt fetched') 73 | t.throws(() => { 74 | opts.get('b') 75 | }, /invalid config key requested: b/i) 76 | t.done() 77 | }) 78 | 79 | test('supports defaults', t => { 80 | const testOpts = puddin({ 81 | a: {default: 1}, 82 | b: {default: () => 2} 83 | }) 84 | const opts = testOpts() 85 | t.equal(opts.get('a'), 1, 'got non-function default value') 86 | t.equal(opts.get('b'), 2, 'got function-based default value') 87 | t.done() 88 | }) 89 | 90 | test('allow programmatic keys', t => { 91 | const testOpts = puddin({}, { 92 | other (key) { return key === 'c' || key === 'd' } 93 | }) 94 | const opts = testOpts({a: 1, b: 2, c: 3, d: 4}) 95 | t.equal(opts.get('c'), 3, 'programmatic key succeeded') 96 | t.equal(opts.get('d'), 4, 'other programmatic key succeeded') 97 | t.throws( 98 | () => opts.get('b'), 99 | /invalid config key requested: b/i, 100 | 'non-defined, non-other key still fails' 101 | ) 102 | t.done() 103 | }) 104 | 105 | test('multiple providers', t => { 106 | const testOpts = puddin({ 107 | a: {}, 108 | b: {}, 109 | c: {} 110 | }) 111 | const opts = testOpts({a: 3, b: 3, c: 3}, {a: 2, b: 2}, {a: 1}) 112 | t.equal(opts.get('a'), 1, 'a from first provider') 113 | t.equal(opts.get('b'), 2, 'b from second provider') 114 | t.equal(opts.get('c'), 3, 'c from third provider') 115 | t.done() 116 | }) 117 | 118 | test('nesting puds', t => { 119 | const topPud = puddin({ 120 | a: {} 121 | }) 122 | const childPud = puddin({ 123 | b: {} 124 | }) 125 | const granPud = puddin({ 126 | c: {} 127 | }) 128 | const grandchild = granPud({a: 1, b: 2, c: 3}) 129 | const child = childPud(grandchild) 130 | const top = topPud(child) 131 | t.equal(top.get('a'), 1, 'topPud property fetched successfully') 132 | t.equal(child.get('b'), 2, 'childPud property fetched successfully') 133 | t.equal(grandchild.get('c'), 3, 'granPud property fetched successfully') 134 | t.throws( 135 | () => top.get('b'), 136 | /invalid config key requested: b/i, 137 | 'topPud has no access to childPud property' 138 | ) 139 | t.throws( 140 | () => child.get('a'), 141 | /invalid config key requested: a/i, 142 | 'childPud has no access to childPud property' 143 | ) 144 | t.throws( 145 | () => grandchild.get('b'), 146 | /invalid config key requested: b/i, 147 | 'granPud has no access to childPud property' 148 | ) 149 | t.done() 150 | }) 151 | 152 | test('nested pud defaults', t => { 153 | const topPud = puddin({ 154 | a: {} 155 | }) 156 | const childPud = puddin({ 157 | a: {default: 2} 158 | }) 159 | const child = childPud({b: 'idk'}) 160 | const top = topPud(child) 161 | t.equal(top.get('a'), 2, 'topPud property uses childPud defaults') 162 | t.equal(child.get('a'), 2, 'childPud property uses own defaults') 163 | t.done() 164 | }) 165 | 166 | test('concat', t => { 167 | const testOpts = puddin({ 168 | a: {}, 169 | b: {}, 170 | c: {} 171 | }) 172 | let opts = testOpts() 173 | opts = opts.concat({a: 3, b: 3, c: 3}) 174 | t.equal(opts.get('c'), 3, 'c from third provider') 175 | opts = opts.concat({a: 2, b: 2}, {a: 1}) 176 | t.equal(opts.get('a'), 1, 'a from first provider') 177 | t.equal(opts.get('b'), 2, 'b from second provider') 178 | t.done() 179 | }) 180 | 181 | test('is delicious and figgy', t => { 182 | const testOpts = puddin({}) 183 | let opts = testOpts() 184 | t.ok(opts.__isFiggyPudding, 'definitely figgy and delicious') 185 | t.done() 186 | }) 187 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # figgy-pudding [![npm version](https://img.shields.io/npm/v/figgy-pudding.svg)](https://npm.im/figgy-pudding) [![license](https://img.shields.io/npm/l/figgy-pudding.svg)](https://npm.im/figgy-pudding) [![Travis](https://img.shields.io/travis/zkat/figgy-pudding.svg)](https://travis-ci.org/zkat/figgy-pudding) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/zkat/figgy-pudding?svg=true)](https://ci.appveyor.com/project/zkat/figgy-pudding) [![Coverage Status](https://coveralls.io/repos/github/zkat/figgy-pudding/badge.svg?branch=latest)](https://coveralls.io/github/zkat/figgy-pudding?branch=latest) 2 | 3 | [`figgy-pudding`](https://github.com/zkat/figgy-pudding) is a small JavaScript 4 | library for managing and composing cascading options objects -- hiding what 5 | needs to be hidden from each layer, without having to do a lot of manual munging 6 | and passing of options. 7 | 8 | ### The God Object is Dead! 9 | ### Now Bring Us Some Figgy Pudding! 10 | 11 | ## Install 12 | 13 | `$ npm install figgy-pudding` 14 | 15 | ## Table of Contents 16 | 17 | * [Example](#example) 18 | * [Features](#features) 19 | * [API](#api) 20 | * [`figgyPudding(spec)`](#figgy-pudding) 21 | * [`PuddingFactory(values)`](#pudding-factory) 22 | * [`opts.get()`](#opts-get) 23 | * [`opts.concat()`](#opts-concat) 24 | * [`opts.toJSON()`](#opts-to-json) 25 | * [`opts.forEach()`](#opts-for-each) 26 | * [`opts[Symbol.iterator]()`](#opts-symbol-iterator) 27 | * [`opts.entries()`](#opts-entries) 28 | * [`opts.keys()`](#opts-keys) 29 | * [`opts.value()`](#opts-values) 30 | 31 | ### Example 32 | 33 | ```javascript 34 | // print-package.js 35 | const fetch = require('./fetch.js') 36 | const puddin = require('figgy-pudding') 37 | 38 | const PrintOpts = puddin({ 39 | json: { default: false } 40 | }) 41 | 42 | async function printPkg (name, opts) { 43 | // Expected pattern is to call this in every interface function. If `opts` is 44 | // not passed in, it will automatically create an (empty) object for it. 45 | opts = PrintOpts(opts) 46 | const uri = `https://registry.npmjs.com/${name}` 47 | const res = await fetch(uri, opts.concat({ 48 | // Add or override any passed-in configs and pass them down. 49 | log: customLogger 50 | })) 51 | // The following would throw an error, because it's not in PrintOpts: 52 | // console.log(opts.log) 53 | if (opts.json) { 54 | return res.json() 55 | } else { 56 | return res.text() 57 | } 58 | } 59 | 60 | console.log(await printPkg('figgy', { 61 | // Pass in *all* configs at the toplevel, as a regular object. 62 | json: true, 63 | cache: './tmp-cache' 64 | })) 65 | ``` 66 | 67 | ```javascript 68 | // fetch.js 69 | const puddin = require('figgy-pudding') 70 | 71 | const FetchOpts = puddin({ 72 | log: { default: require('npmlog') }, 73 | cache: {} 74 | }) 75 | 76 | module.exports = async function (..., opts) { 77 | opts = FetchOpts(opts) 78 | } 79 | ``` 80 | 81 | ### Features 82 | 83 | * hide options from layer that didn't ask for it 84 | * shared multi-layer options 85 | * make sure `opts` argument is available 86 | * transparent key access like normal keys, through a Proxy. No need for`.get()`! 87 | * default values 88 | * key aliases 89 | * arbitrary key filter functions 90 | * key/value iteration 91 | * serialization 92 | * 100% test coverage using `tap --100` 93 | 94 | ### API 95 | 96 | #### `> figgyPudding({ key: { default: val } | String }, [opts]) -> PuddingFactory` 97 | 98 | Defines an Options constructor that can be used to collect only the needed 99 | options. 100 | 101 | An optional `default` property for specs can be used to specify default values 102 | if nothing was passed in. 103 | 104 | If the value for a spec is a string, it will be treated as an alias to that 105 | other key. 106 | 107 | ##### Example 108 | 109 | ```javascript 110 | const MyAppOpts = figgyPudding({ 111 | lg: 'log', 112 | log: { 113 | default: () => require('npmlog') 114 | }, 115 | cache: {} 116 | }) 117 | ``` 118 | 119 | #### `> PuddingFactory(...providers) -> FiggyPudding{}` 120 | 121 | Instantiates an options object defined by `figgyPudding()`, which uses 122 | `providers`, in order, to find requested properties. 123 | 124 | Each provider can be either a plain object, a `Map`-like object (that is, one 125 | with a `.get()` method) or another figgyPudding `Opts` object. 126 | 127 | When nesting `Opts` objects, their properties will not become available to the 128 | new object, but any further nested `Opts` that reference that property _will_ be 129 | able to read from their grandparent, as long as they define that key. Default 130 | values for nested `Opts` parents will be used, if found. 131 | 132 | ##### Example 133 | 134 | ```javascript 135 | const ReqOpts = figgyPudding({ 136 | follow: {} 137 | }) 138 | 139 | const opts = ReqOpts({ 140 | follow: true, 141 | log: require('npmlog') 142 | }) 143 | 144 | opts.follow // => true 145 | opts.log // => Error: ReqOpts does not define `log` 146 | 147 | const MoreOpts = figgyPudding({ 148 | log: {} 149 | }) 150 | MoreOpts(opts).log // => npmlog object (passed in from original plain obj) 151 | MoreOpts(opts).follow // => Error: MoreOpts does not define `follow` 152 | ``` 153 | 154 | #### `> opts.get(key) -> Value` 155 | 156 | Gets a value from the options object. 157 | 158 | ##### Example 159 | 160 | ```js 161 | const opts = MyOpts(config) 162 | opts.get('foo') // value of `foo` 163 | opts.foo // Proxy-based access through `.get()` 164 | ``` 165 | 166 | #### `> opts.concat(...moreProviders) -> FiggyPudding{}` 167 | 168 | Creates a new opts object of the same type as `opts` with additional providers. 169 | Providers further to the right shadow providers to the left, with properties in 170 | the original `opts` being shadows by the new providers. 171 | 172 | ##### Example 173 | 174 | ```js 175 | const opts = MyOpts({x: 1}) 176 | opts.get('x') // 1 177 | opts.concat({x: 2}).get('x') // 2 178 | opts.get('x') // 1 (original opts object left intact) 179 | ``` 180 | 181 | #### `> opts.toJSON() -> Value` 182 | 183 | Converts `opts` to a plain, JSON-stringifiable JavaScript value. Used internally 184 | by JavaScript to get `JSON.stringify()` working. 185 | 186 | Only keys that are readable by the current pudding type will be serialized. 187 | 188 | ##### Example 189 | 190 | ```js 191 | const opts = MyOpts({x: 1}) 192 | opts.toJSON() // {x: 1} 193 | JSON.stringify(opts) // '{"x":1}' 194 | ``` 195 | 196 | #### `> opts.forEach((value, key, opts) => {}, thisArg) -> undefined` 197 | 198 | Iterates over the values of `opts`, limited to the keys readable by the current 199 | pudding type. `thisArg` will be used to set the `this` argument when calling the 200 | `fn`. 201 | 202 | ##### Example 203 | 204 | ```js 205 | const opts = MyOpts({x: 1, y: 2}) 206 | opts.forEach((value, key) => console.log(key, '=', value)) 207 | ``` 208 | 209 | #### `> opts.entries() -> Iterator<[[key, value], ...]>` 210 | 211 | Returns an iterator that iterates over the keys and values in `opts`, limited to 212 | the keys readable by the current pudding type. Each iteration returns an array 213 | of `[key, value]`. 214 | 215 | ##### Example 216 | 217 | ```js 218 | const opts = MyOpts({x: 1, y: 2}) 219 | [...opts({x: 1, y: 2}).entries()] // [['x', 1], ['y', 2]] 220 | ``` 221 | 222 | #### `> opts[Symbol.iterator]() -> Iterator<[[key, value], ...]>` 223 | 224 | Returns an iterator that iterates over the keys and values in `opts`, limited to 225 | the keys readable by the current pudding type. Each iteration returns an array 226 | of `[key, value]`. Makes puddings work natively with JS iteration mechanisms. 227 | 228 | ##### Example 229 | 230 | ```js 231 | const opts = MyOpts({x: 1, y: 2}) 232 | [...opts({x: 1, y: 2})] // [['x', 1], ['y', 2]] 233 | for (let [key, value] of opts({x: 1, y: 2})) { 234 | console.log(key, '=', value) 235 | } 236 | ``` 237 | 238 | #### `> opts.keys() -> Iterator<[key, ...]>` 239 | 240 | Returns an iterator that iterates over the keys in `opts`, limited to the keys 241 | readable by the current pudding type. 242 | 243 | ##### Example 244 | 245 | ```js 246 | const opts = MyOpts({x: 1, y: 2}) 247 | [...opts({x: 1, y: 2}).keys()] // ['x', 'y'] 248 | ``` 249 | 250 | #### `> opts.values() -> Iterator<[value, ...]>` 251 | 252 | Returns an iterator that iterates over the values in `opts`, limited to the keys 253 | readable by the current pudding type. 254 | 255 | ##### Example 256 | ' 257 | ```js 258 | const opts = MyOpts({x: 1, y: 2}) 259 | [...opts({x: 1, y: 2}).values()] // [1, 2] 260 | ``` 261 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## When Something Happens 4 | 5 | If you see a Code of Conduct violation, follow these steps: 6 | 7 | 1. Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s) or commits. 8 | 2. That person should immediately stop the behavior and correct the issue. 9 | 3. If this doesn’t happen, or if you're uncomfortable speaking up, [contact the maintainers](#contacting-maintainers). 10 | 4. As soon as available, a maintainer will look into the issue, and take [further action (see below)](#further-enforcement), starting with a warning, then temporary block, then long-term repo or organization ban. 11 | 12 | When reporting, please include any relevant details, links, screenshots, context, or other information that may be used to better understand and resolve the situation. 13 | 14 | **The maintainer team will prioritize the well-being and comfort of the recipients of the violation over the comfort of the violator.** See [some examples below](#enforcement-examples). 15 | 16 | ## Our Pledge 17 | 18 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers of this project pledge to making participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, technical preferences, nationality, personal appearance, race, religion, or sexual identity and orientation. 19 | 20 | ## Our Standards 21 | 22 | Examples of behavior that contributes to creating a positive environment include: 23 | 24 | * Using welcoming and inclusive language. 25 | * Being respectful of differing viewpoints and experiences. 26 | * Gracefully accepting constructive feedback. 27 | * Focusing on what is best for the community. 28 | * Showing empathy and kindness towards other community members. 29 | * Encouraging and raising up your peers in the project so you can all bask in hacks and glory. 30 | 31 | Examples of unacceptable behavior by participants include: 32 | 33 | * The use of sexualized language or imagery and unwelcome sexual attention or advances, including when simulated online. The only exception to sexual topics is channels/spaces specifically for topics of sexual identity. 34 | * Casual mention of slavery or indentured servitude and/or false comparisons of one's occupation or situation to slavery. Please consider using or asking about alternate terminology when referring to such metaphors in technology. 35 | * Making light of/making mocking comments about trigger warnings and content warnings. 36 | * Trolling, insulting/derogatory comments, and personal or political attacks. 37 | * Public or private harassment, deliberate intimidation, or threats. 38 | * Publishing others' private information, such as a physical or electronic address, without explicit permission. This includes any sort of "outing" of any aspect of someone's identity without their consent. 39 | * Publishing private screenshots or quotes of interactions in the context of this project without all quoted users' *explicit* consent. 40 | * Publishing of private communication that doesn't have to do with reporting harrassment. 41 | * Any of the above even when [presented as "ironic" or "joking"](https://en.wikipedia.org/wiki/Hipster_racism). 42 | * Any attempt to present "reverse-ism" versions of the above as violations. Examples of reverse-isms are "reverse racism", "reverse sexism", "heterophobia", and "cisphobia". 43 | * Unsolicited explanations under the assumption that someone doesn't already know it. Ask before you teach! Don't assume what people's knowledge gaps are. 44 | * [Feigning or exaggerating surprise](https://www.recurse.com/manual#no-feigned-surprise) when someone admits to not knowing something. 45 | * "[Well-actuallies](https://www.recurse.com/manual#no-well-actuallys)" 46 | * Other conduct which could reasonably be considered inappropriate in a professional or community setting. 47 | 48 | ## Scope 49 | 50 | This Code of Conduct applies both within spaces involving this project and in other spaces involving community members. This includes the repository, its Pull Requests and Issue tracker, its Twitter community, private email communications in the context of the project, and any events where members of the project are participating, as well as adjacent communities and venues affecting the project's members. 51 | 52 | Depending on the violation, the maintainers may decide that violations of this code of conduct that have happened outside of the scope of the community may deem an individual unwelcome, and take appropriate action to maintain the comfort and safety of its members. 53 | 54 | ### Other Community Standards 55 | 56 | As a project on GitHub, this project is additionally covered by the [GitHub Community Guidelines](https://help.github.com/articles/github-community-guidelines/). 57 | 58 | Additionally, as a project hosted on npm, is is covered by [npm, Inc's Code of Conduct](https://www.npmjs.com/policies/conduct). 59 | 60 | Enforcement of those guidelines after violations overlapping with the above are the responsibility of the entities, and enforcement may happen in any or all of the services/communities. 61 | 62 | ## Maintainer Enforcement Process 63 | 64 | Once the maintainers get involved, they will follow a documented series of steps and do their best to preserve the well-being of project members. This section covers actual concrete steps. 65 | 66 | ### Contacting Maintainers 67 | 68 | You may get in touch with the maintainer team through any of the following methods: 69 | 70 | * Through email: 71 | * [kzm@sykosomatic.org](mailto:kzm@sykosomatic.org) (Kat Marchán) 72 | 73 | ### Further Enforcement 74 | 75 | If you've already followed the [initial enforcement steps](#enforcement), these are the steps maintainers will take for further enforcement, as needed: 76 | 77 | 1. Repeat the request to stop. 78 | 2. If the person doubles down, they will have offending messages removed or edited by a maintainers given an official warning. The PR or Issue may be locked. 79 | 3. If the behavior continues or is repeated later, the person will be blocked from participating for 24 hours. 80 | 4. If the behavior continues or is repeated after the temporary block, a long-term (6-12mo) ban will be used. 81 | 82 | On top of this, maintainers may remove any offending messages, images, contributions, etc, as they deem necessary. 83 | 84 | Maintainers reserve full rights to skip any of these steps, at their discretion, if the violation is considered to be a serious and/or immediate threat to the health and well-being of members of the community. These include any threats, serious physical or verbal attacks, and other such behavior that would be completely unacceptable in any social setting that puts our members at risk. 85 | 86 | Members expelled from events or venues with any sort of paid attendance will not be refunded. 87 | 88 | ### Who Watches the Watchers? 89 | 90 | Maintainers and other leaders who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. These may include anything from removal from the maintainer team to a permanent ban from the community. 91 | 92 | Additionally, as a project hosted on both GitHub and npm, [their own Codes of Conducts may be applied against maintainers of this project](#other-community-standards), externally of this project's procedures. 93 | 94 | ### Enforcement Examples 95 | 96 | #### The Best Case 97 | 98 | The vast majority of situations work out like this. This interaction is common, and generally positive. 99 | 100 | > Alex: "Yeah I used X and it was really crazy!" 101 | 102 | > Patt (not a maintainer): "Hey, could you not use that word? What about 'ridiculous' instead?" 103 | 104 | > Alex: "oh sorry, sure." -> edits old comment to say "it was really confusing!" 105 | 106 | #### The Maintainer Case 107 | 108 | Sometimes, though, you need to get maintainers involved. Maintainers will do their best to resolve conflicts, but people who were harmed by something **will take priority**. 109 | 110 | > Patt: "Honestly, sometimes I just really hate using $library and anyone who uses it probably sucks at their job." 111 | 112 | > Alex: "Whoa there, could you dial it back a bit? There's a CoC thing about attacking folks' tech use like that." 113 | 114 | > Patt: "I'm not attacking anyone, what's your problem?" 115 | 116 | > Alex: "@maintainers hey uh. Can someone look at this issue? Patt is getting a bit aggro. I tried to nudge them about it, but nope." 117 | 118 | > KeeperOfCommitBits: (on issue) "Hey Patt, maintainer here. Could you tone it down? This sort of attack is really not okay in this space." 119 | 120 | > Patt: "Leave me alone I haven't said anything bad wtf is wrong with you." 121 | 122 | > KeeperOfCommitBits: (deletes user's comment), "@patt I mean it. Please refer to the CoC over at (URL to this CoC) if you have questions, but you can consider this an actual warning. I'd appreciate it if you reworded your messages in this thread, since they made folks there uncomfortable. Let's try and be kind, yeah?" 123 | 124 | > Patt: "@keeperofbits Okay sorry. I'm just frustrated and I'm kinda burnt out and I guess I got carried away. I'll DM Alex a note apologizing and edit my messages. Sorry for the trouble." 125 | 126 | > KeeperOfCommitBits: "@patt Thanks for that. I hear you on the stress. Burnout sucks :/. Have a good one!" 127 | 128 | #### The Nope Case 129 | 130 | > PepeTheFrog🐸: "Hi, I am a literal actual nazi and I think white supremacists are quite fashionable." 131 | 132 | > Patt: "NOOOOPE. OH NOPE NOPE." 133 | 134 | > Alex: "JFC NO. NOPE. @keeperofbits NOPE NOPE LOOK HERE" 135 | 136 | > KeeperOfCommitBits: "👀 Nope. NOPE NOPE NOPE. 🔥" 137 | 138 | > PepeTheFrog🐸 has been banned from all organization or user repositories belonging to KeeperOfCommitBits. 139 | 140 | ## Attribution 141 | 142 | This Code of Conduct was generated using [WeAllJS Code of Conduct Generator](https://npm.im/weallbehave), which is based on the [WeAllJS Code of 143 | Conduct](https://wealljs.org/code-of-conduct), which is itself based on 144 | [Contributor Covenant](http://contributor-covenant.org), version 1.4, available 145 | at 146 | [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4), 147 | and the LGBTQ in Technology Slack [Code of 148 | Conduct](http://lgbtq.technology/coc.html). 149 | --------------------------------------------------------------------------------