-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathThing.js
More file actions
104 lines (90 loc) · 3.63 KB
/
Thing.js
File metadata and controls
104 lines (90 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
export class Thing {
constructor(name) {
const define = (name, getValue, target = this, enumerable = false) => Object.defineProperty(target, name, {
get: () => getValue,
enumerable: enumerable
});
const getProxy = (handler, target = {}) => new Proxy(target, handler);
const generateBooleanProperties = (value, thisValue = this) => getProxy({
get: (_, name) => {
define(`is_a_${name}`, value, thisValue);
define(`is_not_a_${name}`, !value, thisValue);
return thisValue;
}
});
const generatePropertyWithValue = (thisValue = this) => getProxy({
get: (target, name) => (target[name] = getProxy({
get: (_, nameValue) => {
define(name, nameValue, thisValue, true);
return getProxy({
get: (_, nextPropName) => nextPropName === 'and_the'
? generatePropertyWithValue(thisValue)
: thisValue[nextPropName]
});
}
}))
});
const generateHasMethod = handler => getProxy({
apply: (_, thisValue, args) => {
return getProxy({
get: (_, name) => {
return handler.call(thisValue, name, args[0] || 0);
}
});
}
}, new Function());
const getSafeFunctionResult = (thisValue, callback, args = []) => {
const thisPropsKeys = Object.getOwnPropertyNames(thisValue)
.filter(prop => thisValue.propertyIsEnumerable(prop));
const newFn = new Function(...thisPropsKeys, `return ${callback.toString()}`);
return newFn.apply(thisValue, [...thisPropsKeys.map(prop => thisValue[prop])])(...args);
};
const generateNestedItemsCallback = () => getProxy({
apply: (_, thisValue, args) => {
const getFnBody = fnSrt => `this.${fnSrt.toString()
.replace(/^[^{]*{\s*[return]*/, '').replace(/\s*}[^}]*$/, '')
.split('=>').pop().trim()}`;
thisValue.map(thing => getProxy({
apply: (target, thisValue) => new Function('h', getFnBody(target)).call(thisValue)
}, args[0]).call(thing) && thing);
}
}, new Function());
define('name', name, this, true);
define('is_a', generateBooleanProperties(true));
define('is_not_a', generateBooleanProperties(false));
define('is_the', generatePropertyWithValue());
define('has', generateHasMethod((propName, itemsNumber) => {
const getNestedItem = (propName) => {
const childThing = new Thing(propName);
define('with', getProxy({}, childThing.having, new Function()), childThing);
define('being_the', generatePropertyWithValue(childThing), childThing);
return childThing;
};
if (itemsNumber > 1) {
this[propName] = [...Array(itemsNumber)].map(() => getNestedItem(propName.slice(0, -1)));
define('each', generateNestedItemsCallback(), this[propName]);
return this[propName];
}
return (this[propName] = getNestedItem(propName));
}));
define('having', getProxy({}, this.has, new Function()));
define('can', getProxy({
get: (target, name) => getProxy({
apply: (_, thisValue, args) => {
let callsHistory = [];
const argsCopy = [...args];
const callback = argsCopy.pop();
if (argsCopy.length) {
argsCopy.forEach(methodName => define(methodName, callsHistory));
}
define(name, getProxy({
apply: (_, thisValue, args) => {
const callResult = getSafeFunctionResult(thisValue, callback, args);
return callsHistory.push(callResult) && callResult;
}
}, new Function));
}
}, new Function())
}));
}
}