flooent
Fluent interface to provide an expressive syntax for common manipulations. Rather than enforcing a different paradigm, flooent builds upon and extends the native capabilities of various JavaScript objects.
Given you have logical, procedural, "hard to visualize" code like this:
// given we have const path = 'App/Controllers/user.js'
const filename = path.substring(path.lastIndexOf('/') + 1)
let name = filename.substring(0, filename.lastIndexOf('.'))
if (!name.endsWith('Controller')) name+= 'Controller'
return name.substring(0, 1).toUpperCase() + name.substring(1)
refactor it into plain English
// given we have const path = 'App/Controllers/user.js'
given.string(path)
.afterLast('/')
.beforeLast('.')
.endWith('Controller')
.capitalize()
.valueOf()
Index
Get Started
Migration from Version 1 to Version 2
Documentation and Code for Version 1
Installation
npm install flooent
given
Use given
to create either a flooent Number, Array, Map or String.
import { given } from 'flooent'
given.string('hello') // instance of Stringable
given.array([1, 2]) // instance of Arrayable
given.number(1) // instance of Numberable
given.map({ key: 'value' }) // or given.map([['key', 'value']]), or given.map(new Map(...)) | instance of Mappable
given.any('anything') // helper class with useful methods for any data type
Flooent objects only extend the native functionality, so you can still execute any native method like given.string('hello').includes('h')
.
To turn flooent objects back into their respective primitive form, use the valueOf()
method.
given.string('hello').valueOf()
When newing up a flooent object, you can also provide a callback as the second argument which will automatically turn the object back into its primitive form.
const shuffledNumbersRaw = given.array([1, 2, 3, 4], numbers => {
return numbers.shuffle()
})
Best Practices
After performing your data manipulations, if you need to use this data further, turn it back into its primitive form (see above) instead of passing it as an argument to another function or returning it.
This is to avoid cases such as flooent having a method (e.g. array.at) that later gets added to native JavaScript with different behaviour. That other function is not expecting a flooent object (specifically a third-party lib) and could make use of the at
method.
Constraints
The contraints that apply to flooent strings and numbers are the same that apply to when you new up a native string/number using new (new String('')
) and is just how JavaScript works.
For one, the type will be object
instead of string
.
typeof given.string('') // object
typeof '' // string
Flooent strings and numbers are immutable. You can still do things like this:
given.string('?') + '!' // '?!'
given.number(1) + 1 // 2
which will return a primitive (not an instance of flooent).
However you can not mutate flooent objects like this:
given.string('') += '!' // ERROR
given.number(1) += 1 // ERROR
There are various fluent alternatives available.
Functional API
If you only need to do a single thing, you can also import most functions individually. The result of these functions will not be turned into a flooent object.
import { afterLast } from 'flooent/string'
afterLast('www.example.com', '.') // 'com'
import { move } from 'flooent/array'
move(['music', 'tech', 'sports'], 0, 'after', 1) // ['tech', 'music', 'sports']
import { times } from 'flooent/number'
times(3, i => i) // [0, 1, 2]
import { rename } from 'flooent/map'
rename(new Map([['item_id', 1]]), 'item_id', 'itemId') // Map { itemId → 1 }
In addition, there is an experimental API for a pipable API:
import { pipe, afterLast, beforeLast, endWith, capitalize } from 'flooent/fp/string'
const path = 'App/Controllers/user.js'
pipe(path, afterLast('/'), beforeLast('.'), endWith('Controller'), capitalize) // UserController
Note: flooent/fp/string
, flooent/fp/map
, flooent/fp/number
, and flooent/fp/array
all return the same function pipe
.
Strings
You have access to everything from the native String object.
pipe
Executes the callback and transforms the result back into a flooent string if it is a string.
given.string('').pipe(str => str.append('!')) // String { '!' }
is
Compares the given value with the raw string.
given.string('flooent').is('flooent') // true
includedIn
Checks if the string is included in the given array.
given.string('flooent').includedIn(['flooent', 'string'])
Fluent methods
after
Returns the remaining text after the first occurrence of the given value. If the value does not exist in the string, the entire string is returned unchanged.
given.string('sub.domain.com').after('.') // String { 'domain.com' }
afterLast
Returns the remaining text after the last occurrence of the given value. If the value does not exist in the string, the entire string is returned unchanged.
given.string('sub.domain.com').afterLast('.') // String { 'com' }
before
Returns the text before the first occurrence of the given value. If the value does not exist in the string, the entire string is returned unchanged.
given.string('sub.domain.com').before('.') // String { 'sub' }
beforeLast
Returns the text before the last occurrence of the given value. If the value does not exist in the string, the entire string is returned unchanged.
given.string('sub.domain.com').beforeLast('.') // String { 'sub.domain' }
append
Alias for concat
. Appends the given value to string.
given.string('hello').append(' world') // String { 'hello world' }
prepend
Prepends the given value to string.
given.string('world').prepend('hello ') // String { 'hello world' }
endWith
Appends the given value only if string doesn't already end with it.
given.string('hello').endWith(' world') // String { 'hello world' }
given.string('hello world').endWith(' world') // String { 'hello world' }
startWith
Prepends the given value only if string doesn't already start with it.
given.string('world').startWith('hello ') // String { 'hello world' }
given.string('hello world').startWith('hello ') // String { 'hello world' }
limit
Truncates text to given length and appends second argument if string got truncated.
given.string('The quick brown fox jumps over the lazy dog').limit(9) // The quick...
given.string('The quick brown fox jumps over the lazy dog').limit(9, ' (Read more)') // The quick (Read more)
given.string('Hello').limit(10) // Hello
tap
Tap into the chain without modifying the string.
given.string('')
.append('!')
.tap(str => console.log(str))
.append('!')
// ...
when
Executes the callback if first given value evaluates to true. Result will get transformed back into a flooent string if it is a raw string.
// can be a boolean
given.string('').when(true, str => str.append('!')) // String { '!' }
given.string('').when(false, str => str.append('!')) // String { '' }
// or a method
given.string('hello').when(str => str.endsWith('hello'), str => str.append(' world')) // String { 'hello world' }
given.string('hi').when(str => str.endsWith('hello'), str => str.append(' world')) // String { 'hello' }
whenEmpty
Executes the callback if string is empty. Result will get transformed back into a flooent string if it is a raw string.
given.string('').whenEmpty(str => str.append('!')) // String { '!' }
given.string('hello').whenEmpty(str => str.append('!')) // String { 'hello' }
wrap
Wraps a string with the given value.
given.string('others').wrap('***') // String { '***others***' }
given.string('oldschool').wrap('<blink>', '</blink>') // String { '<blink>oldschool</blink>' }
unwrap
Unwraps a string with the given value.
given.string('***others***').unwrap('***') // String { 'others' }
given.string('<blink>oldschool</blink>').unwrap('<blink>', '</blink>') // String { 'oldschool' }
camel
Turns the string into camel case.
given('foo bar').camel() // String { 'fooBar' }
title
Turns the string into title case.
given.string('foo bar').title() // String { 'Foo Bar' }
studly
Turns the string into studly case.
given('foo bar').studly() // String { 'FooBar' }
capitalize
Capitalizes the first character.
given.string('foo bar').capitalize() // String { 'Foo bar' }
kebab
Turns the string into kebab case.
given('foo bar').kebab() // String { 'foo-bar' }
snake
Turns the string into snake case.
given('foo bar').snake() // String { 'foo_bar' }
slug
Turns the string into URI conform slug.
given.string('Foo Bar ♥').slug() // String { 'foo-bar' }
given.string('foo bär').slug('+') // String { 'foo+bar' }
parse
Parses a string back into its original form.
given.string('true').parse() // true
given.string('23').parse() // 23
given.string('{\"a\":1}').parse() // { a: 1 }
Arrays
You have access to everything from the native Array object.
pipe
Executes callback and transforms result back into a flooent array if the result is an array.
const someMethodToBePipedThrough = array => array.append(1)
given.array([]).pipe(someMethodToBePipedThrough) // [1]
mutate
Mutates the original array with the return value of the given callback. This is an escape hatch for when you need it and usually not recommended.
const numbers = given.array(1, 2, 3)
numbers.mutate(n => n.append(4)) // [1, 2, 3, 4]
numbers // [1, 2, 3, 4]
sum
Returns the sum of the array.
given.array([2, 2, 1]).sum() // 5
See usage for arrays of objects.
when
Executes callback if first given value evaluates to true. Result will get transformed back into a flooent array if it is an array.
// can be a boolean
given.array([]).when(true, str => str.append(1)) // [1]
given.array([]).when(false, str => str.append(1)) // []
// or a method
given.array([]).when(array => array.length === 0), array => array.append('called!')) // ['called']
given.array([]).when(array => array.length === 1, array => array.append('called!')) // []
isEmpty
Returns a boolean whether the array is empty or not.
given.array([]).isEmpty() // true
given.array([1]).isEmpty() // false
toMap
Turns an array in the structure of [ ['key', 'value'] ]
into a flooent map.
given.map({ key: 'value' }).entries().toMap()
Fluent methods
where
Filters array by given key / value pair.
const numbers = [1, 1, 2, 3]
given.array(numbers).where(1) // [1, 1]
See usage for arrays of objects.
whereIn
Filters array by given values.
const numbers = [1, 1, 2, 3]
given.array(numbers).whereIn([1, 3]) // [1, 1, 3]
See usage for arrays of objects.
whereNot
Removes given value from array.
const numbers = [1, 1, 2, 3]
given.array(numbers).whereNot(1) // [2, 3]
See usage for arrays of objects.
whereNotIn
Removes given values from array.
const numbers = [1, 1, 2, 3]
given.array(numbers).whereNotIn([2, 3]) // [1, 1]
See usage for arrays of objects.
first
Returns the first (x) element(s) in the array or undefined.
given.array([1, 2, 3]).first() // 1
given.array([1, 2, 3]).first(2) // [1, 2]
second
Returns the second element in the array or undefined.
given.array([1, 2, 3]).second() // 2
last
Returns last (x) element(s) in array or undefined.
given.array([1, 2, 3]).last() // 3
given.array([1, 2, 3]).last(2) // [2, 3]
Alternatively, pass in a callback to get the last item that passes the given truth test (inverse of find
).
given.array([1, 2, 3]).last(item => item > 1) // 3
nth
Returns element at given index or undefined. If given value is negative, it searches from behind.
given.array(['a', 'b', 'c']).nth(1) // 'b'
given.array(['a', 'b', 'c']).nth(5) // undefined
given.array(['a', 'b', 'c']).nth(-1) // 'c'
reject
Return all items that don't pass the given truth test. Inverse of Array.filter
.
given.array([{ id: 1, disabled: true }]).reject(item => item.disabled) // []
until
Returns the items until either the given value is found, or the given callback returns true
.
given.array(['a', 'b', 'c']).until('c') // ['a', 'b']
given.array(['a', 'b', 'c']).until(item => item === 'c') // ['a', 'b']
shuffle
Shuffles the array.
given.array([1, 2, 3]).shuffle() // ?, maybe: [1, 3, 2]
unique
Returns array of unique values.
given.array([1, 1, 2]).unique() // [1, 2]
See usage for arrays of objects.
chunk
Breaks the array into multiple, smaller arrays of a given size.
given.array([1, 2, 3, 4, 5]).chunk(3) // [[1, 2, 3], [4, 5]]
forPage
Returns the items for the given page and size.
given.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']).forPage(1, 3) // ['a', 'b', 'c']
given.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']).forPage(2, 3) // ['d', 'e', 'f']
given.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']).forPage(3, 3) // ['g']
given.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']).forPage(4, 3) // []
pad
Fills up the array with the given value.
given.array([1, 2, 3]).pad(5, 0) // [1, 2, 3, 0, 0]
filled
Only returns items which are not empty.
given.array([0, '', null, undefined, 1, 2]).filled() // [1, 2]
See usage for arrays of objects.
partition
Returns a tuple separating the items that pass the given truth test.
const users = given.array([{ id: 1, active: false }, { id: 2, active: false }, { id: 3, active: true }])
const [activeUsers, inactiveUsers] = users.partition(user => user.active)
prepend
Prepends the given items to the array. Unlike unshift
, it is immutable and returns a new array.
const numbers = given.array([2, 3])
numbers.prepend(0, 1) // [0, 1, 2, 3]
To prepend items at a specific index, check out the Pointer API.
append
Appends the given items to the array. Unlike push
, it is immutable and returns a new array.
const numbers = given.array([0, 1])
numbers.append(2, 3) // [0, 1, 2, 3]
To append items at a specific index, check out the Pointer API.
sortAsc / sortDesc
Sorts an array in their respective order and returns a new array.
given.array([3, 1, 2]).sortAsc() // [1, 2, 3]
given.array([3, 1, 2]).sortDesc() // [3, 2, 1]
See usage for arrays of objects.
tap
Tap into the chain without modifying the array.
given.array([])
.append(1)
.tap(array => console.log(array))
.append(2)
// ...
Pointer API
Points to a specific index inside the array to do further actions on it.
given.array(['music', 'video', 'tech']).point(1) // returns pointer pointing to 'video'
given.array(['music', 'video', 'tech']).point(-1) // returns pointer pointing to 'tech'
given.array(['music', 'video', 'tech']).point(item => item === 'music') // returns pointer pointing to 'music'
append
Appends given value to array in between the currently pointed item and its next item and returns a new array.
given.array(['music', 'tech']).point(0).append('video') // ['music', 'video', 'tech']
prepend
Prepends given value to array in between the currently pointed item and its previous item and returns a new array.
given.array(['music', 'tech']).point(1).prepend('video') // ['music', 'video', 'tech']
set
Sets the value at the current index and returns a new array.
given.array(['music', 'tec']).point(1).set(item => item + 'h') // ['music', 'tech']
remove
Removes the current index and returns a new array.
given.array(['music', 'tech']).point(1).remove() // ['music']
split
Splits the array at the current index
given.array(['a', 'is', 'c']).point(1).split() // [['a'], ['c']]
value
Returns the value for current pointer position.
given.array(['music', 'tech']).point(1).value() // ['music', 'tech']
step
Steps forward or backwards given the number of steps.
given.array(['music', 'tec']).point(1).step(-1).value() // ['music']
move
Moves an item in the array using the given source index to either "before" or "after" the given target.
given.array(['b', 'a', 'c']).move(0, 'after', 1) // ['a', 'b', 'c']
given.array(['b', 'a', 'c']).move(0, 'before', 2) // ['a', 'b', 'c']
given.array(['b', 'a', 'c']).move(1, 'before', 0) // ['a', 'b', 'c']
Instead of the index, you can also specify "first" or "last":
given.array(['c', 'a', 'b']).move('first', 'after', 'last') // ['a', 'b', 'c']
given.array(['b', 'c', 'a']).move('last', 'before', 'first') // ['a', 'b', 'c']
Methods for arrays of objects
sum
Returns the sum of the given field/result of callback in the array.
const users = [{ id: 1, points: 10 }, { id: 2, points: 10 }, { id: 3, points: 10 }]
given.array(users).sum('points') // 30
given.array(users).sum(user => user.points * 10) // 300
sortAsc / sortDesc
Sorts an array in their respective order and returns a new array.
const numbers = [{ val: 3 }, { val: 1 }, { val: 2 }]
given.array(numbers).sortAsc('val') // [{ val: 1 }, { val: 2 }, { val: 3 }]
given.array(numbers).sortDesc('val') // [{ val: 3 }, { val: 2 }, { val: 1 }]
Also works by passing the index (useful when working with array entries).
given.array([[0], [2], [1]]).sortAsc(0)) // [[0], [1], [2]])
Alternatively, pass in a map function of which its result will become the key instead.
const numbers = [{ val: 3 }, { val: 1 }, { val: 2 }]
given.array(numbers).sortAsc(item => item.val) // [{ val: 1 }, { val: 2 }, { val: 3 }]
given.array(numbers).sortDesc(item => item.val) // [{ val: 3 }, { val: 2 }, { val: 1 }]
pluck
Pluck the given field out of each object in the array.
const cities = [
{ id: 1, name: 'Munich' },
{ id: 2, name: 'Naha' },
]
given.array(cities).pluck('name') // ['Munich', 'Naha']
where
Filters array by given key / value pair.
const cities = [
{ id: 1, name: 'Munich' },
{ id: 2, name: 'Naha' },
{ id: 3, name: 'Naha' },
]
given.array(cities).where('name', 'Munich') // [{ id: 1, name: 'Munich' }]
whereNot
Removes items from array by the given key / value pair.
const cities = [
{ id: 1, name: 'Munich' },
{ id: 2, name: 'Naha' },
{ id: 3, name: 'Naha' },
]
given.array(cities).whereNot('name', 'Naha') // [{ id: 1, name: 'Munich' }]
whereIn
Filters array by given key and values.
const cities = [
{ id: 1, name: 'Munich' },
{ id: 2, name: 'Naha' },
{ id: 3, name: 'Yoron' },
]
given.array(cities).whereIn('name', ['Munich', 'Yoron']) // [{ id: 1, name: 'Munich' }, { id: 3, name: 'Yoron' }]
whereNotIn
Removes items from array by the given key and values.
const cities = [
{ id: 1, name: 'Munich' },
{ id: 2, name: 'Naha' },
{ id: 3, name: 'Yoron' },
]
given.array(cities).whereNotIn('name', ['Naha', 'Yoron']) // [{ id: 1, name: 'Munich' }]
omit
Omits given keys from all objects in the array.
const people = [
{ id: 1, age: 24, initials: 'mz' },
{ id: 2, age: 2, initials: 'lz' }
]
given.array(people).omit(['initials', 'age']) // [ { id: 1 }, { id: 2 } ])
unique
Returns array of unique values comparing the given key.
const items = [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }, { id: 1, name: 'music' }]
given.array(items).unique('id') // [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }]
Alternatively, pass in a function of which its result will become the key instead.
const items = [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }, { id: 3, name: 'MUSIC' }]
given.array(items).unique(item => item.name.toLowerCase()) // [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }]
filled
Only returns items which are not empty.
const items = [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }, { id: 3, name: '' }]
given.array(items).filled('name') // [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }]
groupBy
Groups an array by the given key and returns a flooent map.
const items = [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }, { id: 3, name: 'music' }]
given.array(items).groupBy('name') // result is:
/*
{
music: [{ id: 1, name: 'music' }, { id: 3, name: 'music' }],
movie: [{ id: 2, name: 'movie' }]
}
*/
Alternatively, pass in a function of which its result will become the key instead.
const items = [{ id: 1, name: 'Music' }, { id: 2, name: 'movie' }, { id: 3, name: 'music' }]
given.array(items).groupBy(item => item.name.toUpperCase()) // result is:
/*
{
MUSIC: [{ id: 1, name: 'music' }, { id: 3, name: 'music' }],
MOVIE: [{ id: 2, name: 'movie' }]
}
*/
keyBy
Keys the collection by the given key and returns a flooent map. If multiple items have the same key, only the last one will appear in the new collection.
const items = [{ id: 1, name: 'music' }, { id: 2, name: 'movie' }, { id: 3, name: 'music' }]
given.array(items).keyBy('name') // result is:
/*
{
music: { id: 3, name: 'music' },
movie: { id: 2, name: 'movie' }
}
*/
toKeyedMap
Turns the given array into a flooent map with each element becoming a key in the map.
const genres = ['music', 'tech', 'games']
const map = given.array(genres).toKeyedMap(null) // result is:
/*
{
music: null,
tech: null,
games: null
}
*/
Alternatively, pass in a callback to specify the default value for each item individually:
const genres = ['music', 'tech', 'games']
const map = given.array(genres).toKeyedMap(genre => genre.toUpperCase()) // result is:
/*
{
music: 'MUSIC',
tech: 'TECH',
games: 'GAMES'
}
*/
Maps
You have access to everything from the native Map object.
The native methods keys()
, entries()
and values()
will return an instance of flooent Array instead of a native Array.
For nested data structures, only the first layer gets transformed into a map
toObject/toJSON
Turns the map into an object.
const map = given.map({ key: 'value' }) // Map { key → "value" }
map.toObject() // { key: 'value' }
map.toJSON() // { key: 'value' }
pull
Returns the value for the given key and deletes the key value pair from the map (mutation).
const map = given.map({ key: 'value' })
map.pull('key') // 'value'
map.has('key') // false
mapKeys
Iterates the entries through the given callback and assigns each result as the key.
const map = given.map({ a: 1 }).mapKeys((value, key, index) => key + value)
map.get('a1') // 1
mapValues
Iterates the entries through the given callback and assigns each result as the value.
const map = given.map({ a: '1' }).mapValues((value, key, index) => key + value)
map.get('a') // a1
only
Returns a new map with only the given keys.
given.map({ one: 1, two: 2, three: 3 }).only(['one', 'two']) // Map { "one" → 1, "two" → 2 }
except
Inverse of only
. Returns a new map with all keys except for the given keys.
given.map({ one: 1, two: 2, three: 3 }).except(['one', 'two']) // Map { "three" → 3 }
arrange
Rearranges the map to the given keys. Any unmentioned keys will be appended to the end.
given.map({ strings: 2, numbers: 1, functions: 4 })
.arrange('numbers', 'functions')
.keys() // ['numbers', 'functions', 'strings']
rename
Renames the given key with the new key if found, keeping the original insertion order.
given.map({ one: 1, to: 2, three: 3 })
.rename('to', 'two')
.keys() // ['one', 'two', 'three']
Numbers
You have access to everything from the native Number object.
times
Executes the callback for number of base values' times and returns a flooent array with the result of each iteration.
given.number(3).times(i => i) // [0, 1, 2]
pad
Fills up the number with zeroes.
given.number(40).pad(4) // '0040'
ordinal
Returns the number with its ordinal suffix. Only supports English.
given.number(1).ordinal() // '1st'
given.number(9).ordinal() // '9th'
isBetween / isBetweenOr
Checks if the number is between two given numbers. isBetweenOr
is inclusive, while isBetween
is exclusive.
given.number(5).isBetween(1, 10) // true
given.number(5).isBetween(5, 10) // false
given.number(5).isBetweenOr(5, 10) // true
Fluent methods
Working with percentages
given.number(40).percent().of(750) // Number { 300 }
given.number(300).of(750).inPercent() // Number { 40 }
round
Rounds down until .4 and up from .5.
given.number(10.4).round() // Number { 10 }
given.number(10.5).round() // Number { 11 }
ceil
Always rounds its value up to the next largest whole number or integer.
given.number(10.2).ceil() // Number { 11 }
floor
Always rounds its value down.
given.number(10.9).floor() // Number { 10 }
Any
A generic helper class for any kind of data types.
do
Executes and returns the result of a callback.
This is useful for grouping common logic together and avoiding temporary variables.
Before
const user = User.first() // variable "user" is only used here
const nameMatches = expect(user.name).toBe('test name')
After
const nameMatches = given.any(User.first()).do(user => {
return expect(user.name).toBe('test name')
})
Macros (extending flooent)
Extending flooent methods is easy as pie thanks to macro
.
import { given } from 'flooent'
given.string.macro('scream', function() {
return this.toUpperCase()
})
given.string('hello').scream() // String { 'HELLO' }
Define macros at a central place before your business logic. E.g. entry point or service provider
TypeScript
For TypeScript support, you need to additionally declare the module.
declare module 'flooent' {
interface Stringable {
scream(): Stringable;
}
}
More examples
These methods, while convenient, are not in the core since they are not all too common yet quadruply the bundle size among other reasons.
Array.is
Deep compares an array with the given callback.import { given } from 'flooent'
import isequal from 'lodash.isequal' // npm install lodash.isequal
given.array.macro('is', function(compareWith) {
return isequal(this, compareWith)
})
Then, use it like this:
const users = [{ id: 1 }]
given.array(users).is([{ id: 1 }]) // true
Array.clone
Deep clone an array and map.import { given } from 'flooent'
import clonedeep from 'lodash.clonedeep' // npm install lodash.clonedeep
given.array.macro('clone', function() {
// lodash does array.constructor(length) which doesn't work on subclassed arrays
const clone = clonedeep([...this])
return this.constructor.from(clone)
})
given.map.macro('clone', function() {
return this.entries().clone().toMap()
})
Then, use it like this:
given.array([['key', 'value']]).clone()
given.map([['key', 'value']]).clone()
String.plural & String.singular
Turns string into plural/singular form.import { given } from 'flooent'
import pluralize from 'pluralize' // npm install pluralize
given.string.macro('plural', function(count) {
const plural = pluralize(this, count, false)
return new this.constructor(plural) // new up again because pluralize returns raw string.
})
given.string.macro('singular', function() {
return new this.constructor(pluralize.singular(this))
})
Then, use it like this:
given.string('child').plural() // String { 'children' }
given.string('child').plural(3) // String { 'children' }
given.string('child').plural(1) // String { 'child' }
given.string('children').singular() // String { 'child' }
given.string('child').singular() // String { 'child' }
Future considerations
- Drop CJS once ES modules are widely supported in Node. ES modules are much lighter.