Skip to content

Commit

Permalink
✨ Add the Placeholder and Trail class to the Develry namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
skerit committed Feb 10, 2024
1 parent 20c9be1 commit d21602e
Show file tree
Hide file tree
Showing 4 changed files with 364 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Add `Blast.didInitialLoad()` method
* Make `Blast.executeAfterLoadingCycle()` keep `did_initial_load` variable alone
* Add `String.parseQuoted(input)` to parse json-like string values
* Add the `Placeholder` and `Trail` class to the Develry namespace

## 0.8.18 (2024-01-19)

Expand Down
2 changes: 2 additions & 0 deletions lib/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,8 @@ function BlastInit(modifyPrototype) {
'AbstractNumeric',
'Decimal',
'Error',
'Placeholder',
'Trail',
'Informer',
'State',
'Request',
Expand Down
104 changes: 104 additions & 0 deletions lib/placeholder.js
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');
257 changes: 257 additions & 0 deletions lib/trail.js
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);
});

0 comments on commit d21602e

Please sign in to comment.