Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2017, Mike Ralphson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+291
View File
@@ -0,0 +1,291 @@
# RefTools
## Functions
<dl>
<dt><a href="#nop">nop(obj)</a> ⇒</dt>
<dd><p>a no-op placeholder which returns the given object unchanged
useful for when a clone function needs to be passed but cloning is not
required</p>
</dd>
<dt><a href="#clone">clone(obj)</a> ⇒</dt>
<dd><p>clones the given object using JSON.parse and JSON.stringify</p>
</dd>
<dt><a href="#shallowClone">shallowClone(obj)</a> ⇒</dt>
<dd><p>clones the given object&#39;s properties shallowly, ignores properties from prototype</p>
</dd>
<dt><a href="#deepClone">deepClone(obj)</a> ⇒</dt>
<dd><p>clones the given object&#39;s properties deeply, ignores properties from prototype</p>
</dd>
<dt><a href="#fastClone">fastClone(obj)</a> ⇒</dt>
<dd><p>clones the given object&#39;s properties shallowly, using Object.assign</p>
</dd>
<dt><a href="#circularClone">circularClone()</a></dt>
<dd><p>Source: stackoverflow <a href="http://bit.ly/2A1Kha6">http://bit.ly/2A1Kha6</a></p>
</dd>
<dt><a href="#dereference">dereference(o)</a> ⇒</dt>
<dd><p>dereferences the given object</p>
</dd>
<dt><a href="#flatten">flatten(obj, callback)</a> ⇒</dt>
<dd><p>flattens an object into an array of properties</p>
</dd>
<dt><a href="#jpescape">jpescape(s)</a> ⇒</dt>
<dd><p>escapes JSON Pointer using ~0 for ~ and ~1 for /</p>
</dd>
<dt><a href="#jpunescape">jpunescape(s)</a> ⇒</dt>
<dd><p>unescapes JSON Pointer using ~0 for ~ and ~1 for /</p>
</dd>
<dt><a href="#jptr">jptr(obj, prop, newValue)</a> ⇒</dt>
<dd><p>from obj, return the property with a JSON Pointer prop, optionally setting it
to newValue</p>
</dd>
<dt><a href="#recurse">recurse(object, state, callback)</a></dt>
<dd><p>recurses through the properties of an object, given an optional starting state
anything you pass in state.payload is passed to the callback each time</p>
</dd>
<dt><a href="#reref">reref(obj, options)</a> ⇒</dt>
<dd><p>Simply modifies an object to have no self-references by replacing them
with $ref pointers</p>
</dd>
<dt><a href="#objToGraph">objToGraph(obj, containerName)</a> ⇒</dt>
<dd><p>Takes an object and creates a graph of JSON Pointer / References</p>
</dd>
<dt><a href="#visit">visit(obj, comparison, callbacks)</a> ⇒</dt>
<dd><p>Given an expanded object and an optional object to compare to (e.g. its $ref&#39;d form), will call
the following functions:</p>
<ul>
<li>callbacks.before - lets you modify the initial starting state, must return it</li>
<li>callbacks.where - lets you select a subset of properties, return a truthy value</li>
<li>callbacks.filter - called for all selected properties, can mutate/remove (by setting to undefined)</li>
<li>callbacks.compare - allowing the objects to be compared by path (i.e. for $ref reinstating)</li>
<li>callbacks.identity - called on any object identity (previously seen) properties</li>
<li>callbacks.selected - called for all selected/unfiltered properties, does not mutate directly</li>
<li>callbacks.count - called at the end with the number of selected properties</li>
<li>callbacks.finally - called at the end of the traversal</li>
</ul>
</dd>
</dl>
## Typedefs
<dl>
<dt><a href="#Result">Result</a> ⇒ <code><a href="#Result">Result</a></code></dt>
<dd><p>Try to get a topological sorting out of directed graph.</p>
</dd>
</dl>
<a name="nop"></a>
## nop(obj) ⇒
a no-op placeholder which returns the given object unchanged
useful for when a clone function needs to be passed but cloning is not
required
**Kind**: global function
**Returns**: the input object, unchanged
| Param | Description |
| --- | --- |
| obj | the input object |
<a name="clone"></a>
## clone(obj) ⇒
clones the given object using JSON.parse and JSON.stringify
**Kind**: global function
**Returns**: the cloned object
| Param | Description |
| --- | --- |
| obj | the object to clone |
<a name="shallowClone"></a>
## shallowClone(obj) ⇒
clones the given object's properties shallowly, ignores properties from prototype
**Kind**: global function
**Returns**: the cloned object
| Param | Description |
| --- | --- |
| obj | the object to clone |
<a name="deepClone"></a>
## deepClone(obj) ⇒
clones the given object's properties deeply, ignores properties from prototype
**Kind**: global function
**Returns**: the cloned object
| Param | Description |
| --- | --- |
| obj | the object to clone |
<a name="fastClone"></a>
## fastClone(obj) ⇒
clones the given object's properties shallowly, using Object.assign
**Kind**: global function
**Returns**: the cloned object
| Param | Description |
| --- | --- |
| obj | the object to clone |
<a name="circularClone"></a>
## circularClone()
Source: stackoverflow http://bit.ly/2A1Kha6
**Kind**: global function
<a name="dereference"></a>
## dereference(o) ⇒
dereferences the given object
**Kind**: global function
**Returns**: the dereferenced object
**Definitions**: a source of definitions to reference
**Options**: optional settings (used recursively)
| Param | Description |
| --- | --- |
| o | the object to dereference |
<a name="flatten"></a>
## flatten(obj, callback) ⇒
flattens an object into an array of properties
**Kind**: global function
**Returns**: the flattened object as an array of properties
| Param | Description |
| --- | --- |
| obj | the object to flatten |
| callback | a function which can mutate or filter the entries (by returning null) |
<a name="jpescape"></a>
## jpescape(s) ⇒
escapes JSON Pointer using ~0 for ~ and ~1 for /
**Kind**: global function
**Returns**: the escaped string
| Param | Description |
| --- | --- |
| s | the string to escape |
<a name="jpunescape"></a>
## jpunescape(s) ⇒
unescapes JSON Pointer using ~0 for ~ and ~1 for /
**Kind**: global function
**Returns**: the unescaped string
| Param | Description |
| --- | --- |
| s | the string to unescape |
<a name="jptr"></a>
## jptr(obj, prop, newValue) ⇒
from obj, return the property with a JSON Pointer prop, optionally setting it
to newValue
**Kind**: global function
**Returns**: the found property, or false
| Param | Description |
| --- | --- |
| obj | the object to point into |
| prop | the JSON Pointer or JSON Reference |
| newValue | optional value to set the property to |
<a name="recurse"></a>
## recurse(object, state, callback)
recurses through the properties of an object, given an optional starting state
anything you pass in state.payload is passed to the callback each time
**Kind**: global function
| Param | Description |
| --- | --- |
| object | the object to recurse through |
| state | optional starting state, can be set to null or |
| callback | the function which receives object,key,state on each property |
<a name="reref"></a>
## reref(obj, options) ⇒
Simply modifies an object to have no self-references by replacing them
with $ref pointers
**Kind**: global function
**Returns**: the re-referenced object (mutated)
| Param | Description |
| --- | --- |
| obj | the object to re-reference |
| options | may contain a prefix property for the generated refs |
<a name="objToGraph"></a>
## objToGraph(obj, containerName) ⇒
Takes an object and creates a graph of JSON Pointer / References
**Kind**: global function
**Returns**: the graph suitable for passing to toposort()
| Param | Description |
| --- | --- |
| obj | the object to convert |
| containerName | the property containing definitions. Default: definitions |
<a name="visit"></a>
## visit(obj, comparison, callbacks) ⇒
Given an expanded object and an optional object to compare to (e.g. its $ref'd form), will call
the following functions:
* callbacks.before - lets you modify the initial starting state, must return it
* callbacks.where - lets you select a subset of properties, return a truthy value
* callbacks.filter - called for all selected properties, can mutate/remove (by setting to undefined)
* callbacks.compare - allowing the objects to be compared by path (i.e. for $ref reinstating)
* callbacks.identity - called on any object identity (previously seen) properties
* callbacks.selected - called for all selected/unfiltered properties, does not mutate directly
* callbacks.count - called at the end with the number of selected properties
* callbacks.finally - called at the end of the traversal
**Kind**: global function
**Returns**: the possibly mutated object
| Param | Description |
| --- | --- |
| obj | the object to visit |
| comparison | optional object to compare to |
| callbacks | object containing functions as above |
<a name="Result"></a>
## Result ⇒ [<code>Result</code>](#Result)
Try to get a topological sorting out of directed graph.
**Kind**: global typedef
| Param | Type | Description |
| --- | --- | --- |
| nodes | <code>Object</code> | A list of nodes, including edges (see below). |
**Properties**
| Name | Type | Description |
| --- | --- | --- |
| sort | <code>array</code> | the sort, empty if not found |
| nodesWithEdges, | <code>array</code> | will be empty unless a cycle is found |
+102
View File
@@ -0,0 +1,102 @@
'use strict';
/**
* a collection of cloning functions
*/
/**
* a no-op placeholder which returns the given object unchanged
* useful for when a clone function needs to be passed but cloning is not
* required
* @param obj the input object
* @return the input object, unchanged
*/
function nop(obj) {
return obj;
}
/**
* clones the given object using JSON.parse and JSON.stringify
* @param obj the object to clone
* @return the cloned object
*/
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
/**
* clones the given object's properties shallowly, ignores properties from prototype
* @param obj the object to clone
* @return the cloned object
*/
function shallowClone(obj) {
let result = {};
for (let p in obj) {
if (obj.hasOwnProperty(p)) {
result[p] = obj[p];
}
}
return result;
}
/**
* clones the given object's properties deeply, ignores properties from prototype
* @param obj the object to clone
* @return the cloned object
*/
function deepClone(obj) {
let result = Array.isArray(obj) ? [] : {};
for (let p in obj) {
if (obj.hasOwnProperty(p) || Array.isArray(obj)) {
result[p] = (typeof obj[p] === 'object') ? deepClone(obj[p]) : obj[p];
}
}
return result;
}
/**
* clones the given object's properties shallowly, using Object.assign
* @param obj the object to clone
* @return the cloned object
*/
function fastClone(obj) {
return Object.assign({},obj);
}
/**
* Source: stackoverflow http://bit.ly/2A1Kha6
*/
function circularClone(obj, hash) {
if (!hash) hash = new WeakMap();
// Do not try to clone primitives or functions
if (Object(obj) !== obj || obj instanceof Function) return obj;
if (hash.has(obj)) return hash.get(obj); // Cyclic reference
try { // Try to run constructor (without arguments, as we don't know them)
var result = new obj.constructor();
} catch(e) { // Constructor failed, create object without running the constructor
result = Object.create(Object.getPrototypeOf(obj));
}
// Optional: support for some standard constructors (extend as desired)
/*if (obj instanceof Map)
Array.from(obj, ([key, val]) => result.set(circularClone(key, hash),
circularClone(val, hash)) );
else if (obj instanceof Set)
Array.from(obj, (key) => result.add(circularClone(key, hash)) );
*/
// Register in hash
hash.set(obj, result);
// Clone and assign enumerable own properties recursively
return Object.assign(result, ...Object.keys(obj).map (
key => ({ [key]: circularClone(obj[key], hash) }) ));
}
module.exports = {
nop : nop,
clone : clone,
shallowClone : shallowClone,
deepClone : deepClone,
fastClone : fastClone,
circularClone : circularClone
};
+105
View File
@@ -0,0 +1,105 @@
'use strict';
const recurse = require('./recurse.js').recurse;
const clone = require('./clone.js').shallowClone;
const jptr = require('./jptr.js').jptr;
const isRef = require('./isref.js').isRef;
var getLogger = function (options) {
if (options && options.verbose) {
return {
warn: function() {
var args = Array.prototype.slice.call(arguments);
console.warn.apply(console, args);
}
}
}
else {
return {
warn: function() {
//nop
}
}
}
}
/**
* dereferences the given object
* @param o the object to dereference
* @definitions a source of definitions to reference
* @options optional settings (used recursively)
* @return the dereferenced object
*/
function dereference(o,definitions,options) {
if (!options) options = {};
if (!options.cache) options.cache = {};
if (!options.state) options.state = {};
options.state.identityDetection = true;
// options.depth allows us to limit cloning to the first invocation
options.depth = (options.depth ? options.depth+1 : 1);
let obj = (options.depth > 1 ? o : clone(o));
let container = { data: obj };
let defs = (options.depth > 1 ? definitions : clone(definitions));
// options.master is the top level object, regardless of depth
if (!options.master) options.master = obj;
let logger = getLogger(options);
let changes = 1;
while (changes > 0) {
changes = 0;
recurse(container,options.state,function(obj,key,state){
if (isRef(obj,key)) {
let $ref = obj[key]; // immutable
changes++;
if (!options.cache[$ref]) {
let entry = {};
entry.path = state.path.split('/$ref')[0];
entry.key = $ref;
logger.warn('Dereffing %s at %s',$ref,entry.path);
entry.source = defs;
entry.data = jptr(entry.source,entry.key);
if (entry.data === false) {
entry.data = jptr(options.master,entry.key);
entry.source = options.master;
}
if (entry.data === false) {
logger.warn('Missing $ref target',entry.key);
}
options.cache[$ref] = entry;
entry.data = state.parent[state.pkey] = dereference(jptr(entry.source,entry.key),entry.source,options);
if (options.$ref && (typeof state.parent[state.pkey] === 'object') && (state.parent[state.pkey] !== null)) state.parent[state.pkey][options.$ref] = $ref;
entry.resolved = true;
}
else {
let entry = options.cache[$ref];
if (entry.resolved) {
// we have already seen and resolved this reference
logger.warn('Patching %s for %s',$ref,entry.path);
state.parent[state.pkey] = entry.data;
if (options.$ref && (typeof state.parent[state.pkey] === 'object') && (state.parent[state.pkey] !== null)) state.parent[state.pkey][options.$ref] = $ref;
}
else if ($ref === entry.path) {
// reference to itself, throw
throw new Error(`Tight circle at ${entry.path}`);
}
else {
// we're dealing with a circular reference here
logger.warn('Unresolved ref');
state.parent[state.pkey] = jptr(entry.source,entry.path);
if (state.parent[state.pkey] === false) {
state.parent[state.pkey] = jptr(entry.source,entry.key);
}
if (options.$ref && (typeof state.parent[state.pkey] === 'object') && (state.parent[state.pkey] !== null)) state.parent[options.$ref] = $ref;
}
}
}
});
}
return container.data;
}
module.exports = {
dereference : dereference
};
+21
View File
@@ -0,0 +1,21 @@
'use strict';
const recurse = require('./recurse.js').recurse;
function findObj(container,obj) {
if (container === obj) {
return { found: true, path: '#/', parent: null };
}
let result = { found: false, path: null, parent: null };
recurse(container,{},function(o,key,state){
if ((o[key] === obj) && (!result.found)) {
result = { found: true, path: state.path, parent: o };
}
});
return result;
}
module.exports = {
findObj: findObj
};
+41
View File
@@ -0,0 +1,41 @@
'use strict';
const recurse = require('./recurse.js').recurse;
/**
* flattens an object into an array of properties
* @param obj the object to flatten
* @param callback a function which can mutate or filter the entries (by returning null)
* @return the flattened object as an array of properties
*/
function flatten(obj,callback) {
let arr = [];
let iDepth, oDepth = 0;
let state = {identityDetection:true};
recurse(obj,state,function(obj,key,state){
let entry = {};
entry.name = key;
entry.value = obj[key];
entry.path = state.path;
entry.parent = obj;
entry.key = key;
if (callback) entry = callback(entry);
if (entry) {
if (state.depth > iDepth) {
oDepth++;
}
else if (state.depth < iDepth) {
oDepth--;
}
entry.depth = oDepth;
iDepth = state.depth;
arr.push(entry);
}
});
return arr;
}
module.exports = {
flatten : flatten
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
function isRef(obj,key) {
return ((key === '$ref') && (!!obj && typeof obj[key] === 'string'));
}
module.exports = {
isRef: isRef
};
+97
View File
@@ -0,0 +1,97 @@
'use strict';
/**
* escapes JSON Pointer using ~0 for ~ and ~1 for /
* @param s the string to escape
* @return the escaped string
*/
function jpescape(s) {
return s.replace(/\~/g, '~0').replace(/\//g, '~1');
}
/**
* unescapes JSON Pointer using ~0 for ~ and ~1 for /
* @param s the string to unescape
* @return the unescaped string
*/
function jpunescape(s) {
return s.replace(/\~1/g, '/').replace(/~0/g, '~');
}
// JSON Pointer specification: http://tools.ietf.org/html/rfc6901
/**
* from obj, return the property with a JSON Pointer prop, optionally setting it
* to newValue
* @param obj the object to point into
* @param prop the JSON Pointer or JSON Reference
* @param newValue optional value to set the property to
* @return the found property, or false
*/
function jptr(obj, prop, newValue) {
if (typeof obj === 'undefined') return false;
if (!prop || typeof prop !== 'string' || (prop === '#')) return (typeof newValue !== 'undefined' ? newValue : obj);
if (prop.indexOf('#')>=0) {
let parts = prop.split('#');
let uri = parts[0];
if (uri) return false; // we do internal resolution only
prop = parts[1];
prop = decodeURIComponent(prop.slice(1).split('+').join(' '));
}
if (prop.startsWith('/')) prop = prop.slice(1);
let components = prop.split('/');
for (let i=0;i<components.length;i++) {
components[i] = jpunescape(components[i]);
let setAndLast = (typeof newValue !== 'undefined') && (i == components.length-1);
let index = parseInt(components[i],10);
if (!Array.isArray(obj) || isNaN(index) || (index.toString() !== components[i])) {
index = (Array.isArray(obj) && components[i] === '-') ? -2 : -1;
}
else {
components[i] = (i > 0) ? components[i-1] : ''; // backtrack to indexed property name
}
if ((index != -1) || (obj && obj.hasOwnProperty(components[i]))) {
if (index >= 0) {
if (setAndLast) {
obj[index] = newValue;
}
obj = obj[index];
}
else if (index === -2) {
if (setAndLast) {
if (Array.isArray(obj)) {
obj.push(newValue);
}
return newValue;
}
else return undefined;
}
else {
if (setAndLast) {
obj[components[i]] = newValue;
}
obj = obj[components[i]];
}
}
else {
if ((typeof newValue !== 'undefined') && (typeof obj === 'object') &&
(!Array.isArray(obj))) {
obj[components[i]] = (setAndLast ? newValue : ((components[i+1] === '0' || components[i+1] === '-') ? [] : {}));
obj = obj[components[i]];
}
else return false;
}
}
return obj;
}
module.exports = {
jptr : jptr,
jpescape : jpescape,
jpunescape : jpunescape
};
+62
View File
@@ -0,0 +1,62 @@
'use strict';
const jpescape = require('./jptr.js').jpescape;
function defaultState() {
return {
path: '#',
depth: 0,
pkey: '',
parent: {},
payload: {},
seen: new WeakMap(),
identity: false,
identityDetection: false
};
}
/**
* recurses through the properties of an object, given an optional starting state
* anything you pass in state.payload is passed to the callback each time
* @param object the object to recurse through
* @param state optional starting state, can be set to null or {}
* @param callback the function which receives object,key,state on each property
*/
function recurse(object, state, callback) {
if (!state) state = {depth:0};
if (!state.depth) {
state = Object.assign({},defaultState(),state);
}
if (typeof object !== 'object') return;
let oPath = state.path;
for (let key in object) {
state.key = key;
state.path = state.path + '/' + encodeURIComponent(jpescape(key));
state.identityPath = state.seen.get(object[key]);
state.identity = (typeof state.identityPath !== 'undefined');
if (object.hasOwnProperty(key)) {
callback(object, key, state);
}
if ((typeof object[key] === 'object') && (!state.identity)) {
if (state.identityDetection && !Array.isArray(object[key]) && object[key] !== null) {
state.seen.set(object[key],state.path);
}
let newState = {};
newState.parent = object;
newState.path = state.path;
newState.depth = state.depth ? state.depth+1 : 1;
newState.pkey = key;
newState.payload = state.payload;
newState.seen = state.seen;
newState.identity = false;
newState.identityDetection = state.identityDetection;
recurse(object[key], newState, callback);
}
state.path = oPath;
}
}
module.exports = {
recurse : recurse
};
+34
View File
@@ -0,0 +1,34 @@
'use strict';
const recurse = require('./recurse.js').recurse;
const jptr = require('./jptr.js').jptr;
/**
* Simply modifies an object to have no self-references by replacing them
* with $ref pointers
* @param obj the object to re-reference
* @param options may contain a prefix property for the generated refs
* @return the re-referenced object (mutated)
*/
function reref(obj,options) {
let master = obj;
recurse(obj,{identityDetection:true},function(obj,key,state){
if (state.identity) {
let replacement = jptr(master,state.identityPath);
// ensure it's still there and we've not reffed it away
let newRef = state.identityPath;
if (state.identityPath !== state.path) {
if (options && options.prefix) newRef = newRef.replace('#/',options.prefix);
if (replacement !== false) obj[key] = { $ref: newRef }
else if (options && options.verbose) console.warn(state.identityPath,'gone away at',state.path);
}
}
});
return obj;
}
module.exports = {
reref : reref
};
+148
View File
@@ -0,0 +1,148 @@
'use strict';
/**
* TopoSort function is LICENSE: MIT, everything else is BSD-3-Clause
*/
/*
* Source: https://simplapi.wordpress.com/2015/08/19/detect-graph-cycle-in-javascript/
* removed dependency on underscore, MER
*/
const recurse = require('./recurse.js').recurse;
const isRef = require('./isref.js').isRef;
/*
Nodes should look like:
var nodes = [
{ _id: "3", links: ["8", "10"] },
{ _id: "5", links: ["11"] },
{ _id: "7", links: ["11", "8"] },
{ _id: "8", links: ["9"] },
{ _id: "11", links: ["2", "9", "10"] },
{ _id: "10", links: [] },
{ _id: "9", links: [] },
{ _id: "2", links: [] }
];
*/
/**
* Try to get a topological sorting out of directed graph.
*
* @typedef {Object} Result
* @property {array} sort the sort, empty if not found
* @property {array} nodesWithEdges, will be empty unless a cycle is found
* @param nodes {Object} A list of nodes, including edges (see below).
* @return {Result}
*/
function toposort (nodes) {
// Test if a node has got any incoming edges
function hasIncomingEdge(list, node) {
for (var i = 0, l = list.length; i < l; ++i) {
if (list[i].links.find(function(e,i,a){
return node._id == e;
})) return true;
}
return false;
};
// Kahn's Algorithm
var L = [],
S = nodes.filter(function(node) {
return !hasIncomingEdge(nodes, node);
}),
n = null;
while(S.length) {
// Remove a node n from S
n = S.pop();
// Add n to tail of L
L.push(n);
var i = n.links.length;
while (i--) {
// Getting the node associated to the current stored id in links
var m = nodes.find(function(e){
return n.links[i] === e._id;
});
if (!m) throw new Error('Could not find node from link: '+n.links[i]);
// Remove edge e from the graph
n.links.pop();
if (!hasIncomingEdge(nodes, m)) {
S.push(m);
}
}
}
// If any of them still have links, there is cycle somewhere
var nodesWithEdges = nodes.filter(function(node) {
return node.links.length !== 0;
});
// modified to return both the cyclic nodes and the sort
return {
sort : nodesWithEdges.length == 0 ? L : null,
nodesWithEdges : nodesWithEdges
};
}
/**
* Takes an object and creates a graph of JSON Pointer / References
* @param obj the object to convert
* @param containerName the property containing definitions. Default: definitions
* @return the graph suitable for passing to toposort()
*/
function objToGraph(obj, containerName) {
if (!containerName) containerName = 'definitions';
let graph = [];
for (let p in obj[containerName]) {
let entry = {};
entry._id = '#/'+containerName+'/'+p;
entry.links = [];
graph.push(entry);
}
recurse(obj,{identityDetection:true},function(obj,key,state){
if (isRef(obj,key)) {
let ptr = obj[key].replace('/$ref','');
let spath = state.path.replace('/$ref','');
let target = graph.find(function(e){
return e._id === ptr;
});
if (target) {
target.links.push(ptr);
}
else {
target = {};
target._id = ptr;
target.links = [];
target.links.push(ptr);
graph.push(target);
}
let source = graph.find(function(e){
return e._id === spath;
});
if (source) {
//source.links.push(spath);
}
else {
source = {};
source._id = spath
source.links = [];
graph.push(source);
}
}
});
return graph;
}
module.exports = {
toposort : toposort,
objToGraph : objToGraph
};
+65
View File
@@ -0,0 +1,65 @@
'use strict';
const recurse = require('./recurse.js').recurse;
const jptr = require('./jptr.js').jptr;
/**
* Given an expanded object and an optional object to compare to (e.g. its $ref'd form), will call
* the following functions:
* * callbacks.before - lets you modify the initial starting state, must return it
* * callbacks.where - lets you select a subset of properties, return a truthy value
* * callbacks.filter - called for all selected properties, can mutate/remove (by setting to undefined)
* * callbacks.compare - allowing the objects to be compared by path (i.e. for $ref reinstating)
* * callbacks.identity - called on any object identity (previously seen) properties
* * callbacks.selected - called for all selected/unfiltered properties, does not mutate directly
* * callbacks.count - called at the end with the number of selected properties
* * callbacks.finally - called at the end of the traversal
* @param obj the object to visit
* @param comparison optional object to compare to
* @param callbacks object containing functions as above
* @return the possibly mutated object
*/
function visit(obj,comparison,callbacks) {
let state = {identityDetection:true};
let count = 0;
if (callbacks.before) state = callbacks.before(obj,'',{});
recurse(obj,state,function(obj,key,state){
let selected = true;
if (callbacks.where) {
selected = callbacks.where(obj,key,state);
}
if (selected) {
if (callbacks.filter) {
obj[key] = callbacks.filter(obj,key,state);
if (typeof obj[key] === 'undefined') {
delete obj[key]; // to be doubly sure
}
}
if (typeof obj[key] !== 'undefined') {
if (callbacks.compare && comparison) {
let equiv = jptr(comparison,state.path);
if (equiv) {
obj[key] = callbacks.compare(obj,key,state,equiv);
}
}
if (typeof obj[key] !== 'undefined' && state.identity && callbacks.identity) {
obj[key] = callbacks.identity(obj,key,state,state.identityPath);
}
if (typeof obj[key] !== 'undefined') {
if (callbacks.selected) {
callbacks.selected(obj,key,state);
}
count++;
}
}
}
});
if (callbacks.count) callbacks.count(obj,'',state,count);
if (callbacks.finally) callbacks.finally(obj,'',state);
return obj;
}
module.exports = {
visit : visit
};
+37
View File
@@ -0,0 +1,37 @@
{
"name": "reftools",
"version": "1.1.9",
"description": "Utility functions to deal with references in objects",
"main": "lib/recurse.js",
"scripts": {},
"repository": {
"type": "git",
"url": "https://github.com/Mermade/oas-kit.git"
},
"funding": "https://github.com/Mermade/oas-kit?sponsor=1",
"keywords": [
"json-reference",
"json-pointer",
"object",
"objects",
"circular",
"reference",
"dereference",
"clone",
"flatten",
"recurse",
"recursion",
"iterate",
"iteration",
"traverse",
"traversal",
"visitor"
],
"author": "Mike Ralphson",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/mermade/oas-kit/issues"
},
"homepage": "https://github.com/mermade/oas-kit#readme",
"gitHead": "b1bba3fc5007e96a991bf2a015cf0534ac36b88b"
}