diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 0000000..d657c7a
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,15 @@
+engines:
+ eslint:
+ enabled: true
+ channel: 'eslint-8'
+ config:
+ config: '.eslintrc.yaml'
+
+checks:
+ method-complexity:
+ config:
+ threshold: 10
+
+ratings:
+ paths:
+ - '**.js'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 435be73..9198c99 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,6 +2,7 @@ name: CI
on:
push:
+ pull_request:
env:
CI: true
@@ -21,3 +22,7 @@ jobs:
windows:
needs: [lint]
uses: haraka/.github/.github/workflows/windows.yml@master
+
+ macos:
+ needs: [lint]
+ uses: haraka/.github/.github/workflows/macos.yml@master
diff --git a/.release b/.release
index 7cd5707..0fa4e69 160000
--- a/.release
+++ b/.release
@@ -1 +1 @@
-Subproject commit 7cd5707f7d69f8d4dca1ec407ada911890e59d0a
+Subproject commit 0fa4e690ffabb0157e46d56f18e4f7cfe49ce291
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2227e4c..6ef84ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
### Unreleased
+### [1.1.0] - 2024-05-03
+
+- set: added 'default' mode. See docs. #
+
### [1.0.7] - 2024-04-08
- use `[files]` in package.json. Delete .npmignore.
@@ -42,3 +46,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
[1.0.4]: https://github.com/haraka/haraka-notes/releases/tag/1.0.4
[1.0.6]: https://github.com/haraka/haraka-notes/releases/tag/1.0.6
[1.0.7]: https://github.com/haraka/haraka-notes/releases/tag/v1.0.7
+[1.1.0]: https://github.com/haraka/haraka-notes/releases/tag/v1.1.0
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..0a6d322
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,8 @@
+# Contributors
+
+This handcrafted artisinal software is brought to you by:
+
+|
msimerson (23)|
+| :---: |
+
+this file is maintained by [.release](https://github.com/msimerson/.release)
diff --git a/README.md b/README.md
index ac35779..7436d63 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,23 @@
Notes are objects that exist on Haraka connections and transactions. Prior to the release of [haraka-notes](https://github.com/haraka/haraka-notes), notes was just an empty object. Now notes is an empty object with two functions:
-### set (path, value)
+## Usage
-Sets a note at a dot delimited path to the specified value. The path can be any number of levels deep and any missing objects in the path are [autovivified](https://en.wikipedia.org/wiki/Autovivification). Perl refugees, contain yourselves.
+```js
+const Notes = require('haraka-notes')
+const myNote = new Notes()
+
+myNote.set('some.path', 'a value') // { some: {path: 'a value'}}
+myNote.get('some.path') // 'a value'
+```
+
+### set (path, value, [onlyIfUndefined])
+
+Sets a note at a dot delimited path to the specified value. The path can be any number of levels deep and any missing objects in the path are [autovivified](https://en.wikipedia.org/wiki/Autovivification). Perl afficianados, contain yourselves.
+
+#### set default
+
+If the third set argument is any truthy value, then the property is only set if the current value is undefined. This is useful for applying default values.
```js
connection.transaction.notes.set('queue.wants', 'smtp_forward')
diff --git a/index.js b/index.js
index 8ceacfc..b7b1bdc 100644
--- a/index.js
+++ b/index.js
@@ -1,54 +1,58 @@
class Notes {
- constructor(notes) {
- if (notes && typeof notes === 'object') {
- Object.assign(this, notes)
- }
-
- Object.defineProperty(this, 'set', {
- configurable: false,
- enumerable: false,
- writable: false,
- value: assignPathValue.bind(this),
- })
-
- Object.defineProperty(this, 'get', {
- configurable: false,
- enumerable: false,
- writable: false,
- value: getPathValue.bind(this),
- })
+ constructor(notes) {
+ if (notes && typeof notes === 'object') {
+ Object.assign(this, notes)
}
+
+ Object.defineProperty(this, 'set', {
+ configurable: false,
+ enumerable: false,
+ writable: false,
+ value: assignPathValue.bind(this),
+ })
+
+ Object.defineProperty(this, 'get', {
+ configurable: false,
+ enumerable: false,
+ writable: false,
+ value: getPathValue.bind(this),
+ })
+ }
}
module.exports = Notes
function getSegments(path) {
- // a dot.delimited.path
- if (typeof path === 'string') return path.split('.')
+ // a dot.delimited.path
+ if (typeof path === 'string') return path.split('.')
- // ['one', 'two', 'thr.ee']
- if (Array.isArray(path)) return path
+ // ['one', 'two', 'thr.ee']
+ if (Array.isArray(path)) return path
}
-function assignPathValue(path, value) {
- if (path === undefined || value === undefined) return
+function assignPathValue(path, value, onlyWhenUndefined) {
+ if (path === undefined || value === undefined) return
- const segments = getSegments(path)
- let dest = this
+ const segments = getSegments(path)
+ let dest = this
- while (segments.length > 1) {
+ while (segments.length > 1) {
// create any missing paths
- if (!dest[segments[0]]) dest[segments[0]] = {}
- // set dest one path segment deeper
- dest = dest[segments.shift()]
- }
+ if (!dest[segments[0]]) dest[segments[0]] = {}
+ // set dest one path segment deeper
+ dest = dest[segments.shift()]
+ }
+ if (onlyWhenUndefined) {
+ if (dest[segments[0]] === undefined) dest[segments[0]] = value
+ } else {
dest[segments[0]] = value
+ }
}
function getPathValue(path) {
- if (!path) return
- const segments = getSegments(path)
- return segments.reduce((prev, curr) => {
- return prev ? prev[curr] : undefined
- }, this)
+ if (!path) return
+ const segments = getSegments(path)
+ return segments.reduce((prev, curr) => {
+ return prev ? prev[curr] : undefined
+ }, this)
}
diff --git a/package.json b/package.json
index 7189b26..5418450 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "haraka-notes",
- "version": "1.0.7",
+ "version": "1.1.0",
"description": "Haraka Notes",
"main": "index.js",
"files": [
@@ -13,8 +13,8 @@
"prettier": "npx prettier . --check",
"prettier:fix": "npx prettier . --write --log-level=warn",
"test": "npx mocha@10",
- "versions": "npx @msimerson/dependency-version-checker check",
- "versions:fix": "npx @msimerson/dependency-version-checker update && npm run prettier:fix"
+ "versions": "npx dependency-version-checker check",
+ "versions:fix": "npx dependency-version-checker update && npm run prettier:fix"
},
"repository": {
"type": "git",
diff --git a/test/index.js b/test/index.js
index ebab3da..16f565a 100644
--- a/test/index.js
+++ b/test/index.js
@@ -3,91 +3,89 @@ const assert = require('assert')
const Notes = require('../index')
describe('notes', () => {
- beforeEach((done) => {
- this.notes = new Notes()
- done()
- })
+ beforeEach((done) => {
+ this.notes = new Notes()
+ done()
+ })
- it('exports an object', (done) => {
- // console.log(this.notes)
- assert.ok(typeof this.notes === 'object')
- done()
- })
+ it('exports an object', () => {
+ assert.ok(typeof this.notes === 'object')
+ })
- const functionList = ['get', 'set']
+ const functionList = ['get', 'set']
- functionList.forEach((fn) => {
- it(`has ${fn}()`, (done) => {
- assert.equal(typeof this.notes[fn], 'function')
- done()
- })
+ for (const fn of functionList) {
+ it(`has ${fn}()`, () => {
+ assert.equal(typeof this.notes[fn], 'function')
})
+ }
- functionList.forEach((fn) => {
- it(`ignores attempts to redefine ${fn}`, (done) => {
- this.notes[fn] = 'turd'
- this.notes[fn]('turd')
- done()
- })
+ for (const fn of functionList) {
+ it(`ignores attempts to redefine ${fn}`, () => {
+ this.notes[fn] = 'turd'
+ this.notes[fn]('turd')
})
+ }
- it('sets a top level value', (done) => {
- this.notes.set('foo', 'bar')
- // console.log(this.notes)
- assert.equal(this.notes.foo, 'bar')
- done()
- })
+ it('sets a top level value', () => {
+ this.notes.set('foo', 'bar')
+ assert.equal(this.notes.foo, 'bar')
+ })
- it('can set a false value', (done) => {
- this.notes.set('boolean', false)
- assert.equal(this.notes.boolean, false)
- done()
- })
+ it('can set a false value', () => {
+ this.notes.set('boolean', false)
+ assert.equal(this.notes.boolean, false)
+ })
- it('gets a top level value', (done) => {
- this.notes.set('foo', 'bar')
- assert.equal(this.notes.get('foo'), 'bar')
- done()
- })
+ it('gets a top level value', () => {
+ this.notes.set('foo', 'bar')
+ assert.equal(this.notes.get('foo'), 'bar')
+ })
- it('sets/gets a second level value', (done) => {
- this.notes.set('seg1.seg2', 'bar')
- assert.equal(this.notes.seg1.seg2, 'bar')
- assert.equal(this.notes.get('seg1.seg2'), 'bar')
- done()
- })
+ it('sets/gets a second level value', () => {
+ this.notes.set('seg1.seg2', 'bar')
+ assert.equal(this.notes.seg1.seg2, 'bar')
+ assert.equal(this.notes.get('seg1.seg2'), 'bar')
+ })
- it('sets/gets a three level value', (done) => {
- this.notes.set('one.two.three', 'floor')
- assert.equal(this.notes.one.two.three, 'floor')
- assert.equal(this.notes.get('one.two.three'), 'floor')
- done()
- })
+ it('sets/gets a three level value', () => {
+ this.notes.set('one.two.three', 'floor')
+ assert.equal(this.notes.one.two.three, 'floor')
+ assert.equal(this.notes.get('one.two.three'), 'floor')
+ })
- it('supports array syntax', (done) => {
- this.notes.set(['one', 'two', 'three'], 'floor')
- assert.equal(this.notes.one.two.three, 'floor')
- assert.equal(this.notes.get(['one', 'two', 'three']), 'floor')
- done()
- })
+ it('supports array syntax', () => {
+ this.notes.set(['one', 'two', 'three'], 'floor')
+ assert.equal(this.notes.one.two.three, 'floor')
+ assert.equal(this.notes.get(['one', 'two', 'three']), 'floor')
+ })
- it('array syntax tolerates dots', (done) => {
- this.notes.set(['one', 'two', 'three.four'], 'floor')
- assert.equal(this.notes.one.two['three.four'], 'floor')
- assert.equal(this.notes.get(['one', 'two', 'three.four']), 'floor')
- done()
- })
+ it('array syntax tolerates dots', () => {
+ this.notes.set(['one', 'two', 'three.four'], 'floor')
+ assert.equal(this.notes.one.two['three.four'], 'floor')
+ assert.equal(this.notes.get(['one', 'two', 'three.four']), 'floor')
+ })
+
+ it('sets default sets a property', () => {
+ this.notes.set(['one', 'two'], 'tree', true)
+ assert.equal(this.notes.one.two, 'tree')
+ })
+
+ it('set default does NOT change defined property', () => {
+ this.notes.set('one.two', 'tree', true)
+ this.notes.set('one.two', 'three', true)
+ assert.equal(this.notes.one.two, 'tree')
+ })
})
describe('notes + object', () => {
- it('assigns instantiation object', (done) => {
- const passIn = {
- one: true,
- two: 'false',
- three: 'floor',
- }
- this.notes = this.notes = new Notes(passIn)
- assert.deepEqual(this.notes, passIn)
- done()
- })
+ it('assigns instantiation object', () => {
+ const passIn = {
+ one: true,
+ two: 'false',
+ three: 'floor',
+ }
+ this.notes = this.notes = new Notes(passIn)
+ assert.deepEqual(this.notes, passIn)
+ })
})