diff options
| author | (quasar) nebula <qznebula@protonmail.com> | 2026-01-26 14:07:13 -0400 |
|---|---|---|
| committer | (quasar) nebula <qznebula@protonmail.com> | 2026-01-26 14:07:13 -0400 |
| commit | 1f37e5d6b0c6fccc9c46aabd7bd402375131d452 (patch) | |
| tree | e441757a73dd2b2cb346ce33b90bf185c614fe7c | |
| parent | aa3cb2dd34780fd97873340c3faf7388993fa8d8 (diff) | |
data: break up content.js, flash.js, sorting-rule.js
| -rw-r--r-- | src/data/things/content/CommentaryEntry.js | 20 | ||||
| -rw-r--r-- | src/data/things/content/ContentEntry.js (renamed from src/data/things/content.js) | 73 | ||||
| -rw-r--r-- | src/data/things/content/CreditingSourcesEntry.js | 16 | ||||
| -rw-r--r-- | src/data/things/content/LyricsEntry.js | 43 | ||||
| -rw-r--r-- | src/data/things/content/ReferencingSourcesEntry.js | 16 | ||||
| -rw-r--r-- | src/data/things/content/index.js | 9 | ||||
| -rw-r--r-- | src/data/things/flash/Flash.js (renamed from src/data/things/flash.js) | 199 | ||||
| -rw-r--r-- | src/data/things/flash/FlashAct.js | 74 | ||||
| -rw-r--r-- | src/data/things/flash/FlashSide.js | 136 | ||||
| -rw-r--r-- | src/data/things/flash/index.js | 3 | ||||
| -rw-r--r-- | src/data/things/index.js | 7 | ||||
| -rw-r--r-- | src/data/things/sorting-rule/DocumentSortingRule.js (renamed from src/data/things/sorting-rule.js) | 158 | ||||
| -rw-r--r-- | src/data/things/sorting-rule/SortingRule.js | 86 | ||||
| -rw-r--r-- | src/data/things/sorting-rule/ThingSortingRule.js | 83 | ||||
| -rw-r--r-- | src/data/things/sorting-rule/index.js | 3 |
15 files changed, 495 insertions, 431 deletions
diff --git a/src/data/things/content/CommentaryEntry.js b/src/data/things/content/CommentaryEntry.js new file mode 100644 index 00000000..32d69213 --- /dev/null +++ b/src/data/things/content/CommentaryEntry.js @@ -0,0 +1,20 @@ +import {V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; + +import {hasAnnotationPart} from '#composite/things/content'; + +import {ContentEntry} from './ContentEntry.js'; + +export class CommentaryEntry extends ContentEntry { + static [Thing.wikiData] = 'commentaryData'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Expose only + + isCommentaryEntry: exposeConstant(V(true)), + + isWikiEditorCommentary: hasAnnotationPart(V('wiki editor')), + }); +} diff --git a/src/data/things/content.js b/src/data/things/content/ContentEntry.js index 64d03e69..7dc81345 100644 --- a/src/data/things/content.js +++ b/src/data/things/content/ContentEntry.js @@ -20,7 +20,6 @@ import { } from '#composite/control-flow'; import { - hasAnnotationPart, withAnnotationPartNodeLists, withExpressedOrImplicitArtistReferences, withWebArchiveDate, @@ -245,75 +244,3 @@ export class ContentEntry extends Thing { }, }; } - -export class CommentaryEntry extends ContentEntry { - static [Thing.wikiData] = 'commentaryData'; - - static [Thing.getPropertyDescriptors] = () => ({ - // Expose only - - isCommentaryEntry: [ - exposeConstant({ - value: input.value(true), - }), - ], - - isWikiEditorCommentary: hasAnnotationPart({ - part: input.value('wiki editor'), - }), - }); -} - -export class LyricsEntry extends ContentEntry { - static [Thing.wikiData] = 'lyricsData'; - - static [Thing.getPropertyDescriptors] = () => ({ - // Update & expose - - originDetails: contentString(), - - // Expose only - - isLyricsEntry: exposeConstant(V(true)), - - isWikiLyrics: hasAnnotationPart(V('wiki lyrics')), - helpNeeded: hasAnnotationPart(V('help needed')), - - hasSquareBracketAnnotations: [ - exitWithoutDependency('isWikiLyrics', V(false), V('falsy')), - exitWithoutDependency('body', V(false)), - - { - dependencies: ['body'], - compute: ({body}) => - /\[.*\]/m.test(body), - }, - ], - }); - - static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(ContentEntry, { - fields: { - 'Origin Details': {property: 'originDetails'}, - }, - }); -} - -export class CreditingSourcesEntry extends ContentEntry { - static [Thing.wikiData] = 'creditingSourceData'; - - static [Thing.getPropertyDescriptors] = () => ({ - // Expose only - - isCreditingSourcesEntry: exposeConstant(V(true)), - }); -} - -export class ReferencingSourcesEntry extends ContentEntry { - static [Thing.wikiData] = 'referencingSourceData'; - - static [Thing.getPropertyDescriptors] = () => ({ - // Expose only - - isReferencingSourceEntry: exposeConstant(V(true)), - }); -} diff --git a/src/data/things/content/CreditingSourcesEntry.js b/src/data/things/content/CreditingSourcesEntry.js new file mode 100644 index 00000000..7331ae8c --- /dev/null +++ b/src/data/things/content/CreditingSourcesEntry.js @@ -0,0 +1,16 @@ +import {V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; + +import {ContentEntry} from './ContentEntry.js'; + +export class CreditingSourcesEntry extends ContentEntry { + static [Thing.wikiData] = 'creditingSourceData'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Expose only + + isCreditingSourcesEntry: exposeConstant(V(true)), + }); +} diff --git a/src/data/things/content/LyricsEntry.js b/src/data/things/content/LyricsEntry.js new file mode 100644 index 00000000..88e4464d --- /dev/null +++ b/src/data/things/content/LyricsEntry.js @@ -0,0 +1,43 @@ +import {V} from '#composite'; +import Thing from '#thing'; + +import {exitWithoutDependency, exposeConstant} from '#composite/control-flow'; +import {contentString} from '#composite/wiki-properties'; + +import {hasAnnotationPart} from '#composite/things/content'; + +import {ContentEntry} from './ContentEntry.js'; + +export class LyricsEntry extends ContentEntry { + static [Thing.wikiData] = 'lyricsData'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + originDetails: contentString(), + + // Expose only + + isLyricsEntry: exposeConstant(V(true)), + + isWikiLyrics: hasAnnotationPart(V('wiki lyrics')), + helpNeeded: hasAnnotationPart(V('help needed')), + + hasSquareBracketAnnotations: [ + exitWithoutDependency('isWikiLyrics', V(false), V('falsy')), + exitWithoutDependency('body', V(false)), + + { + dependencies: ['body'], + compute: ({body}) => + /\[.*\]/m.test(body), + }, + ], + }); + + static [Thing.yamlDocumentSpec] = Thing.extendDocumentSpec(ContentEntry, { + fields: { + 'Origin Details': {property: 'originDetails'}, + }, + }); +} diff --git a/src/data/things/content/ReferencingSourcesEntry.js b/src/data/things/content/ReferencingSourcesEntry.js new file mode 100644 index 00000000..76fafbc2 --- /dev/null +++ b/src/data/things/content/ReferencingSourcesEntry.js @@ -0,0 +1,16 @@ +import {V} from '#composite'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; + +import {ContentEntry} from './ContentEntry.js'; + +export class ReferencingSourcesEntry extends ContentEntry { + static [Thing.wikiData] = 'referencingSourceData'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Expose only + + isReferencingSourceEntry: exposeConstant(V(true)), + }); +} diff --git a/src/data/things/content/index.js b/src/data/things/content/index.js new file mode 100644 index 00000000..aaa7f304 --- /dev/null +++ b/src/data/things/content/index.js @@ -0,0 +1,9 @@ +// Yet Another Index.js File Descending From A Folder Named Content + +export * from './ContentEntry.js'; + +export * from './CommentaryEntry.js'; +export * from './LyricsEntry.js'; + +export * from './CreditingSourcesEntry.js'; +export * from './ReferencingSourcesEntry.js'; diff --git a/src/data/things/flash.js b/src/data/things/flash/Flash.js index b595ec58..1f290b3f 100644 --- a/src/data/things/flash.js +++ b/src/data/things/flash/Flash.js @@ -1,9 +1,6 @@ -export const FLASH_DATA_FILE = 'flashes.yaml'; - import {input, V} from '#composite'; -import {sortFlashesChronologically} from '#sort'; import Thing from '#thing'; -import {anyOf, isColor, isContentString, isDirectory, isNumber, isString} +import {anyOf, isColor, isDirectory, isNumber, isString} from '#validators'; import { @@ -25,13 +22,10 @@ import { } from '#composite/control-flow'; import { - color, commentatorArtists, constitutibleArtwork, - contentString, contributionList, dimensions, - directory, fileExtension, name, referenceList, @@ -250,194 +244,3 @@ export class Flash extends Thing { ]; } } - -export class FlashAct extends Thing { - static [Thing.referenceType] = 'flash-act'; - static [Thing.friendlyName] = `Flash Act`; - static [Thing.wikiData] = 'flashActData'; - - static [Thing.getPropertyDescriptors] = ({Flash, FlashSide}) => ({ - // Update & expose - - side: thing(V(FlashSide)), - - name: name(V('Unnamed Flash Act')), - directory: directory(), - color: color(), - - listTerminology: [ - exposeUpdateValueOrContinue({ - validate: input.value(isContentString), - }), - - withPropertyFromObject('side', V('listTerminology')), - exposeDependency('#side.listTerminology'), - ], - - flashes: thingList(V(Flash)), - - // Update only - - find: soupyFind(), - reverse: soupyReverse(), - - // Expose only - - isFlashAct: exposeConstant(V(true)), - }); - - static [Thing.findSpecs] = { - flashAct: { - referenceTypes: ['flash-act'], - bindTo: 'flashActData', - }, - }; - - static [Thing.reverseSpecs] = { - flashActsWhoseFlashesInclude: { - bindTo: 'flashActData', - - referencing: flashAct => [flashAct], - referenced: flashAct => flashAct.flashes, - }, - }; - - static [Thing.yamlDocumentSpec] = { - fields: { - 'Act': {property: 'name'}, - 'Directory': {property: 'directory'}, - - 'Color': {property: 'color'}, - 'List Terminology': {property: 'listTerminology'}, - - 'Review Points': {ignore: true}, - }, - }; -} - -export class FlashSide extends Thing { - static [Thing.referenceType] = 'flash-side'; - static [Thing.friendlyName] = `Flash Side`; - static [Thing.wikiData] = 'flashSideData'; - - static [Thing.getPropertyDescriptors] = ({FlashAct}) => ({ - // Update & expose - - name: name(V('Unnamed Flash Side')), - directory: directory(), - color: color(), - listTerminology: contentString(), - - acts: thingList(V(FlashAct)), - - // Update only - - find: soupyFind(), - - // Expose only - - isFlashSide: exposeConstant(V(true)), - }); - - static [Thing.yamlDocumentSpec] = { - fields: { - 'Side': {property: 'name'}, - 'Directory': {property: 'directory'}, - 'Color': {property: 'color'}, - 'List Terminology': {property: 'listTerminology'}, - }, - }; - - static [Thing.findSpecs] = { - flashSide: { - referenceTypes: ['flash-side'], - bindTo: 'flashSideData', - }, - }; - - static [Thing.reverseSpecs] = { - flashSidesWhoseActsInclude: { - bindTo: 'flashSideData', - - referencing: flashSide => [flashSide], - referenced: flashSide => flashSide.acts, - }, - }; - - static [Thing.getYamlLoadingSpec] = ({ - documentModes: {allInOne}, - thingConstructors: {Flash, FlashAct}, - }) => ({ - title: `Process flashes file`, - file: FLASH_DATA_FILE, - - documentMode: allInOne, - documentThing: document => - ('Side' in document - ? FlashSide - : 'Act' in document - ? FlashAct - : Flash), - - connect(results) { - let thing, i; - - for (i = 0; thing = results[i]; i++) { - if (thing.isFlashSide) { - const side = thing; - const acts = []; - - for (i++; thing = results[i]; i++) { - if (thing.isFlashAct) { - const act = thing; - const flashes = []; - - for (i++; thing = results[i]; i++) { - if (thing.isFlash) { - const flash = thing; - - flash.act = act; - flashes.push(flash); - - continue; - } - - i--; - break; - } - - act.side = side; - act.flashes = flashes; - acts.push(act); - - continue; - } - - if (thing.isFlash) { - throw new Error(`Flashes must be under an act`); - } - - i--; - break; - } - - side.acts = acts; - - continue; - } - - if (thing.isFlashAct) { - throw new Error(`Acts must be under a side`); - } - - if (thing.isFlash) { - throw new Error(`Flashes must be under a side and act`); - } - } - }, - - sort({flashData}) { - sortFlashesChronologically(flashData); - }, - }); -} diff --git a/src/data/things/flash/FlashAct.js b/src/data/things/flash/FlashAct.js new file mode 100644 index 00000000..66d4ee1b --- /dev/null +++ b/src/data/things/flash/FlashAct.js @@ -0,0 +1,74 @@ + +import {input, V} from '#composite'; +import Thing from '#thing'; +import {isContentString} from '#validators'; + +import {withPropertyFromObject} from '#composite/data'; +import {exposeConstant, exposeDependency, exposeUpdateValueOrContinue} + from '#composite/control-flow'; +import {color, directory, name, soupyFind, soupyReverse, thing, thingList} + from '#composite/wiki-properties'; + +export class FlashAct extends Thing { + static [Thing.referenceType] = 'flash-act'; + static [Thing.friendlyName] = `Flash Act`; + static [Thing.wikiData] = 'flashActData'; + + static [Thing.getPropertyDescriptors] = ({Flash, FlashSide}) => ({ + // Update & expose + + side: thing(V(FlashSide)), + + name: name(V('Unnamed Flash Act')), + directory: directory(), + color: color(), + + listTerminology: [ + exposeUpdateValueOrContinue({ + validate: input.value(isContentString), + }), + + withPropertyFromObject('side', V('listTerminology')), + exposeDependency('#side.listTerminology'), + ], + + flashes: thingList(V(Flash)), + + // Update only + + find: soupyFind(), + reverse: soupyReverse(), + + // Expose only + + isFlashAct: exposeConstant(V(true)), + }); + + static [Thing.findSpecs] = { + flashAct: { + referenceTypes: ['flash-act'], + bindTo: 'flashActData', + }, + }; + + static [Thing.reverseSpecs] = { + flashActsWhoseFlashesInclude: { + bindTo: 'flashActData', + + referencing: flashAct => [flashAct], + referenced: flashAct => flashAct.flashes, + }, + }; + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Act': {property: 'name'}, + 'Directory': {property: 'directory'}, + + 'Color': {property: 'color'}, + 'List Terminology': {property: 'listTerminology'}, + + 'Review Points': {ignore: true}, + }, + }; +} diff --git a/src/data/things/flash/FlashSide.js b/src/data/things/flash/FlashSide.js new file mode 100644 index 00000000..72782bdd --- /dev/null +++ b/src/data/things/flash/FlashSide.js @@ -0,0 +1,136 @@ +const FLASH_DATA_FILE = 'flashes.yaml'; + +import {V} from '#composite'; +import {sortFlashesChronologically} from '#sort'; +import Thing from '#thing'; + +import {exposeConstant} from '#composite/control-flow'; +import {color, contentString, directory, name, soupyFind, thingList} + from '#composite/wiki-properties'; + +export class FlashSide extends Thing { + static [Thing.referenceType] = 'flash-side'; + static [Thing.friendlyName] = `Flash Side`; + static [Thing.wikiData] = 'flashSideData'; + + static [Thing.getPropertyDescriptors] = ({FlashAct}) => ({ + // Update & expose + + name: name(V('Unnamed Flash Side')), + directory: directory(), + color: color(), + listTerminology: contentString(), + + acts: thingList(V(FlashAct)), + + // Update only + + find: soupyFind(), + + // Expose only + + isFlashSide: exposeConstant(V(true)), + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Side': {property: 'name'}, + 'Directory': {property: 'directory'}, + 'Color': {property: 'color'}, + 'List Terminology': {property: 'listTerminology'}, + }, + }; + + static [Thing.findSpecs] = { + flashSide: { + referenceTypes: ['flash-side'], + bindTo: 'flashSideData', + }, + }; + + static [Thing.reverseSpecs] = { + flashSidesWhoseActsInclude: { + bindTo: 'flashSideData', + + referencing: flashSide => [flashSide], + referenced: flashSide => flashSide.acts, + }, + }; + + static [Thing.getYamlLoadingSpec] = ({ + documentModes: {allInOne}, + thingConstructors: {Flash, FlashAct}, + }) => ({ + title: `Process flashes file`, + file: FLASH_DATA_FILE, + + documentMode: allInOne, + documentThing: document => + ('Side' in document + ? FlashSide + : 'Act' in document + ? FlashAct + : Flash), + + connect(results) { + let thing, i; + + for (i = 0; thing = results[i]; i++) { + if (thing.isFlashSide) { + const side = thing; + const acts = []; + + for (i++; thing = results[i]; i++) { + if (thing.isFlashAct) { + const act = thing; + const flashes = []; + + for (i++; thing = results[i]; i++) { + if (thing.isFlash) { + const flash = thing; + + flash.act = act; + flashes.push(flash); + + continue; + } + + i--; + break; + } + + act.side = side; + act.flashes = flashes; + acts.push(act); + + continue; + } + + if (thing.isFlash) { + throw new Error(`Flashes must be under an act`); + } + + i--; + break; + } + + side.acts = acts; + + continue; + } + + if (thing.isFlashAct) { + throw new Error(`Acts must be under a side`); + } + + if (thing.isFlash) { + throw new Error(`Flashes must be under a side and act`); + } + } + }, + + sort({flashData}) { + sortFlashesChronologically(flashData); + }, + }); +} diff --git a/src/data/things/flash/index.js b/src/data/things/flash/index.js new file mode 100644 index 00000000..19b8cc34 --- /dev/null +++ b/src/data/things/flash/index.js @@ -0,0 +1,3 @@ +export * from './Flash.js'; +export * from './FlashAct.js'; +export * from './FlashSide.js'; diff --git a/src/data/things/index.js b/src/data/things/index.js index 4a3d7ad8..bf8a5a33 100644 --- a/src/data/things/index.js +++ b/src/data/things/index.js @@ -1,8 +1,11 @@ // Not actually the entry point for #things - that's init.js in this folder. export * from './album/index.js'; +export * from './content/index.js'; +export * from './flash/index.js'; export * from './group/index.js'; export * from './homepage-layout/index.js'; +export * from './sorting-rule/index.js'; export * from './AdditionalFile.js'; export * from './AdditionalName.js'; @@ -16,7 +19,3 @@ export * from './NewsEntry.js'; export * from './StaticPage.js'; export * from './Track.js'; export * from './WikiInfo.js'; - -export * from './content.js'; -export * from './flash.js'; -export * from './sorting-rule.js'; diff --git a/src/data/things/sorting-rule.js b/src/data/things/sorting-rule/DocumentSortingRule.js index 101a4966..0f67d8f5 100644 --- a/src/data/things/sorting-rule.js +++ b/src/data/things/sorting-rule/DocumentSortingRule.js @@ -1,21 +1,12 @@ -export const SORTING_RULE_DATA_FILE = 'sorting-rules.yaml'; - import {readFile, writeFile} from 'node:fs/promises'; import * as path from 'node:path'; import {V} from '#composite'; -import {chunkByProperties, compareArrays, unique} from '#sugar'; +import {chunkByProperties, compareArrays} from '#sugar'; import Thing from '#thing'; import {isObject, isStringNonEmpty, anyOf, strictArrayOf} from '#validators'; import { - compareCaseLessSensitive, - sortByDate, - sortByDirectory, - sortByName, -} from '#sort'; - -import { documentModes, flattenThingLayoutToDocumentOrder, getThingLayoutForFilename, @@ -23,7 +14,6 @@ import { } from '#yaml'; import {exposeConstant} from '#composite/control-flow'; -import {flag} from '#composite/wiki-properties'; function isSelectFollowingEntry(value) { isObject(value); @@ -36,151 +26,7 @@ function isSelectFollowingEntry(value) { return true; } -export class SortingRule extends Thing { - static [Thing.friendlyName] = `Sorting Rule`; - static [Thing.wikiData] = 'sortingRules'; - - static [Thing.getPropertyDescriptors] = () => ({ - // Update & expose - - active: flag(V(true)), - - message: { - flags: {update: true, expose: true}, - update: {validate: isStringNonEmpty}, - }, - - // Expose only - - isSortingRule: exposeConstant(V(true)), - }); - - static [Thing.yamlDocumentSpec] = { - fields: { - 'Message': {property: 'message'}, - 'Active': {property: 'active'}, - }, - }; - - static [Thing.getYamlLoadingSpec] = ({ - documentModes: {allInOne}, - thingConstructors: {DocumentSortingRule}, - }) => ({ - title: `Process sorting rules file`, - file: SORTING_RULE_DATA_FILE, - - documentMode: allInOne, - documentThing: document => - (document['Sort Documents'] - ? DocumentSortingRule - : null), - }); - - check(opts) { - return this.constructor.check(this, opts); - } - - apply(opts) { - return this.constructor.apply(this, opts); - } - - static check(rule, opts) { - const result = this.apply(rule, {...opts, dry: true}); - if (!result) return true; - if (!result.changed) return true; - return false; - } - - static async apply(_rule, _opts) { - throw new Error(`Not implemented`); - } - - static async* applyAll(_rules, _opts) { - throw new Error(`Not implemented`); - } - - static async* go({dataPath, wikiData, dry}) { - const rules = wikiData.sortingRules; - const constructors = unique(rules.map(rule => rule.constructor)); - - for (const constructor of constructors) { - yield* constructor.applyAll( - rules - .filter(rule => rule.active) - .filter(rule => rule.constructor === constructor), - {dataPath, wikiData, dry}); - } - } -} - -export class ThingSortingRule extends SortingRule { - static [Thing.getPropertyDescriptors] = () => ({ - // Update & expose - - properties: { - flags: {update: true, expose: true}, - update: { - validate: strictArrayOf(isStringNonEmpty), - }, - }, - - // Expose only - - isThingSortingRule: exposeConstant(V(true)), - }); - - static [Thing.yamlDocumentSpec] = { - fields: { - 'By Properties': {property: 'properties'}, - }, - }; - - sort(sortable) { - if (this.properties) { - for (const property of this.properties.toReversed()) { - const get = thing => thing[property]; - const lc = property.toLowerCase(); - - if (lc.endsWith('date')) { - sortByDate(sortable, {getDate: get}); - continue; - } - - if (lc.endsWith('directory')) { - sortByDirectory(sortable, {getDirectory: get}); - continue; - } - - if (lc.endsWith('name')) { - sortByName(sortable, {getName: get}); - continue; - } - - const values = sortable.map(get); - - if (values.every(v => typeof v === 'string')) { - sortable.sort((a, b) => - compareCaseLessSensitive(get(a), get(b))); - continue; - } - - if (values.every(v => typeof v === 'number')) { - sortable.sort((a, b) => get(a) - get(b)); - continue; - } - - sortable.sort((a, b) => - (get(a).toString() < get(b).toString() - ? -1 - : get(a).toString() > get(b).toString() - ? +1 - : 0)); - } - } - - return sortable; - } -} +import {ThingSortingRule} from './ThingSortingRule.js'; export class DocumentSortingRule extends ThingSortingRule { static [Thing.getPropertyDescriptors] = () => ({ diff --git a/src/data/things/sorting-rule/SortingRule.js b/src/data/things/sorting-rule/SortingRule.js new file mode 100644 index 00000000..4ce9d97a --- /dev/null +++ b/src/data/things/sorting-rule/SortingRule.js @@ -0,0 +1,86 @@ +const SORTING_RULE_DATA_FILE = 'sorting-rules.yaml'; + +import {V} from '#composite'; +import {unique} from '#sugar'; +import Thing from '#thing'; +import {isStringNonEmpty} from '#validators'; + +import {exposeConstant} from '#composite/control-flow'; +import {flag} from '#composite/wiki-properties'; + +export class SortingRule extends Thing { + static [Thing.friendlyName] = `Sorting Rule`; + static [Thing.wikiData] = 'sortingRules'; + + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + active: flag(V(true)), + + message: { + flags: {update: true, expose: true}, + update: {validate: isStringNonEmpty}, + }, + + // Expose only + + isSortingRule: exposeConstant(V(true)), + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'Message': {property: 'message'}, + 'Active': {property: 'active'}, + }, + }; + + static [Thing.getYamlLoadingSpec] = ({ + documentModes: {allInOne}, + thingConstructors: {DocumentSortingRule}, + }) => ({ + title: `Process sorting rules file`, + file: SORTING_RULE_DATA_FILE, + + documentMode: allInOne, + documentThing: document => + (document['Sort Documents'] + ? DocumentSortingRule + : null), + }); + + check(opts) { + return this.constructor.check(this, opts); + } + + apply(opts) { + return this.constructor.apply(this, opts); + } + + static check(rule, opts) { + const result = this.apply(rule, {...opts, dry: true}); + if (!result) return true; + if (!result.changed) return true; + return false; + } + + static async apply(_rule, _opts) { + throw new Error(`Not implemented`); + } + + static async* applyAll(_rules, _opts) { + throw new Error(`Not implemented`); + } + + static async* go({dataPath, wikiData, dry}) { + const rules = wikiData.sortingRules; + const constructors = unique(rules.map(rule => rule.constructor)); + + for (const constructor of constructors) { + yield* constructor.applyAll( + rules + .filter(rule => rule.active) + .filter(rule => rule.constructor === constructor), + {dataPath, wikiData, dry}); + } + } +} diff --git a/src/data/things/sorting-rule/ThingSortingRule.js b/src/data/things/sorting-rule/ThingSortingRule.js new file mode 100644 index 00000000..b5cc76dc --- /dev/null +++ b/src/data/things/sorting-rule/ThingSortingRule.js @@ -0,0 +1,83 @@ +import {V} from '#composite'; +import Thing from '#thing'; +import {isStringNonEmpty, strictArrayOf} from '#validators'; + +import { + compareCaseLessSensitive, + sortByDate, + sortByDirectory, + sortByName, +} from '#sort'; + +import {exposeConstant} from '#composite/control-flow'; + +import {SortingRule} from './SortingRule.js'; + +export class ThingSortingRule extends SortingRule { + static [Thing.getPropertyDescriptors] = () => ({ + // Update & expose + + properties: { + flags: {update: true, expose: true}, + update: { + validate: strictArrayOf(isStringNonEmpty), + }, + }, + + // Expose only + + isThingSortingRule: exposeConstant(V(true)), + }); + + static [Thing.yamlDocumentSpec] = { + fields: { + 'By Properties': {property: 'properties'}, + }, + }; + + sort(sortable) { + if (this.properties) { + for (const property of this.properties.toReversed()) { + const get = thing => thing[property]; + const lc = property.toLowerCase(); + + if (lc.endsWith('date')) { + sortByDate(sortable, {getDate: get}); + continue; + } + + if (lc.endsWith('directory')) { + sortByDirectory(sortable, {getDirectory: get}); + continue; + } + + if (lc.endsWith('name')) { + sortByName(sortable, {getName: get}); + continue; + } + + const values = sortable.map(get); + + if (values.every(v => typeof v === 'string')) { + sortable.sort((a, b) => + compareCaseLessSensitive(get(a), get(b))); + continue; + } + + if (values.every(v => typeof v === 'number')) { + sortable.sort((a, b) => get(a) - get(b)); + continue; + } + + sortable.sort((a, b) => + (get(a).toString() < get(b).toString() + ? -1 + : get(a).toString() > get(b).toString() + ? +1 + : 0)); + } + } + + return sortable; + } +} diff --git a/src/data/things/sorting-rule/index.js b/src/data/things/sorting-rule/index.js new file mode 100644 index 00000000..7b83bd44 --- /dev/null +++ b/src/data/things/sorting-rule/index.js @@ -0,0 +1,3 @@ +export * from './SortingRule.js'; +export * from './ThingSortingRule.js'; +export * from './DocumentSortingRule.js'; |