Skip to content

Commit

Permalink
Change collection helper callback arguments #2
Browse files Browse the repository at this point in the history
  • Loading branch information
tsuyoshiwada committed Apr 13, 2017
1 parent e59b906 commit d6b9b38
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 67 deletions.
95 changes: 60 additions & 35 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
type DotKey = string | number;
type DotKeys = DotKey[];
type Token = string;
type Tokens = Token[];


/**
* Utilities
*/
Expand Down Expand Up @@ -120,42 +122,76 @@ const tokenize = (str: string): Tokens => {
/**
* Getter
*/
export const get = (data: any, path: DotKey, value: any | null = null): any => {
interface DataWithKeys {
exist: boolean;
wildcard: boolean;
values: [any, DotKeys][];
}

const internalGet = (data: any, path: DotKey, value: any | null): DataWithKeys => {
if (!path || !isString(path)) {
return value;
return {
exist: false,
wildcard: false,
values: [[value, []]],
};
}

const key = '__get_item__';
const tokens = tokenize(path);
const length = tokens.length;
let useWildcard = false;
let index = 0;
let context = { [key]: [data] };
const state: {
index: number;
context: { [index: string]: [any, DotKeys][] };
wildcard: boolean;
} = {
index: 0,
context: { [key]: [[data, []]] },
wildcard: false,
};

tokens.forEach(token => {
const next: any[] = [];

each(context[key], item => {
each(state.context[key], ([item, p]) => {
each(item, (v, k) => {
if (matchToken(k, token)) {
if (token === '*') {
useWildcard = true;
}
next.push(v);
if (!matchToken(k, token)) return;

if (token === '*') {
state.wildcard = true;
}

next.push([v, [...p, k]]);
});
});

if (next.length > 0) {
context = { [key]: next };
index++;
state.context = { [key]: next };
state.index++;
}
});

if (index !== length) return value;
if (state.index !== length) {
return {
exist: false,
wildcard: state.wildcard,
values: [[value, []]],
};
}

return {
exist: true,
wildcard: state.wildcard,
values: state.context[key],
};
};

export const get = (data: any, path: DotKey, value: any | null = null): any => {
const { exist, wildcard, values } = internalGet(data, path, value);

const v = context[key];
return useWildcard ? v : v.shift();
if (!exist) return values[0][0];
if (wildcard) return values.map(v => v[0]);
return values[0][0];
};


Expand Down Expand Up @@ -387,34 +423,23 @@ export const expand = (data: any): any => {
/**
* Executes a provided function once for each element.
*/
const toIterable = (value: any) => !isObj(value) && !isArray(value) ? [value] : value;
export const forEach = (data: any, path: DotKey, iteratee: (value: any, key: DotKey, path: string, data: any | any[]) => boolean | void): void => {
const { exist, values } = internalGet(data, path, null);
if (!exist) return;

export const forEach = (data: any, path: DotKey, iteratee: (value: any, key: DotKey, array: any | any[]) => void): void => {
const result = get(data, path);
if (result === null) return;

const obj = toIterable(result);

each(obj, iteratee);
each(values, ([v, p]) => iteratee(v, p[p.length - 1], p.join('.'), data));
};


/**
* Create a new element
* with the results of calling a provided function on every element.
*/
export const map = (data: any, path: DotKey, iteratee: (value: any, key: DotKey, array: any | any[]) => any): any[] => {
const result = get(data, path);
if (result === null) return [];

const obj = toIterable(result);
const values: any[] = [];

each(obj, (value, key, array) => {
values[key] = iteratee(value, key, array);
});
export const map = (data: any, path: DotKey, iteratee: (value: any, key: DotKey, path: string, data: any | any[]) => any): any[] => {
const { exist, values } = internalGet(data, path, null);
if (!exist) return [];

return values;
return values.map(([v, p]) => iteratee(v, p[p.length - 1], p.join('.'), data));
};


Expand Down
108 changes: 76 additions & 32 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,58 +372,102 @@ describe('dot-wild', () => {


it('forEach()', () => {
let cnt = 0;
let result: { values: any[]; keys: any[] } = {
values: [],
keys: [],
};
let results: any[] = [];

// Not found
dot.forEach(sampleData, 'hoge.fuga', () => {
throw new Error('error');
});

// Normal path
dot.forEach(sampleData, 'nested', (value: any, key: string, data: any) => {
cnt += 1;
result.values.push(value);
result.keys.push(key);
assert.deepStrictEqual(sampleData.nested, data);
dot.forEach(sampleData, 'nested', (value: any, key: any, path: string, data: any) => {
assert(dot.get(data, path) === value);
results.push([value, key, path]);
});

assert(cnt === 1);
assert.deepStrictEqual(result.values[0], sampleData.nested.deep);
assert(results.length === 1);
assert.deepStrictEqual(results[0][0], sampleData.nested);
assert(results[0][1] === 'nested');
assert(results[0][2] === 'nested');

// Use wildcard
cnt = 0;
result.values = [];
result.keys = [];

dot.forEach(sampleData, '*', (value: any, key: number) => {
cnt += 1;
result.values.push(value);
result.keys.push(key);
assert(key === cnt - 1);
results = [];

dot.forEach(sampleData, 'tags.*.*', (value: any, key: any, path: string, data: any) => {
assert.deepStrictEqual(dot.get(data, path), value);
results.push([value, key, path]);
});

assert(cnt === 2);
assert.deepStrictEqual(result.values[0], sampleData.tags);
assert.deepStrictEqual(result.values[1], sampleData.nested);
assert(results.length === 4);

assert(results[0][0] === 1);
assert(results[0][1] === 'id');
assert(results[0][2] === 'tags.0.id');

assert(results[1][0] === 'tag1');
assert(results[1][1] === 'tag');
assert(results[1][2] === 'tags.0.tag');

assert(results[2][0] === 2);
assert(results[2][1] === 'id');
assert(results[2][2] === 'tags.1.id');

assert(results[3][0] === 'tag2');
assert(results[3][1] === 'tag');
assert(results[3][2] === 'tags.1.tag');
});


it('map()', () => {
let result: any[] = [];
let results: any[] = [];

// Not found
results = dot.map(sampleData, 'foo.bar', () => {
throw new Error('error');
});

// Normal path
result = dot.map(sampleData, 'tags', (value: any, key: any, array: any) => {
assert.deepStrictEqual(sampleData.tags, array);
return value.id + key;
results = dot.map(sampleData, 'tags', (value: any, key: any, path: string, data: any) => {
assert.deepStrictEqual(dot.get(data, path), value);
return [value, key, path];
});

assert.deepStrictEqual(result, [1, 3]);
assert(results.length === 1);
assert.deepStrictEqual(results[0][0], sampleData.tags);
assert(results[0][1] === 'tags');
assert(results[0][2] === 'tags');

// Use wildcard
result = dot.map(sampleData, 'nested.deep.*.members.*.profile.age', (value: any, key: any) => {
return value + key;
results = dot.map(sampleData, 'nested.deep.*.members.*.profile.age', (value: any, key: any, path: string, data: any) => {
assert.deepStrictEqual(dot.get(data, path), value);
return [value, key, path];
});

assert.deepStrictEqual(result, [24, 31, 35, 22, 37, 45]);
assert(results.length === 6);

assert(results[0][0] === 24);
assert(results[0][1] === 'age');
assert(results[0][2] === 'nested.deep.0.members.0.profile.age');

assert(results[1][0] === 30);
assert(results[1][1] === 'age');
assert(results[1][2] === 'nested.deep.0.members.1.profile.age');

assert(results[2][0] === 33);
assert(results[2][1] === 'age');
assert(results[2][2] === 'nested.deep.0.members.2.profile.age');

assert(results[3][0] === 19);
assert(results[3][1] === 'age');
assert(results[3][2] === 'nested.deep.1.members.0.profile.age');

assert(results[4][0] === 33);
assert(results[4][1] === 'age');
assert(results[4][2] === 'nested.deep.1.members.1.profile.age');

assert(results[5][0] === 40);
assert(results[5][1] === 'age');
assert(results[5][2] === 'nested.deep.1.members.2.profile.age');
});


Expand Down

0 comments on commit d6b9b38

Please sign in to comment.