1 | 'use strict'
|
2 |
|
3 | var _eval = require('../_eval')
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | function Mixin() {
|
13 |
|
14 | this.additions = null
|
15 |
|
16 |
|
17 | this.removals = []
|
18 |
|
19 | |
20 |
|
21 |
|
22 | this.base = []
|
23 | }
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | Mixin.prototype.execute = function (context, name) {
|
33 | var base, i, additions
|
34 |
|
35 |
|
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 |
|
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 |
|
64 |
|
65 |
|
66 |
|
67 | function 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 |
|
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 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | function 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 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | function 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 |
|
179 | module.exports = Mixin |
\ | No newline at end of file |