« get me outta code hell

validators: isContentString - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/data/things/validators.js
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-01-05 22:02:02 -0400
committer(quasar) nebula <qznebula@protonmail.com>2024-01-06 12:50:30 -0400
commitf46e4c2eba7cfc194907e767893e8c89c072c338 (patch)
tree143e55ff1c6d561a82c1752a5ba90cbc18841b9c /src/data/things/validators.js
parentf2bcab465a645a38d95a4284617d381ac841e0a8 (diff)
validators: isContentString
Diffstat (limited to 'src/data/things/validators.js')
-rw-r--r--src/data/things/validators.js140
1 files changed, 139 insertions, 1 deletions
diff --git a/src/data/things/validators.js b/src/data/things/validators.js
index a212b8a..bff2b70 100644
--- a/src/data/things/validators.js
+++ b/src/data/things/validators.js
@@ -5,9 +5,17 @@ import printable_characters from 'printable-characters';
 const {strlen} = printable_characters;
 
 import {colors, ENABLE_COLOR} from '#cli';
-import {cut, empty, typeAppearance, withAggregate} from '#sugar';
 import {commentaryRegex} from '#wiki-data';
 
+import {
+  cut,
+  empty,
+  matchMultiline,
+  openAggregate,
+  typeAppearance,
+  withAggregate,
+} from '#sugar';
+
 function inspect(value) {
   return nodeInspect(value, {colors: ENABLE_COLOR});
 }
@@ -400,6 +408,136 @@ export const validateAllPropertyValues = (validator) =>
     [validateProperties.validateOtherKeys]: validator,
   });
 
+const illegalCharactersInContent = [
+  '\u200b',
+];
+
+const illegalContentRegexp =
+  new RegExp(`[${illegalCharactersInContent.join('')}]+`, 'g');
+
+const legalContentNearEndRegexp =
+  new RegExp(`[^${illegalCharactersInContent.join('')}]+$`);
+
+const legalContentNearStartRegexp =
+  new RegExp(`^[^${illegalCharactersInContent.join('')}]+`);
+
+const trimWhitespaceNearBothSidesRegexp =
+  /^ +| +$/gm;
+
+const trimWhitespaceNearEndRegexp =
+  / +$/gm;
+
+export function isContentString(content) {
+  isStringNonEmpty(content);
+
+  const mainAggregate = openAggregate({
+    message: `Errors validating content string`,
+    translucent: 'single',
+  });
+
+  const illegalAggregate = openAggregate({
+    message:
+      `Illegal characters found in content string\n` +
+      `(These probably look like normal characters, so try typing\n` +
+      ` the character that belongs into data manually, where marked)`,
+  });
+
+  for (const {match, where} of matchMultiline(content, illegalContentRegexp)) {
+    const matchStart = match.index;
+    const matchEnd = match.index + match[0].length;
+
+    const before =
+      content
+        .slice(Math.max(0, matchStart - 3), matchStart)
+        .match(legalContentNearEndRegexp)
+        ?.[0];
+
+    const after =
+      content
+        .slice(matchEnd, Math.min(content.length, matchEnd + 3))
+        .match(legalContentNearStartRegexp)
+        ?.[0];
+
+    const beforePart =
+      before && colors.green(`"${before}"`);
+
+    const afterPart =
+      after && colors.green(`"${after}"`);
+
+    const surroundings =
+      (before && after
+        ? `between ${beforePart} and ${afterPart}`
+     : before
+        ? `after ${beforePart}`
+     : after
+        ? `before ${afterPart}`
+        : ``);
+
+    const illegalPart =
+      colors.red(`"${match[0]}"`);
+
+    const parts = [
+      `Matched ${illegalPart}`,
+      surroundings,
+      `(${where})`,
+    ].filter(Boolean);
+
+    illegalAggregate.push(new TypeError(parts.join(` `)));
+  }
+
+  const isMultiline = content.includes('\n');
+
+  const trimWhitespaceAggregate = openAggregate({
+    message:
+      (isMultiline
+        ? `Whitespace found at end of line`
+        : `Whitespace found at start or end`),
+  });
+
+  const trimWhitespaceRegexp =
+    (isMultiline
+      ? trimWhitespaceNearEndRegexp
+      : trimWhitespaceNearBothSidesRegexp);
+
+  for (
+    const {match, lineNumber, columnNumber, containingLine} of
+    matchMultiline(content, trimWhitespaceRegexp, {
+      formatWhere: false,
+      getContainingLine: true,
+    })
+  ) {
+    const linePart =
+      colors.yellow(`line ${lineNumber + 1}`);
+
+    const where =
+      (match[0].length === containingLine.length
+        ? `all of ${linePart}`
+     : columnNumber === 0
+        ? (isMultiline
+            ? `start of ${linePart}`
+            : `at start`)
+        : (isMultiline
+            ? `end of ${linePart}`
+            : `at end`));
+
+    const whitespacePart =
+      colors.red(`"${match[0]}"`);
+
+    const parts = [
+      `Matched ${whitespacePart}`,
+      `(${where})`,
+    ];
+
+    trimWhitespaceAggregate.push(new TypeError(parts.join(` `)));
+  }
+
+  mainAggregate.call(() => illegalAggregate.close());
+  mainAggregate.call(() => trimWhitespaceAggregate.close());
+  mainAggregate.close();
+
+  return true;
+}
+
 export const isContribution = validateProperties({
   who: isArtistRef,
   what: optional(isStringNonEmpty),