This is an automated email from the ASF dual-hosted git repository. gerben pushed a commit to branch simpler-matcher-creation in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git
commit 59b28a19086b56d534185cfb9d73fcd65ab9ee28 Author: Gerben <[email protected]> AuthorDate: Thu Sep 3 17:24:04 2020 +0200 WIP Some steps to a middleware/plugin system --- packages/selector/src/index.ts | 69 ++++++++++++++++++++++++++++++------------ packages/selector/src/types.ts | 17 +++++++++++ web/demo/index.js | 23 ++++++++++---- 3 files changed, 83 insertions(+), 26 deletions(-) diff --git a/packages/selector/src/index.ts b/packages/selector/src/index.ts index b95a858..70396c3 100644 --- a/packages/selector/src/index.ts +++ b/packages/selector/src/index.ts @@ -18,38 +18,67 @@ * under the License. */ -import type { Matcher, Selector } from './types'; +import type { Matcher, Selector, SelectorType, MatcherCreator, Plugin } from './types'; export type { Matcher, Selector } from './types'; export type { CssSelector, RangeSelector, TextQuoteSelector } from './types'; -export function createTypedMatcherCreator<TSelectorType extends string, TScope, TMatch extends TScope>( - typeToMatcher: - | Record<TSelectorType, ((selector: Selector) => Matcher<TScope, TMatch>)> - | ((type: TSelectorType) => (selector: Selector) => Matcher<TScope, TMatch>), -): (selector: Selector & { type: TSelectorType }) => Matcher<TScope, TMatch> { +interface TypeToMatcherCreatorMap<TScope, TMatch> { + // [K: SelectorType]: MatcherCreator<TScope, TMatch>; // Gives errors further down. TypeScript’s fault? + [K: string]: MatcherCreator<TScope, TMatch> | undefined; +} - function createMatcher(selector: Selector & { type: TSelectorType }): Matcher<TScope, TMatch> { - const type = selector.type; +export function composeMatcherCreator<TScope, TMatch extends TScope>( + ...plugins: Array<Plugin<TScope, TMatch>> +): MatcherCreator<TScope, TMatch> { + function innerMatcherCreator(selector: Selector): Matcher<TScope, TMatch> { + throw new TypeError(`Unhandled selector. Selector type: ${selector.type}`); + } - if (type === undefined) { - throw new TypeError('Selector does not specify its type'); - } + function outerMatcherCreator(selector: Selector): Matcher<TScope, TMatch> { + return composedMatcherCreator(selector); + } - const innerCreateMatcher = (typeof typeToMatcher === 'function') - ? typeToMatcher(type) - : typeToMatcher[type]; + const composedMatcherCreator = plugins.reduceRight( + ( + matcherCreator: MatcherCreator<TScope, TMatch>, + plugin: Plugin<TScope, TMatch> + ) => plugin(matcherCreator, outerMatcherCreator), + innerMatcherCreator, + ); - if (innerCreateMatcher === undefined) { - throw new TypeError(`Unsupported selector type: ${type}`); - } + return outerMatcherCreator; +} - return innerCreateMatcher(selector); +// A plugin with parameters (i.e. a function that returns a plugin) +// Invokes the matcher implementation corresponding to the selector’s type. +export function mapSelectorTypes<TScope, TMatch extends TScope>( + typeToMatcherCreator: TypeToMatcherCreatorMap<TScope, TMatch>, +): Plugin<TScope, TMatch> { + return function mapSelectorTypesPlugin(next, recurse): MatcherCreator<TScope, TMatch> { + return function(selector: Selector): Matcher<TScope, TMatch> { + const type = selector.type; + if (type !== undefined) { + const matcherCreator = typeToMatcherCreator[type]; + if (matcherCreator !== undefined) + return matcherCreator(selector); + } + // Not a know selector type; continue down the plugin chain. + return next(selector); + } } - - return makeRefinable(createMatcher); } +// A plugin to support the Selector’s refinedBy field . +// TODO this just says `supportRefinement = makeRefinable`; which is doing recursion wrong. +export const supportRefinement: Plugin<any, any> = + function supportRefinementPlugin<TScope, TMatch extends TScope>( + next: MatcherCreator<TScope, TMatch>, + recurse: MatcherCreator<TScope, TMatch>, + ) { + return makeRefinable(next); + }; + export function makeRefinable< TSelector extends Selector, TScope, diff --git a/packages/selector/src/types.ts b/packages/selector/src/types.ts index 58428f7..2cf0929 100644 --- a/packages/selector/src/types.ts +++ b/packages/selector/src/types.ts @@ -46,3 +46,20 @@ export interface RangeSelector extends Selector { export interface Matcher<TScope, TMatch> { (scope: TScope): AsyncGenerator<TMatch, void, void>; } + +export type MatcherCreator<TScope, TMatch> = (selector: Selector) => Matcher<TScope, TMatch>; + +export type Plugin<TScope, TMatch> = + ( + next: MatcherCreator<TScope, TMatch>, + recurse: MatcherCreator<TScope, TMatch>, + ) => typeof next; + + +// Basic form of a plugin: (two equivalents) +// const identity: Plugin<any, any> = (next, recurse) => next; +// const identity: Plugin<any, any> = (next, recurse) => { +// return function (selector: Selector): Matcher<any, any> { +// return next(selector); // or do something else here. +// }; +// }; diff --git a/web/demo/index.js b/web/demo/index.js index 16c9d03..098b51a 100644 --- a/web/demo/index.js +++ b/web/demo/index.js @@ -19,14 +19,22 @@ */ /* global info, module, source, target */ +// declare const module; // TODO type? +// declare const info: HTMLElement; +// declare const source: HTMLElement; +// declare const target: HTMLElement; import { - makeCreateRangeSelectorMatcher, createTextQuoteSelectorMatcher, describeTextQuote, highlightRange, } from '@annotator/dom'; -import { createTypedMatcherCreator } from '@annotator/selector'; +import { + composeMatcherCreator, + mapSelectorTypes, + // supportRangeSelector, + supportRefinement, +} from '@annotator/selector'; const EXAMPLE_SELECTORS = [ { @@ -91,10 +99,13 @@ function cleanup() { target.normalize(); } -const createMatcher = createTypedMatcherCreator({ - TextQuoteSelector: createTextQuoteSelectorMatcher, - RangeSelector: makeCreateRangeSelectorMatcher(createMatcher), // FIXME This goes wrong. Tough! -}); +const createMatcher = composeMatcherCreator( + supportRefinement, + // supportRangeSelector, + mapSelectorTypes({ + TextQuoteSelector: createTextQuoteSelectorMatcher, + }), +); async function anchor(selector) { const matchAll = createMatcher(selector);
