-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathobject-descender.js
176 lines (164 loc) · 5.94 KB
/
object-descender.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
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/**
* A simple solution for descending into an object to retrieve
* a value from a given location.
*
* @example
* const Od = require('object-descender');
* // Create descender with an existing object
* const od = new Od({"a": "A's value", "b": {"b2": "B2's value", "child": {"key": "B2's child's key's value"}}});
* // Retrieve the value of "a"
* od.get('a'); // "A's value"
* // Retrieve the value of "b2". Periods are used to indicate
* // descent into the object, so b2 is at b.b2
* od.get('b.b2'); // "B2's value"
* // "d" doesn't exist, but we tell the object_descender to give
* // us the default value of 2 when the key does not exist.
* od.get("d", 2); // 2
* // It doesn't matter what the value of a key is. Here, we return
* // the entire object stored inside "b"
* od.get('b'); // {"b2": "B2's value", "child": {"key": "B2's child's key's value"}}
* // Go several levels deep
* od.get('b.child.key'); // "B2's child's key's value"
* // Attempt to retrieve a non-existent key without specifying a
* // default return value
* od.get('no_such_key'); // Returns an Error() object
*
* @example
* // Load a .yaml file, rather than providing an object
* const Od = require('object-descender');
* const od = new Od('my_file.yaml');
*
* @example
* // Throw an exception on a missing key, regardless of whether
* // or not a default value was supplied.
* const Od = require('object-descender');
* const od = new Od({});
* od.throw("Could not find key 'my_key.my_other_key'").get('my_key.my_other_key', 'my_value');
*
* @example
* // Throw an exception with a custom message when requested key
* // is not found. Not that we are assigning the result of get()
* // to the variable 'result.' Without throw() being invoked,
* // we would simply wind up with result containing an Error()
* // object. Throw() causes an actual exception to be raised,
* // and assigning the result to a variable will not stop that
* // exception from interrupting program flow. That is the
* // purpose of throw(). If you use throw(), you will need to
* // use try/catch blocks to process the exceptions if you do
* // not want them to crash your program.
* const Od = require('object-descender');
* const od = new Od({});
* var result = od.throw("'%key%' not found in supplied object.").get('desired_key');
*
* @alias object_descender
* @constructor
* @param {string|object} object_or_filename
*/
module.exports = function object_descender(object_or_filename) {
/**
* @type {object}
* @description Holds the object which we will search for keys
* using the get() method.
*/
this.data_object = {};
if (
Object.prototype.toString.call(
object_or_filename
) === '[object Object]'
) {
this.data_object = object_or_filename;
} else if (typeof object_or_filename === 'string') {
const yaml = require('js-yaml');
const fs = require('fs');
this.data_object = yaml.safeLoad(
fs.readFileSync(object_or_filename),
'utf8'
);
} else {
throw new Error(
"Object descender expects a filename or a Javascript object as its only argument."
);
}
/**
* When set, an exception will be thrown when get() fails
* to find the desired key.
*
* @type {bool}
*/
this.throw_on_failure = false;
/**
* Holds the message set by throw().
*
* @type {string}
*/
this.throw_on_failure_message = null;
/**
* Turns on the throw_on_failure flag. This flag is disabled in
* the get() function, and is therefore good for only one call
* to get().
*
* @param {string} message Message for thrown Error. %key% in message
* will be replaced with the key get() failed to locate.
*/
this.throw = function(message) {
this.throw_on_failure = true;
this.throw_on_failure_message = message ? message : "Unable to locate key '%key%'";
return this;
};
/**
* Returns the value of the given key, if found. Otherwise, returns
* the value specified by default_value, or an instance of Error
* if no default value is specified.
*
* @example
* const od = new(require('object_descender'))();
* od.get('first_key.second_key.third_key.desired_key');
*
* @param {string} key A string consisting of a dot-separated path to
* the desired value.
* @param {*} default_value The value to return if the specified
* key is not found.
*/
this.get = function (key, default_value) {
let key_value = (
default_value === undefined ?
new Error('Key \'' + key + '\' not found.') :
default_value
);
if (key && this.data_object) {
let path = key.split('.');
let config_position = this.data_object;
let found;
while ((path_piece = path.shift(path))) {
found = false;
if (config_position == null || !config_position.hasOwnProperty(path_piece)) {
break;
}
config_position = config_position[path_piece];
found = true;
}
if (path.length === 0 && found === true) {
key_value = config_position;
}
else if (this.throw_on_failure === true) {
const message_to_throw =
this.throw_on_failure_message.toString().replace('%key%', key);
this.throw_on_failure = false;
this.throw_on_failure_message = null;
throw new Error(message_to_throw);
}
}
this.throw_on_failure = false;
this.throw_on_failure_message = null;
return key_value;
};
/**
* Set the data for this instance of object-descender
*
* @param {*} data
*/
this.data = function(data) {
this.data_object = data;
return this;
}
};