-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Add the
Placeholder
and Trail
class to the Develry namespace
- Loading branch information
Showing
4 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/** | ||
* A class that should resolve to another value. | ||
* | ||
* @constructor | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
*/ | ||
const Placeholder = Fn.inherits(null, 'Develry', function Placeholder() {}); | ||
|
||
// This is an abstract class | ||
Placeholder.makeAbstractClass(); | ||
|
||
/** | ||
* Resolve all the placeholders in the given object. | ||
* The original object will not be modified. | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Object} input The object that contains the trails | ||
* | ||
* @return {*} | ||
*/ | ||
Placeholder.setStatic(function resolve(input, ...args) { | ||
|
||
if (input instanceof Placeholder) { | ||
return input.resolve(...args); | ||
} | ||
|
||
const seen = new Map(); | ||
return _resolvePlaceholders(input, seen, args); | ||
}); | ||
|
||
/** | ||
* Actual trail resolving | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Object} input The object that contains the trails | ||
* @param {Map} seen The seen objects | ||
* @param {Array} args | ||
* | ||
* @return {*} | ||
*/ | ||
const _resolvePlaceholders = (input, seen, args) => { | ||
|
||
if (seen.has(input)) { | ||
return seen.get(input); | ||
} | ||
|
||
if (input instanceof Placeholder) { | ||
return input.resolve(...args); | ||
} | ||
|
||
let result, | ||
is_array = Array.isArray(input), | ||
is_plain = Obj.isPlainObject(input); | ||
|
||
// Only map arrays and plain objects | ||
if (!is_array && !is_plain) { | ||
result = input; | ||
} else { | ||
result = is_array ? [] : {}; | ||
seen.set(input, result); | ||
|
||
if (is_array) { | ||
let i; | ||
|
||
for (i = 0; i < input.length; i++) { | ||
result[i] = _resolvePlaceholders(input[i], seen, args); | ||
} | ||
} else { | ||
let key; | ||
|
||
for (key in input) { | ||
if (!Object.hasOwnProperty.call(obj, key)) { | ||
continue; | ||
} | ||
|
||
result[key] = _resolvePlaceholders(input[key], seen, args); | ||
} | ||
} | ||
} | ||
|
||
seen.set(input, result); | ||
|
||
return result; | ||
}; | ||
|
||
/** | ||
* Each Placeholder class should have a `resolve` method | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @return {*} | ||
*/ | ||
Placeholder.setAbstractMethod('resolve'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
const CHAIN = Symbol('chain'), | ||
GET_OR_EVALUATE = Symbol('getOrEvaluate'); | ||
|
||
/** | ||
* Class that represents a path | ||
* | ||
* @constructor | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Array} chain | ||
*/ | ||
const Trail = Fn.inherits('Develry.Placeholder', function Trail(chain) { | ||
|
||
if (!(this instanceof Trail)) { | ||
return new Trail(chain); | ||
} | ||
|
||
// The path pieces | ||
this[CHAIN] = chain; | ||
|
||
// Is this a root path? | ||
this.is_root = chain[0] === ''; | ||
|
||
// If it is a root path, remove the first piece | ||
if (this.is_root) { | ||
this[CHAIN].shift(); | ||
} | ||
}); | ||
|
||
/** | ||
* Undry the value | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Object} value | ||
* | ||
* @return {Trail} | ||
*/ | ||
Trail.setStatic(function unDry(value) { | ||
let result = Object.create(this.prototype); | ||
result[CHAIN] = value.chain; | ||
result.is_root = value.is_root; | ||
return result; | ||
}); | ||
|
||
/** | ||
* Create a trail using the given delimiter | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {string|string[]} path | ||
* @param {string} delimiter | ||
* | ||
* @return {Trail} | ||
*/ | ||
Trail.setStatic(function fromDelimiter(path, delimiter = '.') { | ||
|
||
if (!Array.isArray(path)) { | ||
if (path.includes('\\')) { | ||
let escaped_pieces = path.split('\\' + delimiter), | ||
split_pieces, | ||
i; | ||
|
||
path = []; | ||
|
||
for (i = 0; i < escaped_pieces.length; i++) { | ||
split_pieces = escaped_pieces[i].split(delimiter); | ||
|
||
if (i > 0) { | ||
path[path.length-1] += delimiter + split_pieces.shift(); | ||
} | ||
|
||
if (split_pieces.length) { | ||
path.push(...split_pieces); | ||
} | ||
} | ||
} else { | ||
path = path.split(delimiter); | ||
} | ||
} | ||
|
||
return new Trail(path); | ||
}); | ||
|
||
/** | ||
* Create a trail from a dot-separated string | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {string} path | ||
*/ | ||
Trail.setStatic(function fromDot(path) { | ||
return Trail.fromDelimiter(path, '.'); | ||
}); | ||
|
||
/** | ||
* Create a trail from an arrow-separated string | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {string} path | ||
*/ | ||
Trail.setStatic(function fromArrow(path) { | ||
return Trail.fromDelimiter(path, '->'); | ||
}); | ||
|
||
/** | ||
* Create a trail from a slash-separated string | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {string} path | ||
*/ | ||
Trail.setStatic(function fromSlash(path) { | ||
return Trail.fromDelimiter(path, '/'); | ||
}); | ||
|
||
/** | ||
* Return the serialized json-dry representation | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
*/ | ||
Trail.setMethod(function toDry() { | ||
|
||
let value = { | ||
chain : this[CHAIN], | ||
}; | ||
|
||
if (this.is_root) { | ||
value.is_root = true; | ||
} | ||
|
||
return { | ||
value: value, | ||
}; | ||
}); | ||
|
||
/** | ||
* Get the value of the given path. | ||
* If `evaluate` is true and the last piece is a function, it will be called. | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Object} context | ||
* @param {boolean} evaluate | ||
* | ||
* @return {*} | ||
*/ | ||
Trail.setMethod([GET_OR_EVALUATE], function internalGetOrEvaluate(context, evaluate = false) { | ||
|
||
if (!context) { | ||
return; | ||
} | ||
|
||
const chain = this[CHAIN], | ||
length = chain.length, | ||
last = length - 1; | ||
|
||
let value, | ||
index, | ||
key; | ||
|
||
for (index = 0; index < length; index++) { | ||
|
||
if (context == null) { | ||
return; | ||
} | ||
|
||
key = chain[index]; | ||
value = context[key]; | ||
|
||
if (value == null) { | ||
return; | ||
} | ||
|
||
if (evaluate && index == last && typeof value == 'function') { | ||
value = context[key](); | ||
} else { | ||
context = value; | ||
} | ||
} | ||
|
||
return value; | ||
}); | ||
|
||
/** | ||
* Resolve the value of this path. | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Object} context | ||
* | ||
* @return {*} | ||
*/ | ||
Trail.setMethod(function resolve(context) { | ||
return this.extract(context); | ||
}); | ||
|
||
/** | ||
* Extract the actual value of this path. | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Object} context | ||
* | ||
* @return {*} | ||
*/ | ||
Trail.setMethod(function extract(context) { | ||
|
||
if (arguments.length == 0) { | ||
context = Blast.Globals; | ||
} | ||
|
||
return this[GET_OR_EVALUATE](context, false); | ||
}); | ||
|
||
/** | ||
* Extract the actual value of this path. | ||
* Evaluate the last piece if it's a function. | ||
* | ||
* @author Jelle De Loecker <[email protected]> | ||
* @since 0.9.0 | ||
* @version 0.9.0 | ||
* | ||
* @param {Object} context | ||
* | ||
* @return {*} | ||
*/ | ||
Trail.setMethod(function extractOrEvaluate(context) { | ||
|
||
if (arguments.length == 0) { | ||
context = Blast.Globals; | ||
} | ||
|
||
return this[GET_OR_EVALUATE](context, true); | ||
}); |