UNPKG

enum

Version:

Enum is a javascript module that introduces the Enum Type. It works for node.js, in the browser and for deno.

356 lines (308 loc) 10.1 kB
import EnumItem from './enumItem.js' import { isString, isNumber } from './isType.js' import { indexOf } from './indexOf.js' const isBuffer = (obj) => { return obj != null && obj.constructor != null && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) } /** * Returns a string identifying the endianness of the CPU for which the Deno * binary was compiled. Possible values are 'BE' for big endian and 'LE' for * little endian. **/ const getEndianess = () => { // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView#Endianness const buffer = new ArrayBuffer(2) new DataView(buffer).setInt16(0, 256, true /* littleEndian */) // Int16Array uses the platform's endianness. return new Int16Array(buffer)[0] === 256 ? 'LE' : 'BE' } const endianness = getEndianess() /** * Represents an Enum with enum items. * @param {Array || Object} map This are the enum items. * @param {String || Object} options This are options. [optional] */ export default class Enum { constructor (map, options) { /* implement the "ref type interface", so that Enum types can * be used in `node-ffi` function declarations and invokations. * In C, these Enums act as `uint32_t` types. * * https://github.com/TooTallNate/ref#the-type-interface */ this.size = 4 this.indirection = 1 if (options && isString(options)) { options = { name: options } } this._options = options || {} this._options.separator = this._options.separator || ' | ' this._options.endianness = this._options.endianness || endianness this._options.ignoreCase = this._options.ignoreCase || false this._options.freez = this._options.freez || false // backword compatability this._options.freeze = this._options.freeze || this._options.freez || false this.enums = [] if (map.length) { this._enumLastIndex = map.length var array = map map = {} for (var i = 0; i < array.length; i++) { map[array[i]] = Math.pow(2, i) } } for (var member in map) { guardReservedKeys(this._options.name, member) this[member] = new EnumItem(member, map[member], { ignoreCase: this._options.ignoreCase }) this.enums.push(this[member]) } this._enumMap = map if (this._options.ignoreCase) { this.getLowerCaseEnums = function () { var res = {} for (var i = 0, len = this.enums.length; i < len; i++) { res[this.enums[i].key.toLowerCase()] = this.enums[i] } return res } } if (this._options.name) { this.name = this._options.name } const isFlaggable = () => { for (var i = 0, len = this.enums.length; i < len; i++) { var e = this.enums[i] if (!(e.value !== 0 && !(e.value & e.value - 1))) { return false } } return true } this.isFlaggable = isFlaggable() if (this._options.freeze) { this.freezeEnums() // this will make instances of Enum non-extensible } } /** * Returns the appropriate EnumItem key. * @param {EnumItem || String || Number} key The object to get with. * @return {String} The get result. */ getKey (value) { var item = this.get(value) if (item) { return item.key } } /** * Returns the appropriate EnumItem value. * @param {EnumItem || String || Number} key The object to get with. * @return {Number} The get result. */ getValue (key) { var item = this.get(key) if (item) { return item.value } } /** * Returns the appropriate EnumItem. * @param {EnumItem || String || Number} key The object to get with. * @return {EnumItem} The get result. */ get (key, offset) { if (key === null || key === undefined) { return } // Buffer instance support, part of the ref Type interface if (isBuffer(key)) { key = key['readUInt32' + this._options.endianness](offset || 0) } if (EnumItem.isEnumItem(key)) { var foundIndex = indexOf.call(this.enums, key) if (foundIndex >= 0) { return key } if (!this.isFlaggable || (this.isFlaggable && key.key.indexOf(this._options.separator) < 0)) { return } return this.get(key.key) } else if (isString(key)) { var enums = this if (this._options.ignoreCase) { enums = this.getLowerCaseEnums() key = key.toLowerCase() } if (key.indexOf(this._options.separator) > 0) { var parts = key.split(this._options.separator) var value = 0 for (var i = 0; i < parts.length; i++) { var part = parts[i] value |= enums[part].value } return new EnumItem(key, value) } else { return enums[key] } } else { for (var m in this) { // eslint-disable-next-line no-prototype-builtins if (this.hasOwnProperty(m)) { if (this[m].value === key) { return this[m] } } } var result = null if (this.isFlaggable) { for (var n in this) { // eslint-disable-next-line no-prototype-builtins if (this.hasOwnProperty(n)) { if ((key & this[n].value) !== 0) { if (result) { result += this._options.separator } else { result = '' } result += n } } } } return this.get(result || null) } } /** * Sets the Enum "value" onto the give `buffer` at the specified `offset`. * Part of the ref "Type interface". * * @param {Buffer} buffer The Buffer instance to write to. * @param {Number} offset The offset in the buffer to write to. Default 0. * @param {EnumItem || String || Number} value The EnumItem to write. */ set (buffer, offset, value) { var item = this.get(value) if (item) { return buffer['writeUInt32' + this._options.endianness](item.value, offset || 0) } } /** * Define freezeEnums() as a property of the prototype. * make enumerable items nonconfigurable and deep freeze the properties. Throw Error on property setter. */ freezeEnums () { function envSupportsFreezing () { return ( Object.isFrozen && Object.isSealed && Object.getOwnPropertyNames && Object.getOwnPropertyDescriptor && Object.defineProperties && Object.__defineGetter__ && Object.__defineSetter__ ) } function freezer (o) { var props = Object.getOwnPropertyNames(o) props.forEach(function (p) { if (!Object.getOwnPropertyDescriptor(o, p).configurable) { return } Object.defineProperties(o, p, { writable: false, configurable: false }) }) return o } function getPropertyValue (value) { return value } function deepFreezeEnums (o) { if (typeof o !== 'object' || o === null || Object.isFrozen(o) || Object.isSealed(o)) { return } for (var key in o) { // eslint-disable-next-line no-prototype-builtins if (o.hasOwnProperty(key)) { o.__defineGetter__(key, getPropertyValue.bind(null, o[key])) o.__defineSetter__(key, function throwPropertySetError (value) { throw TypeError('Cannot redefine property; Enum Type is not extensible.') }) deepFreezeEnums(o[key]) } } if (Object.freeze) { Object.freeze(o) } else { freezer(o) } } if (envSupportsFreezing()) { deepFreezeEnums(this) } return this } /** * Return true whether the enumItem parameter passed in is an EnumItem object and * has been included as constant of this Enum * @param {EnumItem} enumItem */ isDefined (enumItem) { let condition = (e) => e === enumItem if (isString(enumItem) || isNumber(enumItem)) { condition = (e) => e.is(enumItem) } return this.enums.some(condition) } /** * Returns JSON object representation of this Enum. * @return {String} JSON object representation of this Enum. */ toJSON () { return this._enumMap } /** * Extends the existing Enum with a New Map. * @param {Array} map Map to extend from */ extend (map) { if (map.length) { var array = map map = {} for (var i = 0; i < array.length; i++) { var exponent = this._enumLastIndex + i map[array[i]] = Math.pow(2, exponent) } for (var member in map) { guardReservedKeys(this._options.name, member) this[member] = new EnumItem(member, map[member], { ignoreCase: this._options.ignoreCase }) this.enums.push(this[member]) } for (var key in this._enumMap) { map[key] = this._enumMap[key] } this._enumLastIndex += map.length this._enumMap = map if (this._options.freeze) { this.freezeEnums() // this will make instances of new Enum non-extensible } } }; /** * Registers the Enum Type globally in node.js. * @param {String} key Global variable. [optional] */ static register (key = 'Enum') { if (typeof global !== 'undefined' && !global[key]) { global[key] = Enum } else if (typeof window !== 'undefined' && !window[key]) { window[key] = Enum } } [Symbol.iterator] () { let index = 0 return { next: () => index < this.enums.length ? { done: false, value: this.enums[index++] } : { done: true } } } }; // private var reservedKeys = ['_options', 'get', 'getKey', 'getValue', 'enums', 'isFlaggable', '_enumMap', 'toJSON', '_enumLastIndex'] function guardReservedKeys (customName, key) { if ((customName && key === 'name') || indexOf.call(reservedKeys, key) >= 0) { throw new Error(`Enum key ${key} is a reserved word!`) } }