Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v1.1.0 #21

Merged
merged 5 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
engines:
eslint:
enabled: true
channel: 'eslint-8'
config:
config: '.eslintrc.yaml'

checks:
method-complexity:
config:
threshold: 10

ratings:
paths:
- '**.js'
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: CI

on:
push:
pull_request:

env:
CI: true
Expand All @@ -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
2 changes: 1 addition & 1 deletion .release
Submodule .release updated 7 files
+3 −0 CHANGELOG.md
+4 −3 README.md
+8 −0 base.sh
+82 −0 contributors.js
+1 −1 finish.sh
+36 −5 start.sh
+29 −17 submit.sh
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
8 changes: 8 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Contributors

This handcrafted artisinal software is brought to you by:

| <img height="80" src="https://avatars.githubusercontent.com/u/261635?v=4"><br><a href="https://github.com/msimerson">msimerson</a> (<a href="https://github.com/haraka/haraka-notes/commits?author=msimerson">23</a>)|
| :---: |

<sub>this file is maintained by [.release](https://github.com/msimerson/.release)</sub>
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
76 changes: 40 additions & 36 deletions index.js
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "haraka-notes",
"version": "1.0.7",
"version": "1.1.0",
"description": "Haraka Notes",
"main": "index.js",
"files": [
Expand All @@ -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",
Expand Down
140 changes: 69 additions & 71 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})
Loading