Skip to content

Commit

Permalink
feat(generator): update to interface (return type)
Browse files Browse the repository at this point in the history
  • Loading branch information
lparolari committed Oct 15, 2020
1 parent fc84460 commit 24fda50
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 46 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
],
"dependencies": {
"@types/ramda": "^0.27.25",
"fp-ts": "^2.8.4",
"moment": "^2.29.1",
"ramda": "^0.27.1"
}
Expand Down
42 changes: 22 additions & 20 deletions src/__tests__/generator.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { sort } from "fp-ts/lib/Array";
import * as O from "fp-ts/lib/Option";
import moment, { Moment } from "moment";
import { forEach, KeyValuePair, map, zip } from "ramda";

import { ge, lt } from "../constraint";
import { generator } from "../generator";
import { addInterval, Interval } from "../interval";
import { flatten, takeUntil, changeTime } from "../util";
import { changeTime, flatten, ordMoment, takeUntil } from "../util";
import { mock, revive } from "./mock";

const interval: Interval = { amount: 30, unit: "minutes" };
Expand All @@ -20,34 +22,34 @@ describe("generator", () => {
const startFromD3Gen = () => generator(interval)([ge(d2), ge(d3)])(start);

it("generate a time slot", () => {
expect(alwaysGen().next().value).not.toBe([]);
expect(alwaysGen().next().value).not.toBe(O.none);
});

it("generate subsequent time slots", () => {
const gen = alwaysGen();
expect(gen.next().value).toEqual([start]);
expect(gen.next().value).toEqual([addInterval(interval)(start)]);
expect(gen.next().value).toEqual(O.some(start));
expect(gen.next().value).toEqual(O.some(addInterval(interval)(start)));
});

it("generate time slots wrt constraints (never)", () => {
const gen = neverGen();
expect(gen.next().value).toEqual([]);
expect(gen.next().value).toEqual([]);
expect(gen.next().value).toEqual(O.none);
expect(gen.next().value).toEqual(O.none);
});

it("generate time slots wrt constraints (>= d2)", () => {
const gen = startFromD2Gen();
expect(gen.next().value).toEqual([]);
expect(gen.next().value).toEqual([d2]);
expect(gen.next().value).not.toEqual([]);
expect(gen.next().value).toEqual(O.none);
expect(gen.next().value).toEqual(O.some(d2));
expect(gen.next().value).not.toEqual(O.none);
});

it("generate time slots wrt constraints (>= d2 && => d3)", () => {
const gen = startFromD3Gen();
expect(gen.next().value).toEqual([]);
expect(gen.next().value).toEqual([]);
expect(gen.next().value).toEqual([d3]);
expect(gen.next().value).not.toEqual([]);
expect(gen.next().value).toEqual(O.none);
expect(gen.next().value).toEqual(O.none);
expect(gen.next().value).toEqual(O.some(d3));
expect(gen.next().value).not.toEqual(O.none);
});
});

Expand All @@ -62,9 +64,9 @@ describe("generator with constraints", () => {
const start = moment("2020-10-17 07:00");
const gen = generator(interval)([notBefore8am])(start);

expect(gen.next().value).toEqual([]); // 7:00 am
expect(gen.next().value).toEqual([]); // 7:30 am
expect(gen.next().value).not.toEqual([]); // 8:00 am, now we can generate
expect(gen.next().value).toEqual(O.none); // 7:00 am
expect(gen.next().value).toEqual(O.none); // 7:30 am
expect(gen.next().value).not.toEqual(O.none); // 8:00 am, now we can generate
});

it("generate time slots wrt daily constraints", () => {
Expand All @@ -73,9 +75,9 @@ describe("generator with constraints", () => {

const gen = generator(interval)([notSathurday])(start);

expect(gen.next().value).toEqual([]); // 23:00 sathurday
expect(gen.next().value).toEqual([]); // 23:30 sathurday
expect(gen.next().value).not.toEqual([]); // 00:00 sunday, now we can generate
expect(gen.next().value).toEqual(O.none); // 23:00 sathurday
expect(gen.next().value).toEqual(O.none); // 23:30 sathurday
expect(gen.next().value).not.toEqual(O.none); // 00:00 sunday, now we can generate
});
});

Expand Down Expand Up @@ -105,7 +107,7 @@ describe("generator with complex constraints", () => {
notAtLaunch,
])(start);

const actualWeek = flatten(takeUntil(stop)(gen));
const actualWeek = sort(ordMoment)(flatten(takeUntil(stop)(gen)));
const expectedWeek = map(revive, mock.workingWeek);

forEach((x: KeyValuePair<Moment, Moment>) => {
Expand Down
9 changes: 5 additions & 4 deletions src/__tests__/slot.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { none, some } from "fp-ts/lib/Option";
import moment from "moment";

import { slot } from "../slot";
Expand All @@ -7,15 +8,15 @@ const start = d1;

describe("single time slot", () => {
it("create a time slot", () => {
expect(slot([])(start)).not.toEqual([]);
expect(slot([])(start)).not.toEqual(none);
});

it("create first value equal to start", () => {
expect(slot([])(start)).toEqual([start]);
expect(slot([])(start)).toEqual(some(start));
});

it("work also with constraints", () => {
expect(slot([() => true])(start)).toEqual([start]);
expect(slot([() => false])(start)).toEqual([]);
expect(slot([() => true])(start)).toEqual(some(start));
expect(slot([() => false])(start)).toEqual(none);
});
});
13 changes: 9 additions & 4 deletions src/__tests__/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { none } from "fp-ts/lib/Option";
import moment from "moment";

import { generator } from "../generator";
Expand All @@ -10,7 +11,7 @@ describe("generator", () => {
const d1 = moment();
const start = d1;

let gen: Generator<Slot, never, unknown>;
let gen: Generator<Slot, Slot, Slot>;

beforeEach(() => {
gen = generator(interval)([])(start);
Expand All @@ -28,6 +29,10 @@ describe("generator", () => {
expect(flatten(take(0)(gen))).toHaveLength(0);
expect(flatten(take(5)(gen))).toHaveLength(5);
});

it("flatten also nones", () => {
expect(flatten([none])).toHaveLength(0);
});
});

describe("takeUntil", () => {
Expand All @@ -38,9 +43,9 @@ describe("generator", () => {
});

it("take nothing if iterable is at the end", () => {
const gen: Generator<Slot, Slot, unknown> = (function* () {
yield [];
return [];
const gen: Generator<Slot, Slot, Slot> = (function* () {
yield none;
return none;
})();
expect(takeUntil(stop)(gen)).toHaveLength(0);
});
Expand Down
2 changes: 1 addition & 1 deletion src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { slot, Slot } from "./slot";

export const generator = (interval: Interval) => (
constraints: Constraint[],
) => (start: Moment): Generator<Slot, never, unknown> => {
) => (start: Moment): Generator<Slot, Slot, Slot> => {
return (function* () {
let newStart = start;

Expand Down
8 changes: 5 additions & 3 deletions src/slot.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { none, Option, some } from "fp-ts/lib/Option";
import { Moment } from "moment";

import { Constraint, validate } from "./constraint";

export type Slot = Moment[];
export type Info = Moment;
export type Slot = Option<Info>;

export const slot = (constraints: Constraint[]) => (start: Moment): Slot => {
const current = start;

if (!validate(constraints)(current)) {
return [];
return none;
}

return [current];
return some(current);
};
86 changes: 72 additions & 14 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,81 @@
import * as A from "fp-ts/lib/Array";
import { flow } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import { Ord } from "fp-ts/lib/Ord";
import moment, { Moment } from "moment";
import * as R from "ramda";

import { Slot } from "./slot";
import { Info, Slot } from "./slot";

const repeat = (n: number) => R.repeat(0, n);

export const take = (n: number) => (iterable: IterableIterator<Slot>): Slot[] =>
R.map(() => iterable.next().value, repeat(n));
export const take = (n: number) => (
iterable: Generator<Slot, Slot, Slot>,
): Slot[] => R.map(() => iterable.next().value, repeat(n));

export const flatten = R.flatten;
export const flatten: (ss: Slot[]) => Info[] = flow(
A.map(
O.fold(
() => [],
(x: Info) => [x],
),
),
A.flatten,
);

// takeUntil `m`, with `m` excluded (stops before)
export const takeUntil = (m: Moment) => (iterable: Iterable<Slot>): Slot[] => {
const times: Slot[] = [];
/**
* Take slots until `pred` is true.
*
* @param pred A predicate that returns true whether the search has to continue or not, according to given date.
* @param generator A generator for slots.
* @param acc A result accumulator for recursive calls.
* @returns Slots taken from generator in reverse order.
*/
const takeUntilAcc = (pred: (m: Moment) => boolean) => (
generator: Generator<Slot, Slot, Slot>,
) => (acc: Slot[]): Slot[] => {
const hasToContinueSearch = O.fold(
// We ignore next lint from code coverage because it will never be reached:
// the case of not having a value is covered in `if (hasValue ...)`.
/* istanbul ignore next */
() => false,
(ts: Moment) => pred(ts),
);
const hasValue = O.isSome;

const next = generator.next();
const isIteratorAtTheEnd = next.done;
const optionalSlot = next.value;

for (const tss of iterable) {
if (tss.length > 0) {
const [ts] = tss;
if (ts.isSameOrAfter(m)) return times;
times.push([ts]);
}
if (isIteratorAtTheEnd) {
// We have finished the sequence
return acc;
}

return times;
if (!hasValue(optionalSlot)) {
// We continue searching
return takeUntilAcc(pred)(generator)(acc);
}

if (!hasToContinueSearch(optionalSlot)) {
// We return values calculated until now
return acc;
}

// We add this slot and continue searching
return takeUntilAcc(pred)(generator)([optionalSlot, ...acc]);
};

/** Take slots until `pred` is true. */
export const takeUntilPred = (pred: (m: Moment) => boolean) => (
generator: Generator<Slot, Slot, Slot>,
): Slot[] => takeUntilAcc(pred)(generator)([]);

/** Take slots until reached `stop` date. */
export const takeUntil = (stop: Moment) => (
generator: Generator<Slot, Slot, Slot>,
): Slot[] => takeUntilPred((m: Moment) => m.isBefore(stop))(generator);

type Format = { date: string; datetime: string };

/**
Expand All @@ -49,3 +100,10 @@ export const changeTime = changeTimeWithFormat({
date: "YYYY-MM-DD",
datetime: "YYYY-MM-DD HH:mm:ss",
});

/* istanbul ignore next */
export const ordMoment: Ord<Moment> = {
equals: (x: Moment, y: Moment) => x.isSame(y),
compare: (x: Moment, y: Moment) =>
x.isBefore(y) ? -1 : x.isAfter(y) ? 1 : 0,
};
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3118,6 +3118,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"

fp-ts@^2.8.4:
version "2.8.4"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.8.4.tgz#d1af738a94de8591d441ef656153d04bd878edeb"
integrity sha512-J+kwce5SysU0YKuZ3aCnFk+dyezZD1mij6u26w1fCVfuLYgJR4eeXmVfJiUjthpZ+4yCRkRfcwMI5SkGw52oFA==

fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
Expand Down

0 comments on commit 24fda50

Please sign in to comment.