Skip to content

Commit

Permalink
feat: group(), multiline() and takeUntil()
Browse files Browse the repository at this point in the history
Tests for group() and takeUntil() will be added with the ferrum.doctest
introduction.
  • Loading branch information
koraa committed Dec 23, 2019
1 parent 01ee6d4 commit 0bc0ca0
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ module.exports = {
...require('./trait'),
...require('./stdtraits'),
...require('./sequence'),
...require('./string'),
};
78 changes: 75 additions & 3 deletions src/sequence.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ const assert = require('assert');
const { inspect } = require('util');
const { curry, pipe } = require('./functional');
const {
plus, or, mul, and,
plus, or, mul, and, is,
} = require('./op');
const { type } = require('./typesafe');
const { Trait } = require('./trait');
const {
size, Size, pairs, eq, empty, _typedArrays,
size, Size, pairs, eq, empty, _typedArrays, setdefault,
} = require('./stdtraits');

// ITERATOR GENERATORS ///////////////////////////////////////
Expand Down Expand Up @@ -1249,6 +1249,18 @@ const takeWhile = curry('takeWhile', function* takeWhile(seq, fn) {
}
});

/**
* Cut off the sequence at the first point where the given condition is met.
*
* `list(takeUntil([1,2,3,4,5,6...], x => x > 4))` yields `[1,2,3,4]`
*
* @function
* @param {Sequence} seq Any sequence for which iter() is defined
* @param {Function} fn The predicate function
* @returns {Iterator}
*/
const takeUntil = curry('takeUntil', (seq, fn) => takeWhile(seq, (v) => !fn(v)));

/**
* Cut of the sequence at the point where the given value is
* first encountered.
Expand All @@ -1257,7 +1269,7 @@ const takeWhile = curry('takeWhile', function* takeWhile(seq, fn) {
* @param {Sequence} seq Any sequence for which iter() is defined
* @returns {Iterator}
*/
const takeUntilVal = curry('takeUntilVal', (seq, val) => takeWhile(seq, (x) => x !== val));
const takeUntilVal = curry('takeUntilVal', (seq, val) => takeUntil(seq, is(val)));

/**
* Cut of the given sequence at the first undefined or null value.
Expand Down Expand Up @@ -1659,6 +1671,64 @@ const chunkifyWithFallback = curry('chunkifyWithFallback', (seq, len, fallback)
}),
));

/**
* Group the elements of the user defined sequence using a custom container.
*
* This will:
*
* - Calculate the key for every element in the given sequence by
* applying the key function
* - Create a bucket (array) for every key calculated
* - Insert each element into the bucket associated with
* it's calculated key in order
*
* ```js,test
* const { group, assertEquals } = require('ferrum');
*
* const seq = [
* { foo: 42, bar: 22 },
* { foo: 13, bar: 22 },
* { foo: 42, bar: 99 },
* ];
*
* // Group by `foo`
* assertEquals(
* group(seq, ({foo}) => foo),
* new Map([
* [42, [
* { foo: 42, bar: 22 }, // Note that the order in here is well defined
* { foo: 42, bar: 99 }]],
* [13, [
* { foo: 13, bar: 22 }]]
* ])
* );
*
* // Group by `bar`
* assertEquals(
* group(seq, ({bar}) => bar),
* new Map([
* [22, [
* { foo: 42, bar: 22 },
* { foo: 13, bar: 22 }]],
* [42, [
* { foo: 42, bar: 99 }]]
* ])
* );
* ```
*
* @function
* @sourcecode
* @param {Sequence} seq
* @param {Function} keyfn
* @returns {Map} The es6 map containing the keys.
*/
const group = curry('group', (seq, keyfn) => {
const cont = new Map();
each(seq, (elm) => setdefault(cont, keyfn(elm), []).push(elm));
return cont;
});


/**
* Calculate the cartesian product of the given sequences.
*
Expand Down Expand Up @@ -1831,6 +1901,7 @@ module.exports = {
take,
takeWithFallback,
takeWhile,
takeUntil,
takeUntilVal,
takeDef,
flat,
Expand All @@ -1851,6 +1922,7 @@ module.exports = {
chunkifyShort,
chunkify,
chunkifyWithFallback,
group,
cartesian,
cartesian2,
mod,
Expand Down
74 changes: 74 additions & 0 deletions src/string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2019 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

const { pipe } = require('./functional');
const { size } = require('./stdtraits');
const {
filter, map, count, foldl, join,
} = require('./sequence');

/**
* Helpers for working with strings.
*/

/**
* This is a helper for declaring multiline strings.
*
* ```
* const s = multiline(`
* Foo
* Bar
* Baz
*
* Hello
*
* Bang
* `);
* ```
*
* The function basically just takes a string and then
* strips the first & last lines if they are empty.
*
* In order to remove indentation, we determine the common
* whitespace prefix length (number of space 0x20 characters
* at the start of the line). This prefix is simply removed
* from each line...
*/
const multiline = (str) => {
// Discard the leading & trailing line
const lines = str.split('\n');

// Strip the first and the last line
if (lines[0].match(/^\s*$/)) {
lines.shift();
}
if (size(lines) > 0 && lines[size(lines) - 1].match(/^\s*$/)) {
lines.pop();
}

// Find the prefix length
const prefixLen = pipe(
lines,
filter((l) => !l.match(/^\s*$/)), // Disregarding empty lines
map((l) => l.match(/^ */)[0]), // Extract prefixes
map(count), // calculate length
foldl(Infinity, (a, b) => Math.min(a, b)),
); // minimum

return pipe(
lines,
map((l) => l.slice(prefixLen)), // discard prefixes
join('\n'),
);
};

module.exports = { multiline };
41 changes: 41 additions & 0 deletions test/string.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2019 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-env mocha */

const assert = require('assert');
const { multiline } = require('../src/index');

describe('String tests', () => {
it('multiline()', () => {
const ck = (ref, str) => assert.strictEqual(multiline(str), ref);

ck('', '');
ck('Hello', 'Hello');
ck('Hello', `
Hello`);
ck('Hello', `
Hello`);
ck('Hello\nWorld', `
Hello
World`);
ck('Hello\nWorld', `
Hello
World
`);
ck('Hello\n Foo\nWorld', `
Hello
Foo
World
`);
});
});

0 comments on commit 0bc0ca0

Please sign in to comment.