This repository was archived by the owner on Jul 29, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathindex.es6.js
More file actions
125 lines (100 loc) · 3.61 KB
/
index.es6.js
File metadata and controls
125 lines (100 loc) · 3.61 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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
const DEFAULT_FEATURE_PARSE_PREFIX = 'feature_';
class Flags {
// Default the context to empty, for sanity.
ctx = {};
// helper to make loading config easier. Take config, only load keys
// beginning with feature_, and lowercase. The parsing function is
// overridable.
static parseConfig (config, parsefn=Flags.parse) {
return Object.keys(config).reduce((o, key) => {
const parsed = parsefn(key, config[key]);
if (parsed) {
o[parsed.key] = parsed.val;
}
return o;
}, {});
}
static parse (key, val) {
const k = key.toLowerCase();
if (k.indexOf(DEFAULT_FEATURE_PARSE_PREFIX) === -1) { return; }
return { key: k.slice(DEFAULT_FEATURE_PARSE_PREFIX.length), val };
}
// Build a new instance. In most cases, you only pass in config that sets
// experiments up; sometimes, you may prefer to also pass rules in rather
// than calling addRule. Passing in ctx is really only to be used internally
// by withContext.
constructor (featureConfig, rules={}, ctx={}) {
this.config = featureConfig;
this.rules = rules;
this.ctx = ctx;
}
// Add a new rule
addRule (name, fn) {
this.rules[name] = fn;
}
testRule (config, rule, ctx) {
// If there's no rule, check for boolean operator pseudo-rules.
if (!this.rules[rule]) {
return this.tryBooleanOps(config, rule, ctx);
}
// If there is a rule, return if it failed.
return this.rules[rule].call(ctx, config[rule]);
};
// Check for boolean operator pseudo-rules.
tryBooleanOps (config, rule, ctx) {
if (rule === 'or') {
return config[rule].some(subConfig =>
Object.keys(subConfig).some(subRule => this.testRule(subConfig, subRule, ctx))
)
}
if (rule === 'and') {
return config[rule].every(subConfig =>
Object.keys(subConfig).some(subRule => this.testRule(subConfig, subRule, ctx))
)
}
if (rule === 'not') {
const subConfig = config[rule];
return !(Object.keys(subConfig).some(subRule => this.testRule(subConfig, subRule, ctx)));
}
// Otherwise, return false.
return false;
}
// Check if your flags are on a thing
enabled (name, ctx=this.ctx) {
const config = this.config[name];
// If there's no config for the feature we're checking for, assume false.
if (!config) { return false; }
// If the flag is a boolean, just return it.
if (config === true || config === false) {
return config;
}
// Otherwise, get the list of rules from the config.
const rules = Object.keys(config);
// Check if any of the rules fail. Use `find`, which exits as immiediately
// as possible.
const pass = rules.some((r) => this.testRule(config, r, ctx));
// Return whether any of the rules failed
return pass;
}
// Loop through all configured features and return a string array of what
// features _would_ return true for a given context (or are disabled if
// enabled = false.)
allEnabled (ctx=this.ctx, enabled=true) {
return Object.keys(this.config).filter((configName) => {
return this.enabled(configName, ctx) === enabled;
});
}
// Loop through all configured features and return a string array of what
// features _would not_ return true for a given context.
allDisabled (ctx=this.ctx) {
return this.allEnabled(ctx, false);
}
// Create a new, context-bound flags instance for easy calling later on.
withContext (ctx) {
return new Flags(this.config, this.rules, ctx);
}
clone (config=this.config, rules=this.rules, ctx=this.ctx) {
return new Flags({ ...config}, { ...rules }, { ...ctx });
}
}
export default Flags;