You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@annotator.apache.org by ge...@apache.org on 2020/09/03 15:25:38 UTC

[incubator-annotator] 03/03: WIP Some steps to a middleware/plugin system

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 8db8de3611afc22b6eb8ec343ac9878fdd6054c8
Author: Gerben <ge...@treora.com>
AuthorDate: Thu Sep 3 17:24:04 2020 +0200

    WIP Some steps to a middleware/plugin system
---
 packages/selector/src/index.ts | 71 +++++++++++++++++++++++++++++++-----------
 web/demo/index.js              | 23 ++++++++++----
 2 files changed, 69 insertions(+), 25 deletions(-)

diff --git a/packages/selector/src/index.ts b/packages/selector/src/index.ts
index b95a858..5840439 100644
--- a/packages/selector/src/index.ts
+++ b/packages/selector/src/index.ts
@@ -18,36 +18,69 @@
  * under the License.
  */
 
-import type { Matcher, Selector } from './types';
+import type { Matcher, Selector, SelectorType } 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;
+type MatcherCreator<TScope, TMatch> = (selector: Selector) => Matcher<TScope, TMatch>;
+type Plugin<TScope, TMatch> =
+  (
+    next: MatcherCreator<TScope, TMatch>,
+    recurse: MatcherCreator<TScope, TMatch>,
+  ) => typeof next;
 
-    if (type === undefined) {
-      throw new TypeError('Selector does not specify its type');
-    }
+const identity: Plugin<any, any> = (next, recurse) => next;
 
-    const innerCreateMatcher = (typeof typeToMatcher === 'function')
-      ? typeToMatcher(type)
-      : typeToMatcher[type];
+// simply equals makeRefinable!
+export const supportRefinement: Plugin<any, any> =
+  <TScope, TMatch extends TScope>(
+    next: MatcherCreator<TScope, TMatch>,
+    recurse: MatcherCreator<TScope, TMatch>,
+  ) => {
+    return makeRefinable(next);
+  };
 
-    if (innerCreateMatcher === undefined) {
-      throw new TypeError(`Unsupported selector type: ${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}`);
+  }
 
-    return innerCreateMatcher(selector);
+  function outerMatcherCreator(selector: Selector): Matcher<TScope, TMatch> {
+    return composedMatcherCreator(selector);
   }
 
-  return makeRefinable(createMatcher);
+  const composedMatcherCreator = plugins.reduce(
+    (matcherCreator: MatcherCreator<TScope, TMatch>, plugin: Plugin<TScope, TMatch>) => plugin(matcherCreator, outerMatcherCreator),
+    innerMatcherCreator,
+  );
+
+  return outerMatcherCreator;
+}
+
+// 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(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);
+    }
+  }
 }
 
 export function makeRefinable<
diff --git a/web/demo/index.js b/web/demo/index.js
index 16c9d03..44597c3 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,
+  mapSelectorTypes({
+    TextQuoteSelector: createTextQuoteSelectorMatcher,
+  }),
+  // supportRangeSelector,
+);
 
 async function anchor(selector) {
   const matchAll = createMatcher(selector);