wallet-core/node_modules/concordance/lib/pluginRegistry.js
2017-08-14 05:02:09 +02:00

223 lines
6.0 KiB
JavaScript

'use strict'
const semver = require('semver')
const pkg = require('../package.json')
const constants = require('./constants')
const object = require('./complexValues/object')
const lineBuilder = require('./lineBuilder')
const formatUtils = require('./formatUtils')
const itemDescriptor = require('./metaDescriptors/item')
const propertyDescriptor = require('./metaDescriptors/property')
const stringDescriptor = require('./primitiveValues/string')
const recursorUtils = require('./recursorUtils')
const themeUtils = require('./themeUtils')
const API_VERSION = 1
const CONCORDANCE_VERSION = pkg.version
const descriptorRegistry = new Map()
const registry = new Map()
class PluginError extends Error {
constructor (message, plugin) {
super(message)
this.name = 'PluginError'
this.plugin = plugin
}
}
class PluginTypeError extends TypeError {
constructor (message, plugin) {
super(message)
this.name = 'PluginTypeError'
this.plugin = plugin
}
}
class UnsupportedApiError extends PluginError {
constructor (plugin) {
super('Plugin requires an unsupported API version', plugin)
this.name = 'UnsupportedApiError'
}
}
class UnsupportedError extends PluginError {
constructor (plugin) {
super('Plugin does not support this version of Concordance', plugin)
this.name = 'UnsupportedError'
}
}
class DuplicateDescriptorTagError extends PluginError {
constructor (tag, plugin) {
super(`Could not add descriptor: tag ${String(tag)} has already been registered`, plugin)
this.name = 'DuplicateDescriptorTagError'
this.tag = tag
}
}
class DuplicateDescriptorIdError extends PluginError {
constructor (id, plugin) {
const printed = typeof id === 'number'
? `0x${id.toString(16).toUpperCase()}`
: String(id)
super(`Could not add descriptor: id ${printed} has already been registered`, plugin)
this.name = 'DuplicateDescriptorIdError'
this.id = id
}
}
function verify (plugin) {
if (typeof plugin.name !== 'string' || !plugin.name) {
throw new PluginTypeError('Plugin must have a `name`', plugin)
}
if (plugin.apiVersion !== API_VERSION) {
throw new UnsupportedApiError(plugin)
}
if ('minimalConcordanceVersion' in plugin) {
if (!semver.valid(plugin.minimalConcordanceVersion)) {
throw new PluginTypeError('If specified, `minimalConcordanceVersion` must be a valid SemVer version', plugin)
}
const range = `>=${plugin.minimalConcordanceVersion}`
if (!semver.satisfies(CONCORDANCE_VERSION, range)) {
throw new UnsupportedError(plugin)
}
}
}
// Selectively expose descriptor tags.
const publicDescriptorTags = Object.freeze({
complexItem: itemDescriptor.complexTag,
primitiveItem: itemDescriptor.primitiveTag,
primitiveProperty: propertyDescriptor.primitiveTag,
string: stringDescriptor.tag
})
// Don't expose `setDefaultGutter()`.
const publicLineBuilder = Object.freeze({
buffer: lineBuilder.buffer,
first: lineBuilder.first,
last: lineBuilder.last,
line: lineBuilder.line,
single: lineBuilder.single,
actual: Object.freeze({
buffer: lineBuilder.actual.buffer,
first: lineBuilder.actual.first,
last: lineBuilder.actual.last,
line: lineBuilder.actual.line,
single: lineBuilder.actual.single
}),
expected: Object.freeze({
buffer: lineBuilder.expected.buffer,
first: lineBuilder.expected.first,
last: lineBuilder.expected.last,
line: lineBuilder.expected.line,
single: lineBuilder.expected.single
})
})
function modifyTheme (descriptor, modifier) {
themeUtils.addModifier(descriptor, modifier)
return descriptor
}
function add (plugin) {
verify(plugin)
const name = plugin.name
if (registry.has(name)) return registry.get(name)
const id2deserialize = new Map()
const tag2id = new Map()
const addDescriptor = (id, tag, deserialize) => {
if (id2deserialize.has(id)) throw new DuplicateDescriptorIdError(id, plugin)
if (descriptorRegistry.has(tag) || tag2id.has(tag)) throw new DuplicateDescriptorTagError(tag, plugin)
id2deserialize.set(id, deserialize)
tag2id.set(tag, id)
}
const tryDescribeValue = plugin.register({
// Concordance makes assumptions about when AMBIGUOUS occurs. Do not expose
// it to plugins.
UNEQUAL: constants.UNEQUAL,
SHALLOW_EQUAL: constants.SHALLOW_EQUAL,
DEEP_EQUAL: constants.DEEP_EQUAL,
ObjectValue: object.ObjectValue,
DescribedMixin: object.DescribedMixin,
DeserializedMixin: object.DeserializedMixin,
addDescriptor,
applyThemeModifiers: themeUtils.applyModifiers,
descriptorTags: publicDescriptorTags,
lineBuilder: publicLineBuilder,
mapRecursor: recursorUtils.map,
modifyTheme,
wrapFromTheme: formatUtils.wrap
})
const registered = {
id2deserialize,
serializerVersion: plugin.serializerVersion,
name,
tag2id,
theme: plugin.theme || {},
tryDescribeValue
}
registry.set(name, registered)
for (const tag of tag2id.keys()) {
descriptorRegistry.set(tag, registered)
}
return registered
}
exports.add = add
function getDeserializers (plugins) {
return plugins.map(plugin => {
const registered = add(plugin)
return {
id2deserialize: registered.id2deserialize,
name: registered.name,
serializerVersion: registered.serializerVersion
}
})
}
exports.getDeserializers = getDeserializers
function getThemes (plugins) {
return plugins.map(plugin => {
const registered = add(plugin)
return {
name: registered.name,
theme: registered.theme
}
})
}
exports.getThemes = getThemes
function getTryDescribeValues (plugins) {
return plugins.map(plugin => add(plugin).tryDescribeValue)
}
exports.getTryDescribeValues = getTryDescribeValues
function resolveDescriptorRef (tag) {
if (!descriptorRegistry.has(tag)) return null
const registered = descriptorRegistry.get(tag)
return {
id: registered.tag2id.get(tag),
name: registered.name,
serialization: {
serializerVersion: registered.serializerVersion
}
}
}
exports.resolveDescriptorRef = resolveDescriptorRef