UNPKG

3.98 kBJavaScriptView Raw
1'use strict'
2
3var _eval = require('../_eval')
4
5/**
6 * @typedef {string[]} Mixin~Removal
7 */
8
9/**
10 * @class
11 */
12function Mixin() {
13 /** @member {Obj} */
14 this.additions = null
15
16 /** @member {Mixin~Removal[]} */
17 this.removals = []
18
19 /** The base path components
20 * @member {string[]}
21 */
22 this.base = []
23}
24
25/**
26 * Execute and return the result for the parsed Mixin
27 * @param {Object} context
28 * @param {string} name A string like '<' + description + '>' to be part of a thrown execption
29 * @returns {*}
30 * @throws if not parsed
31 */
32Mixin.prototype.execute = function (context, name) {
33 var base, i, additions
34
35 // Execute base
36 base = _eval(this.base[0], context, name)
37 for (i = 1; i < this.base.length; i++) {
38 if (!base || typeof base !== 'object') {
39 throw new Error('Expected ' + this.base.slice(0, i).join('.') + ' to be a non-null object in ' + name)
40 }
41 base = base[this.base[i]]
42 }
43 base = copyDeep(base)
44
45 // Apply modifications
46 this.removals.forEach(function (path) {
47 remove(base, path)
48 })
49
50 if (this.additions) {
51 additions = this.additions.execute(context, name + '.<with>')
52 Object.keys(additions).forEach(function (path) {
53 add(base, additions[path], path.split('.').map(function (each) {
54 return /^[0-9]+$/.test(each) ? Number(each) : each
55 }))
56 }, this)
57 }
58
59 return base
60}
61
62/**
63 * @param {*} x
64 * @returns {*}
65 * @private
66 */
67function copyDeep(x) {
68 var r, key
69 if (Array.isArray(x)) {
70 return x.map(copyDeep)
71 } else if (x && typeof x === 'object' &&
72 (x.constructor === Object || !Object.getPrototypeOf(x))) {
73 // Map
74 r = Object.create(null)
75 for (key in x) {
76 r[key] = copyDeep(x[key])
77 }
78 return r
79 } else {
80 return x
81 }
82}
83
84/**
85 * Remove a path from an object
86 * @param {Object} obj
87 * @param {Array<string|number>} path
88 * @param {number} [i]
89 * @throws {Error}
90 * @private
91 */
92function remove(obj, path, i) {
93 i = i || 0
94
95 var key = path[i],
96 last = i === path.length - 1
97
98 if (!obj || typeof obj !== 'object') {
99 throw new Error('Can\'t remove key ' + key + ' from non-object')
100 }
101
102 if (Array.isArray(obj)) {
103 if (typeof key !== 'number') {
104 obj.forEach(function (each) {
105 remove(each, path, i)
106 })
107 } else if (key >= 0 && key < obj.length) {
108 if (last) {
109 obj.splice(key, 1)
110 } else {
111 remove(obj[key], path, i + 1)
112 }
113 } else {
114 throw new Error('Can\'t remove index ' + key + ' from an array with ' + obj.length + ' elements')
115 }
116 } else {
117 if (typeof key !== 'string') {
118 throw new Error('Can\'t remove the numeric key ' + key + ' from an object')
119 } else if (key in obj) {
120 if (last) {
121 delete obj[key]
122 } else {
123 remove(obj[key], path, i + 1)
124 }
125 } else {
126 throw new Error('Can\'t remove key ' + key + ' from the object')
127 }
128 }
129}
130
131/**
132 * Add/update a path off an object
133 * @param {!Object} obj
134 * @param {Array<string|number>} path
135 * @param {*} value
136 * @param {number} [i]
137 * @throws {Error}
138 */
139function add(obj, value, path, i) {
140 i = i || 0
141
142 var key = path[i],
143 last = i === path.length - 1
144
145 if (!obj || typeof obj !== 'object') {
146 throw new Error('Can\'t add key ' + key + ' to non-object')
147 }
148
149 if (Array.isArray(obj)) {
150 if (typeof key !== 'number') {
151 obj.forEach(function (each) {
152 add(each, value, path, i)
153 })
154 } else if (key >= 0 && key <= obj.length) {
155 if (last) {
156 obj[key] = value
157 } else {
158 add(obj[key], value, path, i + 1)
159 }
160 } else {
161 throw new Error('Can\'t add index ' + key + ' to an array with ' + obj.length + ' elements')
162 }
163 } else {
164 if (typeof key !== 'string') {
165 throw new Error('Can\'t add the numeric key ' + key + ' to an object')
166 } else {
167 if (last) {
168 obj[key] = value
169 } else {
170 if (!(key in obj)) {
171 obj[key] = Object.create(null)
172 }
173 add(obj[key], value, path, i + 1)
174 }
175 }
176 }
177}
178
179module.exports = Mixin
\No newline at end of file