-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
98 lines (94 loc) · 3.66 KB
/
index.js
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
'use strict';
const multimatch = require('multimatch');
/**
* Checks whether an object has an own property matching the given specification.
* @param {Object} target - the object to examine
* @param {string|Symbol} - the name of the property to examine
* @param {Object} spec - a partial property descriptor to which to compare the property
* @param {boolean} [invert=false] - if true, check for a mismatching own property instead
*/
function hasOwnPropertyMatching(target, property, spec, invert=false) {
const descriptor = Object.getOwnPropertyDescriptor(target, property);
if (!descriptor) return false;
for (let [key, value] of Object.entries(spec)) {
if (descriptor[key] !== value) return invert;
}
return !invert;
}
/**
* Returns a metalsmith plugin wrapping the given plugin and limiting it to the
* given scope.
*/
function scoped(plugin, patterns, multimatchOptions={}) {
if ((typeof plugin) !== 'function') {
// Handle CLI usage
[plugin, patterns, multimatchOptions={}] = plugin;
const [pluginName, pluginArgs] = Object.entries(plugin)[0];
// TODO: should we mirror metalsmith's multi-step require here?
plugin = require(pluginName)(pluginArgs);
}
return function scopedPlugin(files, metalsmith, callback) {
function matches(property) {
return ((typeof property) !== 'symbol'
&& multimatch(property, patterns, multimatchOptions).length === 0);
}
const filesView = new Proxy(files, {
get: function (target, property, receiver) {
if (matches(property) && hasOwnPropertyMatching(target, property, {writable: false, configurable: false}, true)) {
return undefined;
} else {
return Reflect.get(target, property, receiver);
}
},
ownKeys: function (target) {
if (!Object.isExtensible(target)) {
return Reflect.ownKeys(target);
} else {
return Reflect.ownKeys(target).filter(
(key) => !matches(key) || hasOwnPropertyMatching(target, key, {configurable: false})
);
}
},
has: function (target, property) {
if (matches(property) && Object.isExtensible(target)
&& hasOwnPropertyMatching(target, property, {configurable: false}, true)) {
return false;
} else {
return Reflect.has(target, property);
}
},
getOwnPropertyDescriptor: function (target, property) {
if (matches(property) && Object.isExtensible(target)
&& hasOwnPropertyMatching(target, property, {configurable: false}, true)) {
return undefined;
} else {
return Reflect.getOwnPropertyDescriptor(target, property);
}
},
defineProperty: function (target, property, descriptor) {
if (matches(property)) {
throw new Error(`${plugin.name} tried to define ${property}, out of scope ${patterns}`);
} else {
return Reflect.defineProperty(target, property, descriptor);
}
},
set: function (target, property, value, receiver) {
if (matches(property)) {
throw new Error(`${plugin.name} tried to set ${property}, out of scope ${patterns}`);
} else {
return Reflect.set(target, property, value, receiver);
}
},
deleteProperty: function (target, property) {
// TODO: should this return 'true' rather than throw?
if (matches(property)) {
throw new Error(`${plugin.name} tried to delete ${property}, out of scope ${patterns}`);
} else {
return Reflect.deleteProperty(target, property);
}
}
});
plugin(filesView, metalsmith, callback);
}
}
module.exports = scoped;