« get me outta code hell

data, replacer: withContentNodes, splitContentNodesAround - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2025-05-01 16:49:02 -0300
committer(quasar) nebula <qznebula@protonmail.com>2025-05-06 12:29:06 -0300
commit2e5b3352a3ab82e4d6fa249f3c74fb8a1f8fad04 (patch)
treecde0493e1771c160a1531416b2172810a480ff5a
parent301f1466b9d0e2e04d5bed6b948cae6683d61224 (diff)
data, replacer: withContentNodes, splitContentNodesAround
-rw-r--r--src/data/composite/things/content/withAnnotationParts.js149
-rw-r--r--src/data/composite/wiki-data/index.js2
-rw-r--r--src/data/composite/wiki-data/splitContentNodesAround.js87
-rw-r--r--src/data/composite/wiki-data/withContentNodes.js25
-rw-r--r--src/replacer.js76
5 files changed, 201 insertions, 138 deletions
diff --git a/src/data/composite/things/content/withAnnotationParts.js b/src/data/composite/things/content/withAnnotationParts.js
index a7b2d1f0..290a7292 100644
--- a/src/data/composite/things/content/withAnnotationParts.js
+++ b/src/data/composite/things/content/withAnnotationParts.js
@@ -4,68 +4,8 @@ import {transposeArrays} from '#sugar';
 import {is} from '#validators';
 
 import {raiseOutputWithoutDependency} from '#composite/control-flow';
-
-import {
-  withFilteredList,
-  withMappedList,
-  withPropertyFromList,
-  withUnflattenedList,
-} from '#composite/data';
-
-function* splitTextNodeAroundCommas(node) {
-  let textNode = {
-    i: node.i,
-    iEnd: null,
-    type: 'text',
-    data: '',
-  };
-
-  let parseFrom = 0;
-  for (const match of node.data.matchAll(/, */g)) {
-    const {index} = match, [{length}] = match;
-
-    textNode.data += node.data.slice(parseFrom, index);
-
-    if (textNode.data) {
-      textNode.iEnd = textNode.i + textNode.data.length;
-      yield textNode;
-    }
-
-    yield {
-      i: node.i + index,
-      iEnd: node.i + index + length,
-      type: 'comma-separator',
-    };
-
-    textNode = {
-      i: node.i + index + length,
-      iEnd: null,
-      type: 'text',
-      data: '',
-    };
-
-    parseFrom = index + length;
-  }
-
-  if (parseFrom !== node.data.length) {
-    textNode.data += node.data.slice(parseFrom);
-    textNode.iEnd = node.iEnd;
-  }
-
-  if (textNode.data) {
-    yield textNode;
-  }
-}
-
-function* splitTextNodesAroundCommas(nodes) {
-  for (const node of nodes) {
-    if (node.type === 'text' && node.data.includes(',')) {
-      yield* splitTextNodeAroundCommas(node);
-    } else {
-      yield node;
-    }
-  }
-}
+import {withPropertyFromList} from '#composite/data';
+import {splitContentNodesAround, withContentNodes} from '#composite/wiki-data';
 
 export default templateCompositeFrom({
   annotation: `withAnnotationParts`,
@@ -84,81 +24,19 @@ export default templateCompositeFrom({
       output: input.value({'#annotationParts': []}),
     }),
 
-    // Get the list of nodes including custom comma-separator nodes.
-
-    {
-      dependencies: ['annotation'],
-      compute: (continuation, {
-        ['annotation']: annotation,
-      }) => continuation({
-        ['#nodes']:
-          Array.from(
-            splitTextNodesAroundCommas(
-              parseInput(annotation))),
-      }),
-    },
-
-    // Join the nodes into arrays for each range between comma separators,
-    // excluding the comma-separator nodes themselves.
-
-    withMappedList({
-      list: '#nodes',
-      map: input.value(node => node.type === 'comma-separator'),
-    }).outputs({
-      '#mappedList': '#commaSeparatorFilter',
+    withContentNodes({
+      from: 'annotation',
     }),
 
-    withMappedList({
-      list: '#commaSeparatorFilter',
-      filter: '#commaSeparatorFilter',
-      map: input.value((_node, index) => index),
-    }),
-
-    withFilteredList({
-      list: '#mappedList',
-      filter: '#commaSeparatorFilter',
-    }).outputs({
-      '#filteredList': '#commaSeparatorIndices',
+    splitContentNodesAround({
+      nodes: '#contentNodes',
+      around: input.value(/, */g),
     }),
 
     {
-      dependencies: ['#nodes', '#commaSeparatorFilter'],
-
+      dependencies: ['#contentNodeLists', input('mode')],
       compute: (continuation, {
-        ['#nodes']: nodes,
-        ['#commaSeparatorFilter']: commaSeparatorFilter,
-      }) => continuation({
-        ['#nodes']:
-          nodes.map((node, index) =>
-            (commaSeparatorFilter[index]
-              ? null
-              : node)),
-      }),
-    },
-
-    {
-      dependencies: ['#commaSeparatorIndices'],
-      compute: (continuation, {
-        ['#commaSeparatorIndices']: commaSeparatorIndices,
-      }) => continuation({
-        ['#unflattenIndices']:
-          [0, ...commaSeparatorIndices],
-      }),
-    },
-
-    withUnflattenedList({
-      list: '#nodes',
-      indices: '#unflattenIndices',
-    }).outputs({
-      '#unflattenedList': '#nodeLists',
-    }),
-
-    // Raise output now, if we're looking for node lists.
-
-    {
-      dependencies: ['#nodeLists', input('mode')],
-      compute: (continuation, {
-        ['#nodeLists']: nodeLists,
+        ['#contentNodeLists']: nodeLists,
         [input('mode')]: mode,
       }) =>
         (mode === 'nodes'
@@ -166,14 +44,11 @@ export default templateCompositeFrom({
           : continuation()),
     },
 
-    // Get the start of the first node, and the end of the last node,
-    // within each list.
-
     {
-      dependencies: ['#nodeLists'],
+      dependencies: ['#contentNodeLists'],
 
       compute: (continuation, {
-        ['#nodeLists']: nodeLists,
+        ['#contentNodeLists']: nodeLists,
       }) => continuation({
         ['#firstNodes']:
           nodeLists.map(list => list.at(0)),
@@ -197,8 +72,6 @@ export default templateCompositeFrom({
       '#lastNodes.iEnd': '#endIndices',
     }),
 
-    // Slice the content text within the bounds of each node list.
-
     {
       dependencies: [
         'annotation',
diff --git a/src/data/composite/wiki-data/index.js b/src/data/composite/wiki-data/index.js
index 005c68c0..44a43db0 100644
--- a/src/data/composite/wiki-data/index.js
+++ b/src/data/composite/wiki-data/index.js
@@ -11,8 +11,10 @@ export {default as inputNotFoundMode} from './inputNotFoundMode.js';
 export {default as inputSoupyFind} from './inputSoupyFind.js';
 export {default as inputSoupyReverse} from './inputSoupyReverse.js';
 export {default as inputWikiData} from './inputWikiData.js';
+export {default as splitContentNodesAround} from './splitContentNodesAround.js';
 export {default as withClonedThings} from './withClonedThings.js';
 export {default as withConstitutedArtwork} from './withConstitutedArtwork.js';
+export {default as withContentNodes} from './withContentNodes.js';
 export {default as withContributionListSums} from './withContributionListSums.js';
 export {default as withCoverArtDate} from './withCoverArtDate.js';
 export {default as withDirectory} from './withDirectory.js';
diff --git a/src/data/composite/wiki-data/splitContentNodesAround.js b/src/data/composite/wiki-data/splitContentNodesAround.js
new file mode 100644
index 00000000..6648d8e1
--- /dev/null
+++ b/src/data/composite/wiki-data/splitContentNodesAround.js
@@ -0,0 +1,87 @@
+import {input, templateCompositeFrom} from '#composite';
+import {splitContentNodesAround} from '#replacer';
+import {anyOf, isFunction, validateInstanceOf} from '#validators';
+
+import {withFilteredList, withMappedList, withUnflattenedList}
+  from '#composite/data';
+
+export default templateCompositeFrom({
+  annotation: `splitContentNodesAround`,
+
+  inputs: {
+    nodes: input({type: 'array'}),
+
+    around: input({
+      validate:
+        anyOf(isFunction, validateInstanceOf(RegExp)),
+    }),
+  },
+
+  outputs: ['#contentNodeLists'],
+
+  steps: () => [
+    {
+      dependencies: [input('nodes'), input('around')],
+
+      compute: (continuation, {
+        [input('nodes')]: nodes,
+        [input('around')]: splitter,
+      }) => continuation({
+        ['#nodes']:
+          Array.from(splitContentNodesAround(nodes, splitter)),
+      }),
+    },
+
+    withMappedList({
+      list: '#nodes',
+      map: input.value(node => node.type === 'separator'),
+    }).outputs({
+      '#mappedList': '#separatorFilter',
+    }),
+
+    withMappedList({
+      list: '#separatorFilter',
+      filter: '#separatorFilter',
+      map: input.value((_node, index) => index),
+    }),
+
+    withFilteredList({
+      list: '#mappedList',
+      filter: '#separatorFilter',
+    }).outputs({
+      '#filteredList': '#separatorIndices',
+    }),
+
+    {
+      dependencies: ['#nodes', '#separatorFilter'],
+
+      compute: (continuation, {
+        ['#nodes']: nodes,
+        ['#separatorFilter']: separatorFilter,
+      }) => continuation({
+        ['#nodes']:
+          nodes.map((node, index) =>
+            (separatorFilter[index]
+              ? null
+              : node)),
+      }),
+    },
+
+    {
+      dependencies: ['#separatorIndices'],
+      compute: (continuation, {
+        ['#separatorIndices']: separatorIndices,
+      }) => continuation({
+        ['#unflattenIndices']:
+          [0, ...separatorIndices],
+      }),
+    },
+
+    withUnflattenedList({
+      list: '#nodes',
+      indices: '#unflattenIndices',
+    }).outputs({
+      '#unflattenedList': '#contentNodeLists',
+    }),
+  ],
+});
diff --git a/src/data/composite/wiki-data/withContentNodes.js b/src/data/composite/wiki-data/withContentNodes.js
new file mode 100644
index 00000000..25352020
--- /dev/null
+++ b/src/data/composite/wiki-data/withContentNodes.js
@@ -0,0 +1,25 @@
+import {input, templateCompositeFrom} from '#composite';
+import {parseInput} from '#replacer';
+
+export default templateCompositeFrom({
+  annotation: `withContentNodes`,
+
+  inputs: {
+    from: input({type: 'string', acceptsNull: false}),
+  },
+
+  outputs: ['#contentNodes'],
+
+  steps: () => [
+    {
+      dependencies: [input('from')],
+
+      compute: (continuation, {
+        [input('from')]: string,
+      }) => continuation({
+        ['#contentNodes']:
+          parseInput(string),
+      }),
+    },
+  ],
+});
diff --git a/src/replacer.js b/src/replacer.js
index 8abbbd6b..8574bf0c 100644
--- a/src/replacer.js
+++ b/src/replacer.js
@@ -871,3 +871,79 @@ export function parseInput(input) {
     ].join('\n'));
   }
 }
+
+export function* splitContentNodesAround(nodes, splitter) {
+  if (splitter instanceof RegExp) {
+    const regex = splitter;
+
+    splitter = function*(text) {
+      for (const match of text.matchAll(regex)) {
+        yield {
+          index: match.index,
+          length: match[0].length,
+        };
+      }
+    };
+  }
+
+  if (typeof splitter === 'string') {
+    throw new TypeError(`Expected generator or regular expression`);
+  }
+
+  function* splitTextNode(node) {
+    let textNode = {
+      i: node.i,
+      iEnd: null,
+      type: 'text',
+      data: '',
+    };
+
+    let parseFrom = 0;
+    for (const match of splitter(node.data)) {
+      const {index, length} = match;
+
+      textNode.data += node.data.slice(parseFrom, index);
+
+      if (textNode.data) {
+        textNode.iEnd = textNode.i + textNode.data.length;
+        yield textNode;
+      }
+
+      yield {
+        i: node.i + index,
+        iEnd: node.i + index + length,
+        type: 'separator',
+        data: {
+          text: node.data.slice(index, index + length),
+          match,
+        },
+      };
+
+      textNode = {
+        i: node.i + index + length,
+        iEnd: null,
+        type: 'text',
+        data: '',
+      };
+
+      parseFrom = index + length;
+    }
+
+    if (parseFrom !== node.data.length) {
+      textNode.data += node.data.slice(parseFrom);
+      textNode.iEnd = node.iEnd;
+    }
+
+    if (textNode.data) {
+      yield textNode;
+    }
+  }
+
+  for (const node of nodes) {
+    if (node.type === 'text') {
+      yield* splitTextNode(node);
+    } else {
+      yield node;
+    }
+  }
+}