script/lib/mode-collection.ts (80 lines of code) (raw):
import chalk from 'chalk'
import flatMap from 'lodash/flatMap'
import isNumber from 'lodash/isNumber'
import isString from 'lodash/isString'
import VariableCollection, {ModeVariable} from './variable-collection'
export default class ModeCollection {
public readonly type: string
public readonly prefix: string
public readonly modes: Map<string, VariableCollection> = new Map()
constructor(type: string, prefix: string) {
this.type = type
this.prefix = prefix
}
public add(modeName: string, vars: VariableCollection) {
this.modes.set(modeName, vars)
}
public validate(): {isValid: boolean; errors: string[]} {
const errors = []
// Ensure that every mode in this collection has the same variables defined
const missingVarsPerMode = this.getMissingVarsPerMode()
if (missingVarsPerMode.length > 0) {
errors.push(`\n> The following variables are missing in one or more modes:\n`)
for (const {modes, variableName} of missingVarsPerMode) {
const msg = chalk`* Variable {bold.red ${variableName}} is missing in modes: ${modes
.map(mode => chalk.bold.bgBlack.white(mode.name))
.join(', ')}`
errors.push(msg)
}
}
// Ensure that all variables variables are defined
const invalidVars = this.getInvalidVars()
if (invalidVars.length > 0) {
errors.push(`\n> The following variables are invalid:\n`)
for (const {variable, mode} of invalidVars) {
const msg = chalk`* {bold.red ${variable.value}} cannot be assigned to {bold.bgBlack.white ${variable.name}} in mode {bold.bgBlack.white ${mode.name}} `
errors.push(msg)
}
errors.push(`\nCheck to make sure variable references do not create an infinite loop.`)
}
return {isValid: errors.length === 0, errors}
}
public [Symbol.iterator]() {
return this.modes[Symbol.iterator]()
}
public finalize() {
for (const key of this.modes.keys()) {
const mode = this.modes.get(key)!
if (mode.parent) {
const parentMode = this.modes.get(mode.parent)!
mode.merge(parentMode)
}
}
}
private getMissingVarsPerMode(): {modes: VariableCollection[]; variableName: string}[] {
if (this.modes.size === 1) {
return []
}
const result: {modes: VariableCollection[]; variableName: string}[] = []
const modes = [...this.modes.values()]
const allVarNames = flatMap(modes, mode => {
return mode.flattened().map(v => v.name)
})
const uniqueVarNames = [...new Set(allVarNames)].sort()
for (const v of uniqueVarNames.values()) {
const missingModes = modes.filter(mode => mode.getByName(v) === undefined)
if (missingModes.length > 0) {
result.push({modes: missingModes, variableName: v})
}
}
return result
}
private getInvalidVars(): {mode: VariableCollection; variable: ModeVariable}[] {
const result: {mode: VariableCollection; variable: ModeVariable}[] = []
for (const [_name, mode] of this.modes) {
const invalidVars = mode.flattened().filter(v => !(isNumber(v.value) || isString(v.value)))
for (const v of invalidVars) {
result.push({mode, variable: v})
}
}
return result
}
}