Skip to content

Commit

Permalink
Merge pull request #415 from smartprocure/MoreMethods
Browse files Browse the repository at this point in the history
More methods
  • Loading branch information
daedalus28 authored Apr 30, 2023
2 parents fe61380 + bcef9b4 commit 1c095fa
Show file tree
Hide file tree
Showing 15 changed files with 624 additions and 7 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# 1.75.0

- Add `renamePropertyOn`, `popProperty`
- Add `updateIfExists`, `updateIfExistsOn`, `updatePaths`, `updatePathsOn`, `updateAllPaths`, `updateAllPathsOn`
- Add `crunchWhitespace`
- Export `writeTreeNode`
- Add `chunkByValue`
- Pass indexes to `arrayToObject`
- Add `omitByIndexed` conversion
- Add `isSubset`
- Add `sizeBy`
- Add `matchesBy`, `matchesBySome`

# 1.74.2

- Make `intersperse` handle more edge cases (undefined, null, stirngs, objects, etc)
Expand Down
118 changes: 118 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ Example:
`(fn, collection) -> collection`
Maps `fn` over the input collection and compacts the result.

### sizeBy

`(fn, collection) -> number`
Returns the size of a collection after filtering by `fn`.

## Convert(\_In)

lodash/fp is great, but sometimes the curry order isn't exactly what you want.
Expand Down Expand Up @@ -289,6 +294,10 @@ Just like `_.reduce`, but with `{cap: false}` so iteratees are not capped (e.g.

Just like `_.pickBy`, but with `{cap: false}` so iteratees are not capped (e.g. indexes are passed).

### omitByIndexed

Just like `_.omitBy`, but with `{cap: false}` so iteratees are not capped (e.g. indexes are passed).

### mapValuesIndexed

Just like `_.mapValues`, but with `{cap: false}` so iteratees are not capped (e.g. indexes are passed).
Expand Down Expand Up @@ -336,6 +345,11 @@ Example:
[[0,7], [3,9], [11,15]] -> [[0,9], [11,15]]
```

### isSubset

`([a], [a]) -> boolean`
Determines if an array is a subset of another array.

### cycle

`[a, b...] -> a -> b`
Expand Down Expand Up @@ -387,6 +401,11 @@ Takes a predicate function and an array, and returns an array of arrays where ea

The predicate is called with two arguments: the current group, and the current element. If it returns truthy, the element is appended to the current group; otherwise, it's used as the first element in a new group.

### chunkByValue

`f -> [] -> [[], ...]`
`chunkBy` when the returned value of an iteratee changes

### toggleElementBy

`bool -> value -> list -> newList`
Expand Down Expand Up @@ -511,6 +530,26 @@ Example:
renameProperty('a', 'b', { a: 1 }) -> { b: 1 }
```

### renamePropertyOn

`sourcePropertyName -> targetPropertyName -> sourceObject -> sourceObject`
Rename a property on an object.
**NOTE**:Mutates the object

Example:

```jsx
renamePropertyOn('a', 'b', { a: 1 }) -> { b: 1 }
```

### popProperty

`k -> { k: v } -> v`
Removes a property from an object and returns the removed value.
Like `F.unsetOn`, but returns the removed value instead of the mutated object. Similar to .pop() on arrays, but for objects.
Supports nested properties using dot notation.
NOTE: Mutates the object. If you don't want mutation, you probably want `_.unset` for the object or `_.get` for the value.

### unwind

`k -> { k: [a, b] } -> [{ k: a }, { k: b }]`
Expand Down Expand Up @@ -727,6 +766,75 @@ Takes two objects and returns the keys they have in common
`(x, y) -> key`
Takes two objects and returns the first key in `y` that x also has
### updateIfExists
`(path, updater, object) -> object`
Like `_.update`, but does not call the iteratee if the path is missing on the object
### updateIfExistsOn
`(path, updater, object) -> object`
Like `F.updateOn`, but does not call the iteratee if the path is missing on the object
_Mutates_ the object
### updatePathsOn
`({ path: transform }, target) -> obj`
Similar to ramda's `R.evolve`, but supports lodash iteratees and nested paths.
Applies transforms to the target object at each path. The transform function is called with the value at that path, and the result is set at that path.
Transforms are **not** called for paths that do not exist in the target object.
Transform functions support lodash iteratee shorthand syntax.
Deep paths are supported by nesting objects and by dotted the keys
Note: _Mutates_ the target object for performance. If you don't want this, use `updatePaths` or clone first.
### updateAllPathsOn
`({ path: transform }, target) -> obj`
Similar to ramda's `R.evolve`, but supports lodash iteratees and nested paths.
Applies transforms to the target object at each path. The transform function is called with the value at that path, and the result is set at that path.
Transforms **are** called for paths that do not exist in the target object.
Transform functions support lodash iteratee shorthand syntax.
Deep paths are supported by nesting objects and by dotted the keys
Note: _Mutates_ the target object for performance. If you don't want this, use `updateAllPaths` or clone first.
### updateAllPaths
`({ path: transform }, target) -> obj`
Similar to ramda's `R.evolve`, but supports lodash iteratees and nested paths.
Applies transforms to the target object at each path. The transform function is called with the value at that path, and the result is set at that path.
Transforms **are** called for paths that do not exist in the target object.
Transform functions support lodash iteratee shorthand syntax.
Deep paths are supported by nesting objects and by dotted the keys
_Note_ Deep clones prior to executing to avoid mutating the target object, but mutates under the hood for performance (while keeping it immutable at the surface). If you're doing this in a place where mutating is safe, you might want `F.updateAllPathsOn` to avoid the `_.deepClone`
### updatePaths
`({ path: transform }, target) -> obj`
Similar to ramda's `R.evolve`, but supports lodash iteratees and nested paths.
Applies transforms to the target object at each path. The transform function is called with the value at that path, and the result is set at that path.
Transforms are **not** called for paths that do not exist in the target object.
Transform functions support lodash iteratee shorthand syntax.
Deep paths are supported by nesting objects and by dotted the keys
_Note_ Deep clones prior to executing to avoid mutating the target object, but mutates under the hood for performance (while keeping it immutable at the surface). If you're doing this in a place where mutating is safe, you might want `F.updatePathsOn` to avoid the `_.deepClone`
### matchesBy
`(criteria: object, object: object) -> boolean`
Takes a criteria object and an object to test against it, and returns true if all the values in the criteria match the values in the object
Criteria values can be functions or values to compare against
Supports dot notation for deep paths
### matchesBySome
`(criteria: object, object: object) -> boolean`
Takes a criteria object and an object to test against it, and returns true if some of the values in the criteria match the values in the object
Criteria values can be functions or values to compare against
Supports dot notation for deep paths
## String
### wrap
Expand Down Expand Up @@ -814,6 +922,11 @@ dedupe.cache //-> {}
dedupe('foo') //-> 'foo'
```
### crunchWhitespace
`string -> string`
Replaces whitespace substrings with a single space and trims leading/trailing whitespace
## Regex
### testRegex
Expand Down Expand Up @@ -1207,6 +1320,11 @@ Structure preserving pre-order depth first traversal which clones, mutates, and
`traverse -> (accumulator, initialValue, tree) -> x`
Just like `_.reduce`, but traverses over the tree with the traversal function in `pre-order`.
### writeTreeNode
Default `writeNode` for `mapTree`. It writes the node to the parent at the given index.
Using the traversal function with tree iteratee properties to find children.
### mapTree
`(traverse, writeNode) -> f -> tree -> newTree`
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "futil-js",
"version": "1.74.2",
"version": "1.75.0",
"description": "F(unctional) util(ities). Resistance is futile.",
"main": "lib/futil-js.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions scripts/generate-docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ let getDocs = async () => {
let jsDocToJson = _.flow(
_.reject('undocumented'),
_.reject({ kind: 'module' }),
_.reject({ access: 'private' }),
_.filter('name'),
_.map(async (x) => {
let path = `${x.meta.path}/${x.meta.filename}`
Expand Down
31 changes: 28 additions & 3 deletions src/array.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'lodash/fp'
import { callOrReturn } from './function'
import { insertAtIndex } from './collection'
import { reduceIndexed } from './conversion'
import { reduceIndexed, mapIndexed } from './conversion'

// TODO: Move to proper files and expose
let callUnless = (check) => (failFn) => (fn) => (x, y) =>
Expand All @@ -16,13 +16,15 @@ let last = _.takeRight(1)
* Joins an array after compacting. Note that due to the underlying behavior of `_.curry` no default `join` value is supported -- you must pass in some string with which to perform the join.
*
* @signature joinString -> [string1, string2, ...stringN] -> string1 + joinString + string2 + joinString ... + stringN
* @typescript {(join: string, x: any[]) => string}
*/
export let compactJoin = _.curry((join, x) => _.compact(x).join(join))

/**
* Compacts and joins an array with `.`
*
* @signature [string1, string2, ...stringN] -> string1 + '.' + string2 + '.' ... + stringN
* @typescript {(arr: any[]) => string}
*/
export let dotJoin = compactJoin('.')

Expand All @@ -31,7 +33,7 @@ export let dotJoin = compactJoin('.')
*
* @signature filterFunction -> [string1, string2, ...stringN] -> string1 + '.' + string2 + '.' ... + stringN
*/
export let dotJoinWith = (fn) => (x) => _.filter(fn, x).join('.')
export let dotJoinWith = (fn) => (x) => _.flow(_.filter(fn), _.join('.'))(x)

/**
* Returns an array of elements that are repeated in the array.
Expand Down Expand Up @@ -89,6 +91,15 @@ export let mergeRanges = _.flow(
)
)

/**
* Determines if an array is a subset of another array.
*
* @signature ([a], [a]) -> boolean
*/
export let isSubset = _.curry(
(array1, array2) => _.difference(array1, array2).length === 0
)

/**
* Creates a function that takes an element of the original array as argument and returns the next element in the array (with wrapping). Note that (1) This will return the first element of the array for any argument not in the array and (2) due to the behavior of `_.curry` the created function will return a function equivalent to itself if called with no argument.
*
Expand All @@ -102,13 +113,14 @@ export let cycle = _.curry((a, n) => a[(a.indexOf(n) + 1) % a.length])
* @signature (k, v, [a]) -> { k(a): v(a) }
*/
export let arrayToObject = _.curry((k, v, a) =>
_.flow(_.keyBy(k), _.mapValues(v))(a)
_.zipObject(mapIndexed(k, a), mapIndexed(v, a))
)

/**
* Converts and array of keys to an object using a predicate
*
* @signature (v, [a]) => { a: v(a) }
* @typescript <T>(fn: (k: string) => T, keys: string[]): { [K in typeof keys[number]]: T } // TS not enforcing the keys :(
*/
export let keysToObject = arrayToObject((x) => x)

Expand Down Expand Up @@ -179,6 +191,19 @@ export let chunkBy = _.curry((f, array) =>
)
)

/**
* `chunkBy` when the returned value of an iteratee changes
*
* @signature f -> [] -> [[], ...]
* @since 1.75.0
*/
export let chunkByValue = _.curry((f, array) =>
chunkBy(
(group, fn) => _.isEqual(_.iteratee(f)(_.last(group)), _.iteratee(f)(fn)),
array
)
)

/**
* Just like toggleElement, but takes an iteratee to determine if it should remove or add. This is useful for example in situations where you might have a checkbox that you want to represent membership of a value in a set instead of an implicit toggle. Used by includeLens.
*
Expand Down
9 changes: 9 additions & 0 deletions src/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,12 @@ export let insertAtIndex = _.curry((index, val, collection) =>
export let compactMap = _.curry((fn, collection) =>
_.flow(_.map(fn), _.compact)(collection)
)

/**
* Returns the size of a collection after filtering by `fn`.
*
* @signature (fn, collection) -> number
*/
export const sizeBy = _.curry((fn, collection) =>
_.flow(_.filter(fn), _.size)(collection)
)
7 changes: 7 additions & 0 deletions src/conversion.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ export const reduceIndexed = noCap.reduce
*/
export const pickByIndexed = noCap.pickBy

/**
* Just like `_.omitBy`, but with `{cap: false}` so iteratees are not capped (e.g. indexes are passed).
*
* @tags convert(_Indexed)
*/
export const omitByIndexed = noCap.omitBy

/**
* Just like `_.mapValues`, but with `{cap: false}` so iteratees are not capped (e.g. indexes are passed).
*
Expand Down
Loading

0 comments on commit 1c095fa

Please sign in to comment.