198 lines
5.5 KiB
JavaScript
198 lines
5.5 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const { HookMap, SyncBailHook, SyncWaterfallHook } = require("tapable");
|
|
const { concatComparators, keepOriginalOrder } = require("../util/comparators");
|
|
|
|
const forEachLevel = (hookMap, type, fn) => {
|
|
const typeParts = type.split(".");
|
|
for (let i = 0; i < typeParts.length; i++) {
|
|
const hook = hookMap.get(typeParts.slice(i).join("."));
|
|
if (hook) {
|
|
const result = fn(hook);
|
|
if (result !== undefined) return result;
|
|
}
|
|
}
|
|
};
|
|
|
|
const forEachLevelWaterfall = (hookMap, type, data, fn) => {
|
|
const typeParts = type.split(".");
|
|
for (let i = 0; i < typeParts.length; i++) {
|
|
const hook = hookMap.get(typeParts.slice(i).join("."));
|
|
if (hook) {
|
|
data = fn(hook, data);
|
|
}
|
|
}
|
|
return data;
|
|
};
|
|
|
|
const forEachLevelFilter = (hookMap, type, items, fn, forceClone) => {
|
|
const typeParts = type.split(".");
|
|
const hooks = [];
|
|
for (let i = 0; i < typeParts.length; i++) {
|
|
const hook = hookMap.get(typeParts.slice(i).join("."));
|
|
if (hook) {
|
|
hooks.push((item, idx, i) => fn(hook, item, idx, i));
|
|
}
|
|
}
|
|
if (hooks.length === 0) return forceClone ? items.slice() : items;
|
|
let i = 0;
|
|
return items.filter((item, idx) => {
|
|
for (const h of hooks) {
|
|
const r = h(item, idx, i);
|
|
if (r !== undefined) {
|
|
if (r) i++;
|
|
return r;
|
|
}
|
|
}
|
|
i++;
|
|
return true;
|
|
});
|
|
};
|
|
|
|
class StatsFactory {
|
|
constructor() {
|
|
this.hooks = Object.freeze({
|
|
/** @type {HookMap<SyncBailHook<[Object, any, Object]>>} */
|
|
extract: new HookMap(
|
|
() => new SyncBailHook(["object", "data", "context"])
|
|
),
|
|
/** @type {HookMap<SyncBailHook<[any, Object, number, number]>>} */
|
|
filter: new HookMap(
|
|
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
|
|
),
|
|
/** @type {HookMap<SyncBailHook<[(function(any, any): number)[], Object]>>} */
|
|
sort: new HookMap(() => new SyncBailHook(["comparators", "context"])),
|
|
/** @type {HookMap<SyncBailHook<[any, Object, number, number]>>} */
|
|
filterSorted: new HookMap(
|
|
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
|
|
),
|
|
/** @type {HookMap<SyncBailHook<[(function(any, any): number)[], Object]>>} */
|
|
sortResults: new HookMap(
|
|
() => new SyncBailHook(["comparators", "context"])
|
|
),
|
|
filterResults: new HookMap(
|
|
() => new SyncBailHook(["item", "context", "index", "unfilteredIndex"])
|
|
),
|
|
/** @type {HookMap<SyncBailHook<[any[], Object]>>} */
|
|
merge: new HookMap(() => new SyncBailHook(["items", "context"])),
|
|
/** @type {HookMap<SyncBailHook<[any[], Object]>>} */
|
|
result: new HookMap(() => new SyncWaterfallHook(["result", "context"])),
|
|
/** @type {HookMap<SyncBailHook<[any, Object]>>} */
|
|
getItemName: new HookMap(() => new SyncBailHook(["item", "context"])),
|
|
/** @type {HookMap<SyncBailHook<[any, Object]>>} */
|
|
getItemFactory: new HookMap(() => new SyncBailHook(["item", "context"]))
|
|
});
|
|
}
|
|
|
|
create(type, data, baseContext) {
|
|
const context = {
|
|
...baseContext,
|
|
type,
|
|
[type]: data
|
|
};
|
|
if (Array.isArray(data)) {
|
|
// run filter on unsorted items
|
|
const items = forEachLevelFilter(
|
|
this.hooks.filter,
|
|
type,
|
|
data,
|
|
(h, r, idx, i) => h.call(r, context, idx, i),
|
|
true
|
|
);
|
|
|
|
// sort items
|
|
const comparators = [];
|
|
forEachLevel(this.hooks.sort, type, h => h.call(comparators, context));
|
|
if (comparators.length > 0) {
|
|
items.sort(
|
|
// @ts-ignore number of arguments is correct
|
|
concatComparators(...comparators, keepOriginalOrder(items))
|
|
);
|
|
}
|
|
|
|
// run filter on sorted items
|
|
const items2 = forEachLevelFilter(
|
|
this.hooks.filterSorted,
|
|
type,
|
|
items,
|
|
(h, r, idx, i) => h.call(r, context, idx, i),
|
|
false
|
|
);
|
|
|
|
// for each item
|
|
const resultItems = items2.map((item, i) => {
|
|
const itemContext = {
|
|
...context,
|
|
_index: i
|
|
};
|
|
|
|
// run getItemName
|
|
const itemName = forEachLevel(this.hooks.getItemName, `${type}[]`, h =>
|
|
h.call(item, itemContext)
|
|
);
|
|
if (itemName) itemContext[itemName] = item;
|
|
const innerType = itemName ? `${type}[].${itemName}` : `${type}[]`;
|
|
|
|
// run getItemFactory
|
|
const itemFactory =
|
|
forEachLevel(this.hooks.getItemFactory, innerType, h =>
|
|
h.call(item, itemContext)
|
|
) || this;
|
|
|
|
// run item factory
|
|
return itemFactory.create(innerType, item, itemContext);
|
|
});
|
|
|
|
// sort result items
|
|
const comparators2 = [];
|
|
forEachLevel(this.hooks.sortResults, type, h =>
|
|
h.call(comparators2, context)
|
|
);
|
|
if (comparators2.length > 0) {
|
|
resultItems.sort(
|
|
// @ts-ignore number of arguments is correct
|
|
concatComparators(...comparators2, keepOriginalOrder(resultItems))
|
|
);
|
|
}
|
|
|
|
// run filter on sorted result items
|
|
const finalResultItems = forEachLevelFilter(
|
|
this.hooks.filterResults,
|
|
type,
|
|
resultItems,
|
|
(h, r, idx, i) => h.call(r, context, idx, i),
|
|
false
|
|
);
|
|
|
|
// run merge on mapped items
|
|
let result = forEachLevel(this.hooks.merge, type, h =>
|
|
h.call(finalResultItems, context)
|
|
);
|
|
if (result === undefined) result = finalResultItems;
|
|
|
|
// run result on merged items
|
|
return forEachLevelWaterfall(this.hooks.result, type, result, (h, r) =>
|
|
h.call(r, context)
|
|
);
|
|
} else {
|
|
const object = {};
|
|
|
|
// run extract on value
|
|
forEachLevel(this.hooks.extract, type, h =>
|
|
h.call(object, data, context)
|
|
);
|
|
|
|
// run result on extracted object
|
|
return forEachLevelWaterfall(this.hooks.result, type, object, (h, r) =>
|
|
h.call(r, context)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
module.exports = StatsFactory;
|