overwatch-api-server
Version:
An Unoffical Overwatch HTTP API
224 lines (186 loc) • 7.28 kB
JavaScript
'use strict'
const constants = require('../constants')
const lineBuilder = require('../lineBuilder')
const recursorUtils = require('../recursorUtils')
const themeUtils = require('../themeUtils')
const DEEP_EQUAL = constants.DEEP_EQUAL
const UNEQUAL = constants.UNEQUAL
const SHALLOW_EQUAL = constants.SHALLOW_EQUAL
function describe (keyDescriptor, valueDescriptor) {
const keyIsPrimitive = keyDescriptor.isPrimitive === true
const valueIsPrimitive = valueDescriptor.isPrimitive === true
return new MapEntry(keyDescriptor, valueDescriptor, keyIsPrimitive, valueIsPrimitive)
}
exports.describe = describe
function deserialize (state, recursor) {
const keyIsPrimitive = state[0]
const valueIsPrimitive = state[1]
const keyDescriptor = recursor()
const valueDescriptor = recursor()
return new MapEntry(keyDescriptor, valueDescriptor, keyIsPrimitive, valueIsPrimitive)
}
exports.deserialize = deserialize
const tag = Symbol('MapEntry')
exports.tag = tag
function mergeWithKey (theme, key, values) {
const lines = lineBuilder.buffer()
const keyRemainder = lineBuilder.buffer()
for (const line of key) {
if (!line.isLast && !line.hasGutter) {
lines.append(line)
} else {
keyRemainder.append(line)
}
}
for (const value of values) {
lines.append(keyRemainder.mergeWithInfix(theme.mapEntry.separator, value).withLastPostfixed(theme.mapEntry.after))
}
return lines
}
class MapEntry {
constructor (key, value, keyIsPrimitive, valueIsPrimitive) {
this.key = key
this.value = value
this.keyIsPrimitive = keyIsPrimitive
this.valueIsPrimitive = valueIsPrimitive
}
createRecursor () {
let emitKey = true
let emitValue = true
return () => {
if (emitKey) {
emitKey = false
return this.key
}
if (emitValue) {
emitValue = false
return this.value
}
return null
}
}
compare (expected) {
if (this.tag !== expected.tag) return UNEQUAL
if (this.keyIsPrimitive !== expected.keyIsPrimitive) return UNEQUAL
if (this.valueIsPrimitive !== expected.valueIsPrimitive) return UNEQUAL
if (!this.keyIsPrimitive) return SHALLOW_EQUAL
const keyResult = this.key.compare(expected.key)
if (keyResult !== DEEP_EQUAL) return keyResult
if (!this.valueIsPrimitive) return SHALLOW_EQUAL
return this.value.compare(expected.value)
}
formatDeep (theme, indent) {
// Verify the map entry can be formatted directly.
if (!this.keyIsPrimitive || typeof this.value.formatDeep !== 'function') return null
// Since formatShallow() would result in theme modifiers being applied
// before the key and value are formatted, do the same here.
const value = this.value.formatDeep(themeUtils.applyModifiersToOriginal(this.value, theme), indent)
if (value === null) return null
const key = this.key.formatDeep(themeUtils.applyModifiersToOriginal(this.key, theme), indent)
return mergeWithKey(theme, key, [value])
}
formatShallow (theme, indent) {
let key = null
const values = []
return {
append: (formatted, origin) => {
if (this.key === origin) {
key = formatted
} else {
values.push(formatted)
}
},
finalize () {
return mergeWithKey(theme, key, values)
}
}
}
diffDeep (expected, theme, indent) {
// Verify a diff can be returned.
if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null
// Only use this logic to format value diffs when the keys are primitive and equal.
if (!this.keyIsPrimitive || !expected.keyIsPrimitive || this.key.compare(expected.key) !== DEEP_EQUAL) {
return null
}
// Since formatShallow() would result in theme modifiers being applied
// before the key and value are formatted, do the same here.
const diff = this.value.diffDeep(expected.value, themeUtils.applyModifiersToOriginal(this.value, theme), indent)
if (diff === null) return null
const key = this.key.formatDeep(themeUtils.applyModifiersToOriginal(this.key, theme), indent, '')
return mergeWithKey(theme, key, [diff])
}
prepareDiff (expected, lhsRecursor, rhsRecursor, compareComplexShape, isCircular) {
// Circular values cannot be compared. They must be treated as being unequal when diffing.
if (isCircular(this.value) || isCircular(expected.value)) return {compareResult: UNEQUAL}
const compareResult = this.compare(expected)
const keysAreEqual = this.tag === expected.tag && this.key.compare(expected.key) === DEEP_EQUAL
// Short-circuit when keys and/or values are deeply equal.
if (compareResult === DEEP_EQUAL || keysAreEqual) return {compareResult}
// Try to line up this or remaining map entries with the expected entries.
const lhsFork = recursorUtils.fork(lhsRecursor)
const rhsFork = recursorUtils.fork(rhsRecursor)
const initialExpected = expected
let expectedIsMissing = false
while (!expectedIsMissing && expected !== null && this.tag === expected.tag) {
if (expected.keyIsPrimitive) {
expectedIsMissing = this.key.compare(expected.key) !== UNEQUAL
} else {
expectedIsMissing = compareComplexShape(this.key, expected.key) !== UNEQUAL
}
expected = rhsFork.shared()
}
let actualIsExtraneous = false
if (this.tag === initialExpected.tag) {
if (initialExpected.keyIsPrimitive) {
let actual = this
while (!actualIsExtraneous && actual !== null && this.tag === actual.tag) {
if (actual.keyIsPrimitive) {
actualIsExtraneous = initialExpected.key.compare(actual.key) === DEEP_EQUAL
}
actual = lhsFork.shared()
}
} else {
let actual = this
while (!actualIsExtraneous && actual !== null && this.tag === actual.tag) {
if (!actual.keyIsPrimitive) {
actualIsExtraneous = compareComplexShape(actual.key, initialExpected.key) !== UNEQUAL
}
actual = lhsFork.shared()
}
}
}
if (actualIsExtraneous && !expectedIsMissing) {
return {
actualIsExtraneous: true,
lhsRecursor: lhsFork.recursor,
rhsRecursor: recursorUtils.unshift(rhsFork.recursor, initialExpected)
}
}
if (expectedIsMissing && !actualIsExtraneous) {
return {
expectedIsMissing: true,
lhsRecursor: recursorUtils.unshift(lhsFork.recursor, this),
rhsRecursor: rhsFork.recursor
}
}
let mustRecurse = false
if (!this.keyIsPrimitive && !initialExpected.keyIsPrimitive) {
if (this.valueIsPrimitive || initialExpected.valueIsPrimitive) {
mustRecurse = this.value.compare(initialExpected.value) !== UNEQUAL
} else {
mustRecurse = compareComplexShape(this.value, initialExpected.value) !== UNEQUAL
}
}
return {
mustRecurse,
isUnequal: !mustRecurse,
lhsRecursor: lhsFork.recursor,
rhsRecursor: rhsFork.recursor
}
}
serialize () {
return [this.keyIsPrimitive, this.valueIsPrimitive]
}
}
Object.defineProperty(MapEntry.prototype, 'isMapEntry', { value: true })
Object.defineProperty(MapEntry.prototype, 'tag', { value: tag })