/**
* Accessible via `require('@buggyorg/graphtools').Edge`
* @module Edge */
import curry from 'lodash/fp/curry'
import merge from 'lodash/fp/merge'
import _ from 'lodash'
import * as Port from './port'
import * as Node from './node'
function normalizeStructure (edge) {
if (!_.has(edge, 'from') || !_.has(edge, 'to')) {
throw new Error('The edge format is not valid. You need to have a from and to value in.\n\n' + JSON.stringify(edge, null, 2) + '\n')
}
var layer = edge.layer || 'dataflow'
if (layer !== 'dataflow') {
var newEdge = _.clone(edge)
if (Node.isValid(edge.from)) {
newEdge.from = Node.id(edge.from)
}
if (Node.isValid(edge.to)) {
newEdge.to = Node.id(edge.to)
}
return newEdge
}
if ((typeof (edge.from) === 'string' && edge.from[0] === '/') ||
(typeof (edge.to) === 'string' && edge.to[0] === '/')) {
return merge(edge, {query: true,
from: (Port.isPort(edge.from)) ? Port.normalize(edge.from) : edge.from,
to: (Port.isPort(edge.to)) ? Port.normalize(edge.to) : edge.to
})
} if (edge.outPort && edge.inPort) {
return _.merge({}, _.omit(edge, ['outPort', 'inPort']),
{layer, from: Port.normalize({node: edge.from, port: edge.outPort}), to: Port.normalize({node: edge.to, port: edge.inPort})})
} else if (!edge.outPort && !edge.inPort && Port.isPort(edge.from) && Port.isPort(edge.to)) {
return { from: Port.normalize(edge.from), to: Port.normalize(edge.to), layer }
} else {
throw new Error('Malformed edge. Cannot translate format into standard format.\nEdge: ' + JSON.stringify(edge))
}
}
/**
* Normalizes the edge into the standard format
*
* ```
* {from: '<port>', to: '<port>'}
* ```
*
* It accepts the following short forms:
*
* ```
* {from: '<node>@<port>', to: '<node>@<port>'}
* {from: '@<port>', to: '<node>@<port>'}
* {from: '<node>@<port>', to: '@<port>'}
* {from: '@<port>', to: '@<port>'}
* {from: '<node>', to: '<node>', outPort: '<port-name>', inPort: '<port-name>'}
* ```
*
* The format must be consistent, you cannot have a mixture for `from` and `to`.
* It is not possible to always add those normalized edges into the graph. They must contain
* valid IDs of the graph. Use `Graph.normalize` for this.
*
* @param {Edge} edge The edge object that should be normalized.
* @returns {Edge} The normalized form of the edge.
* @throws {Error} An error is thrown if the edge is not in a consistent format.
*/
export function normalize (edge) {
var newEdge = normalizeStructure(edge)
if (typeof (newEdge.from) === 'object' && newEdge.from.node.length === 0) {
newEdge.innerCompoundOutput = true
}
if (typeof (newEdge.to) === 'object' && newEdge.to.node.length === 0) {
newEdge.innerCompoundInput = true
}
return newEdge
}
export function isEdgeToParent (edge) {
return edge.innerCompoundInput
}
export function isInnerEdge (edge) {
return !edge.innerCompoundInput && !edge.innerCompoundOutput
}
export function isEdgeFromParent (edge) {
return edge.innerCompoundOutput
}
/**
* @function
* @name equal
* @description Checks whether two normalized edges are equal.
* @param {Edge} edge1 The first edge for the comparison.
* @param {Edge} edge2 The second edge for the comparison.
* @returns {boolean} True if the edges are equal (i.e. they connect the same ports), false otherwise.
*/
export const equal = curry((edge1, edge2) => {
if (edge1.layer === 'dataflow') {
return Port.node(edge1.from) === Port.node(edge2.from) && Port.node(edge1.to) === Port.node(edge2.to) &&
Port.portName(edge1.from) === Port.portName(edge2.from) && Port.portName(edge1.to) === Port.portName(edge2.to) &&
edge1.layer === edge2.layer
} else {
return edge1.from === edge2.from && edge1.to === edge2.to && edge1.layer === edge2.layer
}
})
/**
* Returns a copy of the edge where the path is prefixed with the specified path. [Does nothing currently... can probably be removed. Is used in ./compound.js]
* @param {Edge} edge The edge that will be prefixed
* @param {CompoundPath} path The compound path that prefixes the edge paths.
* @returns {Edge} A new edge that has the prefixed paths.
*/
export function setPath (edge, path) {
return edge
}
/**
* Gets the type of an edge. Note that not every edge must have a type and that the type is not stored inside the json document.
* If you use the graph functions to iterate over edges you always get the type (if available) with the edge.
* @example <caption>Get an edge via a graph method and then get its edge.</caption>
* var edge = Graph.incident(port, graph)
* var type = Edge.type(edge)
* @example <caption>It will yield undefined if you simply access an edge in the json document, do not do that.</caption>
* var edge = graph.edges[0]
* //this will return undefined
* var type = Edge.type(edge) // = undefined
* @param {Edge} edge The edge to use.
* @returns {Type|undefined} Either a real type, a typename or `undefined`. Some edges do not have type information. Usually non-dataflow edges like
* recursion indicators. Those will yield `undefined`.
*/
export function type (edge) {
return edge.type
}
/**
* Explicitly sets the type of the edge. The type of an edge is determined by the connecting ports.
* @param {Type} type The type of the edge.
* @param {Edge} edge The edge that should get the type.
* @returns {Edge} A new edge that has a field for the type.
*/
export function setType (type, edge) {
if (isBetweenPorts(edge)) {
return merge(edge, {type, from: Port.setType(type, edge.from), to: Port.setType(type, edge.to)})
} else if (!isBetweenNodes(edge)) {
throw new Error('[Edge.setType] Cannot handle mixed edges (from port to node or from node into port)')
}
return merge({type}, edge)
}
/**
* Checks whether an object is a valid edge.
* @param {Object} edge The object to test.
* @returns {Boolean} True if the object is an edge, false otherwise.
*/
export function isValid (edge) {
return typeof (edge) === 'object' && edge.from && edge.to
}
/**
* Checks if the edge connects two ports.
* @param {Edge} edge The edge to test
* @returns {Boolean} True if the edge connects two ports, false otherwise.
*/
export function isBetweenPorts (edge) {
return typeof (edge) === 'object' &&
typeof (edge.from) === 'object' && edge.from.port &&
typeof (edge.to) === 'object' && edge.to.port
}
/**
* Checks if the edge connects two nodes (and not ports of those nodes).
* @param {Edge} edge The edge to test
* @returns {Boolean} True if the edge connects two nodes, false otherwise (e.g. if it is an edge between ports).
*/
export function isBetweenNodes (edge) {
return isValid(edge) && !isBetweenPorts(edge)
}