diff options
Diffstat (limited to 'src/thing')
-rw-r--r-- | src/thing/album.js | 70 | ||||
-rw-r--r-- | src/thing/thing.js | 44 | ||||
-rw-r--r-- | src/thing/track.js | 112 | ||||
-rw-r--r-- | src/thing/validators.js | 47 |
4 files changed, 238 insertions, 35 deletions
diff --git a/src/thing/album.js b/src/thing/album.js index 9899b6af..11af8019 100644 --- a/src/thing/album.js +++ b/src/thing/album.js @@ -1,4 +1,6 @@ +import CacheableObject from './cacheable-object.js'; import Thing from './thing.js'; +import find from '../util/find.js'; import { isBoolean, @@ -12,21 +14,70 @@ import { isURL, isString, validateArrayItems, + validateInstanceOf, validateReference, validateReferenceList, } from './validators.js'; -export default class Album extends Thing { +export class TrackGroup extends CacheableObject { static propertyDescriptors = { // Update & expose name: { flags: {update: true, expose: true}, + update: {default: 'Unnamed Track Group', validate: isName} + }, - update: { - default: 'Unnamed Album', - validate: isName + color: { + flags: {update: true, expose: true}, + update: {validate: isColor} + }, + + dateOriginallyReleased: { + flags: {update: true, expose: true}, + update: {validate: isDate} + }, + + tracksByRef: { + flags: {update: true, expose: true}, + update: {validate: validateReferenceList('track')} + }, + + isDefaultTrackGroup: { + flags: {update: true, expose: true}, + update: {validate: isBoolean} + }, + + // Update only + + trackData: { + flags: {update: true}, + update: {validate: validateArrayItems(item => isInstance(item, Track))} + }, + + // Expose only + + tracks: { + flags: {expose: true}, + + expose: { + dependencies: ['tracksByRef', 'trackData'], + compute: ({ tracksByRef, trackData }) => ( + tracksByRef.map(ref => find.track(ref, {wikiData: {trackData}}))) } + } + }; +} + +export default class Album extends Thing { + static [Thing.referenceType] = 'album'; + + static propertyDescriptors = { + // Update & expose + + name: { + flags: {update: true, expose: true}, + update: {default: 'Unnamed Album', validate: isName} }, color: { @@ -36,7 +87,8 @@ export default class Album extends Thing { directory: { flags: {update: true, expose: true}, - update: {validate: isDirectory} + update: {validate: isDirectory}, + expose: Thing.directoryExpose }, urls: { @@ -109,11 +161,11 @@ export default class Album extends Thing { } }, - tracksByRef: { + trackGroups: { flags: {update: true, expose: true}, update: { - validate: validateReferenceList('track') + validate: validateArrayItems(validateInstanceOf(TrackGroup)) } }, @@ -176,6 +228,7 @@ export default class Album extends Thing { // Expose only + /* tracks: { flags: {expose: true}, @@ -185,11 +238,14 @@ export default class Album extends Thing { trackReferences.map(ref => find.track(ref, {wikiData}))) } }, + */ // Update only + /* wikiData: { flags: {update: true} } + */ }; } diff --git a/src/thing/thing.js b/src/thing/thing.js index dd3126c1..54a278d1 100644 --- a/src/thing/thing.js +++ b/src/thing/thing.js @@ -1,30 +1,32 @@ // Base class for Things. No, we will not come up with a better name. // Sorry not sorry! :) -// -// NB: Since these methods all involve processing a variety of input data, some -// of which will pass and some of which may fail, any failures should be thrown -// together as an AggregateError. See util/sugar.js for utility functions to -// make writing code around this easier! import CacheableObject from './cacheable-object.js'; +import { getKebabCase } from '../util/wiki-data.js'; + export default class Thing extends CacheableObject { - static propertyDescriptors = Symbol('Thing property descriptors'); + static referenceType = Symbol('Thing.referenceType'); + + static directoryExpose = { + dependencies: ['name'], + transform(directory, { name }) { + if (directory === null && name === null) + return null; + else if (directory === null) + return getKebabCase(name); + else + return directory; + } + }; + + static getReference(thing) { + if (!thing.constructor[Thing.referenceType]) + throw TypeError(`Passed Thing is ${thing.constructor.name}, which provides no [Thing.referenceType]`); - // Called when collecting the full list of available things of that type - // for wiki data; this method determine whether or not to include it. - // - // This should return whether or not the object is complete enough to be - // used across the wiki - not whether every optional attribute is provided! - // (That is, attributes required for postprocessing & basic page generation - // are all present.) - checkComplete() {} + if (!thing.directory) + throw TypeError(`Passed ${thing.constructor.name} is missing its directory`); - // Called when adding the thing to the wiki data list, and when its source - // data is updated (provided checkComplete() passes). - // - // This should generate any cached object references, across other wiki - // data; for example, building an array of actual track objects - // corresponding to an album's track list ('track:cool-track' strings). - postprocess({wikiData}) {} + return `${thing.constructor[Thing.referenceType]}:${thing.directory}`; + } } diff --git a/src/thing/track.js b/src/thing/track.js new file mode 100644 index 00000000..3174cabb --- /dev/null +++ b/src/thing/track.js @@ -0,0 +1,112 @@ +import Thing from './thing.js'; + +import { + isBoolean, + isColor, + isCommentary, + isContributionList, + isDate, + isDirectory, + isDuration, + isName, + isURL, + isString, + validateArrayItems, + validateReference, + validateReferenceList, +} from './validators.js'; + +export default class Track extends Thing { + static [Thing.referenceType] = 'track'; + + static propertyDescriptors = { + // Update & expose + + name: { + flags: {update: true, expose: true}, + + update: { + default: 'Unnamed Track', + validate: isName + } + }, + + directory: { + flags: {update: true, expose: true}, + update: {validate: isDirectory}, + expose: Thing.directoryExpose + }, + + duration: { + flags: {update: true, expose: true}, + update: {validate: isDuration} + }, + + urls: { + flags: {update: true, expose: true}, + + update: { + validate: validateArrayItems(isURL) + } + }, + + dateFirstReleased: { + flags: {update: true, expose: true}, + update: {validate: isDate} + }, + + hasCoverArt: { + flags: {update: true, expose: true}, + update: {default: true, validate: isBoolean} + }, + + hasURLs: { + flags: {update: true, expose: true}, + update: {default: true, validate: isBoolean} + }, + + referencedTracksByRef: { + flags: {update: true, expose: true}, + update: {validate: validateReferenceList('track')} + }, + + artistContribsByRef: { + flags: {update: true, expose: true}, + update: {validate: isContributionList} + }, + + contributorContribsByRef: { + flags: {update: true, expose: true}, + update: {validate: isContributionList} + }, + + coverArtistContribsByRef: { + flags: {update: true, expose: true}, + update: {validate: isContributionList} + }, + + artTagsByRef: { + flags: {update: true, expose: true}, + update: {validate: validateReferenceList('tag')} + }, + + originalReleaseTrackByRef: { + flags: {update: true, expose: true}, + update: {validate: validateReference('track')} + }, + + commentary: { + flags: {update: true, expose: true}, + update: {validate: isCommentary} + }, + + lyrics: { + flags: {update: true, expose: true}, + update: {validate: isString} + }, + + // Update only + + // Expose only + }; +} diff --git a/src/thing/validators.js b/src/thing/validators.js index 2bdb2995..e745771a 100644 --- a/src/thing/validators.js +++ b/src/thing/validators.js @@ -47,6 +47,24 @@ export function isNegative(number) { return true; } +export function isPositiveOrZero(number) { + isNumber(number); + + if (number < 0) + throw new TypeError(`Expected positive number or zero`); + + return true; +} + +export function isNegativeOrZero(number) { + isNumber(number); + + if (number > 0) + throw new TypeError(`Expected negative number or zero`); + + return true; +} + export function isInteger(number) { isNumber(number); @@ -130,6 +148,10 @@ export function validateArrayItems(itemValidator) { }; } +export function validateInstanceOf(constructor) { + return object => isInstance(object, constructor); +} + // Wiki data (primitives & non-primitives) export function isColor(color) { @@ -187,8 +209,15 @@ export function isDimensions(dimensions) { export function isDirectory(directory) { isStringNonEmpty(directory); - if (directory.match(/[^a-zA-Z0-9\-]/)) - throw new TypeError(`Expected only letters, numbers, and dash, got "${directory}"`); + if (directory.match(/[^a-zA-Z0-9_\-]/)) + throw new TypeError(`Expected only letters, numbers, dash, and underscore, got "${directory}"`); + + return true; +} + +export function isDuration(duration) { + isNumber(duration); + isPositiveOrZero(duration); return true; } @@ -209,13 +238,17 @@ export function validateReference(type = 'track') { return ref => { isStringNonEmpty(ref); - const hasTwoParts = ref.includes(':'); - const [ typePart, directoryPart ] = ref.split(':'); + const match = ref.trim().match(/^(?:(?<typePart>\S+):(?=\S))?(?<directoryPart>.+)(?<!:)$/); + + if (!match) + throw new TypeError(`Malformed reference`); + + const { groups: { typePart, directoryPart } } = match; - if (hasTwoParts && typePart !== type) - throw new TypeError(`Expected ref to begin with "${type}:", got "${typePart}:" (ref: ${ref})`); + if (typePart && typePart !== type) + throw new TypeError(`Expected ref to begin with "${type}:", got "${typePart}:"`); - if (hasTwoParts) + if (typePart) isDirectory(directoryPart); isName(ref); |