Compare commits
62 Commits
8e0dad1e04
...
master
Author | SHA1 | Date | |
---|---|---|---|
cef87ba4a5
|
|||
78556c6c98
|
|||
019647b04a | |||
fa8e0d25c6 | |||
af7bd4e00e | |||
9a2c2ce14c | |||
59542eb7b1 | |||
cafcc73366 | |||
e8382f8117 | |||
4be028f4ef | |||
efdd4ebcd6 | |||
78ad75fd8e | |||
38a5d2f710 | |||
b95f5daa60 | |||
aa1343fedf | |||
f057f2caa6 | |||
dfe3224dec | |||
de5810d368 | |||
040e421d9b | |||
9c2c95e108 | |||
76b2ff31e6 | |||
35e3c69405 | |||
86dd8fefe0 | |||
6af5a144ad | |||
f0bf164873 | |||
a3b3f43812 | |||
b11b2f5f2b | |||
920dc74d9c | |||
8fcbcd2107 | |||
cc0e6bcbb0 | |||
75b803f363 | |||
d478ccd955 | |||
e11f9bea68 | |||
5b96458669 | |||
ab4e9eb368 | |||
cd43d2e457 | |||
8baf87cd43 | |||
58b073fe05 | |||
df00690b57 | |||
7a351b7dab | |||
6e53d8d260 | |||
a491b86947 | |||
63c32bb783 | |||
ebd8936dae | |||
ac3c66bc1c | |||
a34db64fca | |||
8d6d91e562 | |||
acf8004497 | |||
aae79c4f79 | |||
c56a6616ca | |||
695867265d | |||
04c9fc5e8f | |||
650351b201 | |||
f145126821 | |||
1a93f59d9a | |||
e0ea6b7302 | |||
13b3d53c4c | |||
378610bbd8 | |||
81aa911d8a | |||
4f9741f785 | |||
9ce39f4204 | |||
f0497f163d |
14
.drone.yml
Normal file
14
.drone.yml
Normal file
@ -0,0 +1,14 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: setup
|
||||
image: registry:5000/node-build
|
||||
commands:
|
||||
- yarn
|
||||
- curl https://git.scode.ovh/Timo/standard/raw/branch/master/ci.js > ci.js
|
||||
|
||||
- name: build
|
||||
image: registry:5000/node-build
|
||||
commands:
|
||||
- node ci.js
|
25
.eslintrc.js
25
.eslintrc.js
@ -1,17 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
node: true
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
'@scode'
|
||||
],
|
||||
extends: [ '@sapphirecode' ],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly'
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018
|
||||
}
|
||||
}
|
||||
parserOptions: { ecmaVersion: 2018 }
|
||||
};
|
||||
|
8
.liconfig.json
Normal file
8
.liconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"has_license": true,
|
||||
"license": "MIT",
|
||||
"author": "Timo Hocker",
|
||||
"company": "Sapphirecode",
|
||||
"email": "timo@scode.ovh",
|
||||
"software": "graphviz-builder"
|
||||
}
|
26
CHANGELOG.md
Normal file
26
CHANGELOG.md
Normal file
@ -0,0 +1,26 @@
|
||||
# Changelog
|
||||
|
||||
## 1.4.0
|
||||
|
||||
Allow specifying shapes for nodes
|
||||
|
||||
## 1.3.0
|
||||
|
||||
Stream:
|
||||
|
||||
- property to read current node count
|
||||
|
||||
## 1.2.0
|
||||
|
||||
Stream:
|
||||
|
||||
- automatically end node instructions
|
||||
- allow attributes on edges
|
||||
|
||||
## 1.1.0
|
||||
|
||||
Allow creating graphs using a stream
|
||||
|
||||
## 1.0.0
|
||||
|
||||
initial version
|
23
Jenkinsfile
vendored
23
Jenkinsfile
vendored
@ -1,23 +0,0 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
VERSION = VersionNumber([
|
||||
versionNumberString:
|
||||
'${BUILDS_ALL_TIME}',
|
||||
versionPrefix: '1.0.',
|
||||
worstResultForIncrement: 'SUCCESS'
|
||||
])
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Building') {
|
||||
steps {
|
||||
script {
|
||||
currentBuild.displayName = env.VERSION
|
||||
}
|
||||
sh 'yarn ci ${VERSION}'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
MIT License Copyright (c) <year> <author>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
55
README.md
Normal file
55
README.md
Normal file
@ -0,0 +1,55 @@
|
||||
# @sapphirecode/graphviz-builder
|
||||
|
||||
version: 1.4.x
|
||||
|
||||
constructing graphviz files using an easy typescript interface
|
||||
|
||||
## Installation
|
||||
|
||||
npm:
|
||||
|
||||
> npm i --save @sapphirecode/graphviz-builder
|
||||
|
||||
yarn:
|
||||
|
||||
> yarn add @sapphirecode/graphviz-builder
|
||||
|
||||
## Usage
|
||||
|
||||
### Object structure
|
||||
|
||||
```js
|
||||
import {Graph,Color} from '@sapphirecode/graphviz-builder';
|
||||
|
||||
// create a new graph
|
||||
const g = new Graph('foo');
|
||||
|
||||
// add a node to the graph
|
||||
// this function returns the full name of the node that's later used for creating edges
|
||||
const bar = g.add_node('bar');
|
||||
|
||||
// if you want to specify attributes
|
||||
const baz = g.add_node(n => {
|
||||
n.name = 'baz';
|
||||
n.label = 'node baz';
|
||||
n.color = Color.red;
|
||||
});
|
||||
|
||||
// connect nodes
|
||||
g.add_edge(bar, baz);
|
||||
|
||||
// add a subgraph
|
||||
g.add_graph(sg=>{
|
||||
sg.name = 'subgraph';
|
||||
|
||||
sg.add_node('foo');
|
||||
})
|
||||
|
||||
// get info about current stream state
|
||||
g.node_count // count of nodes written
|
||||
g.path // get current path (parent graph names separated by underscores)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT © Timo Hocker <timo@scode.ovh>
|
14
jasmine.json
Normal file
14
jasmine.json
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
{
|
||||
"spec_dir": "test",
|
||||
"spec_files": [
|
||||
"spec/*.js",
|
||||
"spec/*.ts"
|
||||
],
|
||||
"helpers": [
|
||||
"helpers/*.js",
|
||||
"helpers/*.ts"
|
||||
],
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": false
|
||||
}
|
32
jenkins.js
32
jenkins.js
@ -1,32 +0,0 @@
|
||||
/* eslint-disable no-process-exit */
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-sync */
|
||||
'use strict';
|
||||
|
||||
const fs = require ('fs');
|
||||
const child_process = require ('child_process');
|
||||
|
||||
const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8'));
|
||||
|
||||
[
|
||||
,, pkg.version
|
||||
] = process.argv;
|
||||
fs.writeFileSync ('package.json', JSON.stringify (pkg, null, 2));
|
||||
|
||||
child_process.execSync ('yarn lint', { stdio: 'inherit' });
|
||||
if (typeof pkg.scripts !== 'undefined' && typeof pkg.scripts.test === 'string')
|
||||
child_process.execSync ('yarn test', { stdio: 'inherit' });
|
||||
if (
|
||||
typeof pkg.scripts !== 'undefined'
|
||||
&& typeof pkg.scripts.compile === 'string'
|
||||
)
|
||||
child_process.execSync ('yarn compile', { stdio: 'inherit' });
|
||||
|
||||
child_process.exec ('git log -1 | grep \'\\[no publish\\]\'')
|
||||
.addListener ('exit', (code) => {
|
||||
if (code === 0) {
|
||||
console.log ('build not marked for deployment');
|
||||
process.exit (1);
|
||||
}
|
||||
else { child_process.execSync ('yarn publish'); }
|
||||
});
|
@ -1,17 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
node: true
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
'@scode/eslint-config-ts'
|
||||
],
|
||||
extends: [ '@sapphirecode/eslint-config-ts' ],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly'
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018
|
||||
}
|
||||
}
|
||||
parserOptions: { ecmaVersion: 2018 }
|
||||
};
|
||||
|
@ -1,10 +0,0 @@
|
||||
export enum GraphLayouts {
|
||||
neato = 'neato',
|
||||
dot = 'dot',
|
||||
circo = 'circo',
|
||||
fdp = 'fdp',
|
||||
sfdp = 'sfdp',
|
||||
osage = 'osage',
|
||||
twopi = 'twopi',
|
||||
patchwork = 'patchwork'
|
||||
}
|
23
lib/Helper.ts
Normal file
23
lib/Helper.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
/**
|
||||
* removes invalid characters from a string and throws an error
|
||||
* if left with an empty string
|
||||
*
|
||||
* @param name - name to check and transform
|
||||
*/
|
||||
function validate_name (name: string): string {
|
||||
const new_name = name
|
||||
.replace (/[^a-z0-9]/giu, '')
|
||||
.replace (/^[0-9]+/iu, '');
|
||||
if (new_name === '')
|
||||
throw new Error (`invalid node name ${name}`);
|
||||
return new_name;
|
||||
}
|
||||
|
||||
export { validate_name };
|
@ -1,33 +0,0 @@
|
||||
enum EdgeStyles {
|
||||
default = '',
|
||||
solid = 'solid',
|
||||
dashed = 'dashed',
|
||||
dotted='dotted',
|
||||
bold='bold'
|
||||
}
|
||||
|
||||
enum NodeStyles {
|
||||
default = '',
|
||||
solid='solid',
|
||||
dashed='dashed',
|
||||
dotted='dotted',
|
||||
bold='bold',
|
||||
rounded='rounded',
|
||||
diagonals='diagonals',
|
||||
filled='filled',
|
||||
striped='striped',
|
||||
wedged='wedged',
|
||||
invisible='invis'
|
||||
}
|
||||
|
||||
enum GraphStyles {
|
||||
solid = 'solid',
|
||||
dashed = 'dashed',
|
||||
dotted = 'dotted',
|
||||
bold = 'bold',
|
||||
rounded = 'rounded',
|
||||
filled = 'filled',
|
||||
striped = 'striped'
|
||||
}
|
||||
|
||||
export { EdgeStyles, NodeStyles, GraphStyles };
|
@ -1,5 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
/* eslint-disable no-magic-numbers */
|
||||
import { num_to_hex } from '@scode/encoding-helper';
|
||||
import { num_to_hex } from '@sapphirecode/encoding-helper';
|
||||
|
||||
export class Color {
|
||||
public static readonly black = new Color (0, 0, 0);
|
@ -1,4 +1,11 @@
|
||||
import { EdgeStyles } from './Styles';
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { EdgeStyles } from '../enums/Styles';
|
||||
import { Color } from './Color';
|
||||
|
||||
export class Edge {
|
@ -1,3 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { validate_name } from '../Helper';
|
||||
|
||||
export class Element {
|
||||
private _name = '';
|
||||
protected parent_name: string;
|
||||
@ -13,11 +22,7 @@ export class Element {
|
||||
}
|
||||
|
||||
public set name (val: string) {
|
||||
const new_name = val.replace (/[^a-z0-9]/giu, '')
|
||||
.replace (/^[0-9]+/iu, '');
|
||||
if (new_name === '')
|
||||
throw new Error ('invalid name specified');
|
||||
this._name = new_name;
|
||||
this._name = validate_name (val);
|
||||
}
|
||||
|
||||
public get parent (): string {
|
@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { GraphStyles } from '../enums/Styles';
|
||||
import { GraphLayouts } from '../enums/GraphLayouts';
|
||||
import { Element } from './Element';
|
||||
import { Edge } from './Edge';
|
||||
import { Node } from './Node';
|
||||
import { GraphStyles, NodeStyles } from './Styles';
|
||||
import { Color } from './Color';
|
||||
import { GraphLayouts } from './GraphLayouts';
|
||||
|
||||
interface NodeOptions {
|
||||
name: string;
|
||||
label: string;
|
||||
style: NodeStyles;
|
||||
color: Color;
|
||||
}
|
||||
|
||||
export class Graph extends Element {
|
||||
public children: Array<Graph> = [];
|
||||
@ -76,7 +76,7 @@ export class Graph extends Element {
|
||||
.replace (/^\s+$/gmu, '');
|
||||
}
|
||||
|
||||
public add_node (constructor: ((n: Node) => void) | string): string {
|
||||
public add_node (constructor: string | ((n: Node) => void)): string {
|
||||
const node = new Node ('unnamed', this.full_name);
|
||||
|
||||
if (typeof constructor === 'string') {
|
||||
@ -89,7 +89,7 @@ export class Graph extends Element {
|
||||
return node.full_name;
|
||||
}
|
||||
|
||||
public add_graph (constructor: ((g: Graph) => void) | string): string {
|
||||
public add_graph (constructor: string | ((g: Graph) => void)): string {
|
||||
const graph = new Graph ('unnamed', this.full_name);
|
||||
|
||||
graph.directional = this.directional;
|
201
lib/classes/GraphStream.ts
Normal file
201
lib/classes/GraphStream.ts
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
/* eslint-disable line-comment-position */
|
||||
/* eslint-disable no-inline-comments */
|
||||
import { Transform } from 'stream';
|
||||
import { GraphStreamJSON } from '../interfaces/GraphStreamJSON';
|
||||
import {
|
||||
GraphStreamCommand,
|
||||
translate_command
|
||||
} from '../enums/GraphStreamCommand';
|
||||
import { validate_name } from '../Helper';
|
||||
|
||||
interface Stringable {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export class GraphStream extends Transform {
|
||||
private _node_count = 0;
|
||||
private _path: string[] = [];
|
||||
private _state: (GraphStreamCommand | '')[] = [
|
||||
'',
|
||||
''
|
||||
];
|
||||
|
||||
private _directional = false;
|
||||
|
||||
public get path (): string {
|
||||
return this._path.join ('_');
|
||||
}
|
||||
|
||||
public get node_count (): number {
|
||||
return this._node_count;
|
||||
}
|
||||
|
||||
private get level (): string {
|
||||
return ' '.repeat (this._path.length);
|
||||
}
|
||||
|
||||
private finish_node (): void {
|
||||
if (
|
||||
[
|
||||
'ce',
|
||||
'cn'
|
||||
].includes (this._state[1])
|
||||
)
|
||||
this.push ('\n');
|
||||
}
|
||||
|
||||
private expect_state (instr: GraphStreamCommand): void {
|
||||
const states: (GraphStreamCommand|'')[] = [];
|
||||
if ([
|
||||
'cug',
|
||||
'cdg'
|
||||
].includes (instr))
|
||||
states.push ('');
|
||||
|
||||
if ([
|
||||
'eg',
|
||||
'cn',
|
||||
'csg',
|
||||
'ce'
|
||||
].includes (instr))
|
||||
states.push ('at', 'eg', 'cug', 'cdg', 'csg', 'ce', 'cn');
|
||||
|
||||
switch (instr) {
|
||||
case 'at':
|
||||
states.push ('cn', 'cug', 'cdg', 'csg', 'ce');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!states.includes (this._state[1])) {
|
||||
throw new Error (`invalid state to execute command ${
|
||||
translate_command (instr)}
|
||||
expected: ${states.map ((s) => translate_command (s))
|
||||
.join (', ')}
|
||||
actual: ${translate_command (this._state[1])}`);
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, complexity, max-lines-per-function
|
||||
public _transform (
|
||||
chunk: string,
|
||||
encoding: string,
|
||||
callback: ((error?: Error) => unknown)
|
||||
): void {
|
||||
const instr = JSON.parse (chunk) as GraphStreamJSON;
|
||||
this.expect_state (instr.type);
|
||||
switch (instr.type) {
|
||||
case 'cug': // create unordered graph
|
||||
this._path.push (instr.args[0]);
|
||||
this.push (`graph ${this.path} {\n`);
|
||||
this._directional = false;
|
||||
break;
|
||||
case 'cdg': // create directional graph
|
||||
this._path.push (instr.args[0]);
|
||||
this.push (`digraph ${this.path} {\n`);
|
||||
this._directional = true;
|
||||
break;
|
||||
case 'csg': // create subgraph
|
||||
this.finish_node ();
|
||||
this.push (
|
||||
`${this.level}subgraph cluster_${this.path}_${instr.args[0]} {\n`
|
||||
);
|
||||
this._path.push (instr.args[0]);
|
||||
break;
|
||||
case 'eg': // end graph
|
||||
this.finish_node ();
|
||||
this._path.pop ();
|
||||
this.push (`${this.level}}\n\n`);
|
||||
break;
|
||||
case 'cn': // create node
|
||||
this.finish_node ();
|
||||
this.push (`${this.level}${this.path}_${instr.args[0]}`);
|
||||
this._node_count++;
|
||||
break;
|
||||
case 'at': // add attributes
|
||||
if ([
|
||||
'cn',
|
||||
'ce'
|
||||
].includes (this._state[1])) {
|
||||
this.push (` [${instr.args.join (', ')}]\n`);
|
||||
}
|
||||
else {
|
||||
this.push (`${this.level}${
|
||||
instr.args.join (`\n${this.level}`)
|
||||
.replace (/"/gu, '')
|
||||
}\n\n`);
|
||||
}
|
||||
break;
|
||||
case 'ce':
|
||||
this.finish_node ();
|
||||
this.push (`${this.level}${
|
||||
instr.args[0]
|
||||
} -${this._directional ? '>' : '-'} ${instr.args[1]}`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this._state.push (instr.type);
|
||||
this._state.shift ();
|
||||
callback ();
|
||||
}
|
||||
|
||||
public write (
|
||||
instr: GraphStreamJSON
|
||||
): boolean {
|
||||
return super.write (JSON.stringify (instr), 'utf-8');
|
||||
}
|
||||
|
||||
public create_node (name: string): string {
|
||||
const node_name = validate_name (name);
|
||||
this.write ({ type: 'cn', args: [ node_name ] });
|
||||
return `${this.path}_${node_name}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated calling this method is not needed anymore
|
||||
*/
|
||||
public end_node (): void {
|
||||
// this.write ({ type: 'en', args: [] });
|
||||
}
|
||||
|
||||
public create_graph (name: string, type: 'd' | 's' | 'u' = 's'): void {
|
||||
const instr_type = `c${type}g` as GraphStreamCommand;
|
||||
this.write ({ type: instr_type, args: [ validate_name (name) ] });
|
||||
}
|
||||
|
||||
public end_graph (): void {
|
||||
this.write ({ type: 'eg', args: [] });
|
||||
}
|
||||
|
||||
public attributes (attrs: Record<string, Stringable>): void {
|
||||
const solved = [];
|
||||
for (const attr of Object.keys (attrs)) {
|
||||
const val = attrs[attr].toString ();
|
||||
if ((/\n/u).test (val))
|
||||
solved.push (`${attr} = <${val}>`);
|
||||
else
|
||||
solved.push (`${attr} = "${val}"`);
|
||||
}
|
||||
this.write ({ type: 'at', args: solved });
|
||||
}
|
||||
|
||||
public create_edge (origin: string, target: string): void {
|
||||
this.write ({
|
||||
type: 'ce',
|
||||
args: [
|
||||
origin,
|
||||
target
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
@ -1,5 +1,13 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { NodeStyles } from '../enums/Styles';
|
||||
import { NodeShapes } from '../enums/Shapes';
|
||||
import { Element } from './Element';
|
||||
import { NodeStyles } from './Styles';
|
||||
import { Color } from './Color';
|
||||
|
||||
export class Node extends Element {
|
||||
@ -8,6 +16,7 @@ export class Node extends Element {
|
||||
public table_contents?: Array<Array<string>>;
|
||||
public style?: NodeStyles;
|
||||
public color?: Color;
|
||||
public shape?: NodeShapes;
|
||||
|
||||
public constructor (name: string, parent: string, label?: string) {
|
||||
super (name, parent);
|
||||
@ -40,6 +49,8 @@ export class Node extends Element {
|
||||
}
|
||||
if (this.style)
|
||||
attributes.push ({ name: 'style', value: this.style.toString () });
|
||||
if (this.shape)
|
||||
attributes.push ({ name: 'shape', value: this.shape.toString () });
|
||||
if (this.color)
|
||||
attributes.push ({ name: 'color', value: this.color.toString () });
|
||||
|
9
lib/enums/GraphLayouts.ts
Normal file
9
lib/enums/GraphLayouts.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
export type GraphLayouts =
|
||||
'circo' | 'dot' | 'fdp' | 'neato' | 'osage' | 'patchwork' | 'sfdp' | 'twopi'
|
27
lib/enums/GraphStreamCommand.ts
Normal file
27
lib/enums/GraphStreamCommand.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
/* eslint-disable line-comment-position */
|
||||
/* eslint-disable no-inline-comments */
|
||||
type GraphStreamCommand =
|
||||
'at' | 'cdg' | 'ce' | 'cn' | 'csg' | 'cug' | 'eg'
|
||||
|
||||
function translate_command (cmd: GraphStreamCommand|''): string {
|
||||
const translations = {
|
||||
cn: 'create node',
|
||||
cug: 'create unordered graph',
|
||||
cdg: 'create directional graph',
|
||||
csg: 'create subgraph',
|
||||
eg: 'end graph',
|
||||
at: 'attributes',
|
||||
ce: 'create edge',
|
||||
'': 'start'
|
||||
};
|
||||
return `'${translations[cmd]}'`;
|
||||
}
|
||||
|
||||
export { GraphStreamCommand, translate_command };
|
14
lib/enums/Shapes.ts
Normal file
14
lib/enums/Shapes.ts
Normal file
@ -0,0 +1,14 @@
|
||||
type NodeShapes =
|
||||
'' | 'assembly' | 'box' | 'box3d' | 'cds' | 'circle' | 'component'
|
||||
| 'cylinder' | 'diamond' | 'doublecircle' | 'doubleoctagon' | 'egg'
|
||||
| 'ellipse' | 'fivepoverhang' | 'folder' | 'hexagon' | 'house'
|
||||
| 'insulator' | 'invhouse' | 'invtrapezium' | 'invtriangle' | 'larrow'
|
||||
| 'lpromoter' | 'Mcircle' | 'Mdiamond' | 'Msquare' | 'none' | 'note'
|
||||
| 'noverhang' | 'octagon' | 'oval' | 'parallelogram' | 'pentagon'
|
||||
| 'plain' | 'plaintext' | 'point' | 'polygon' | 'primersite' | 'promoter'
|
||||
| 'proteasesite' | 'proteinstab' | 'rarrow' | 'rect' | 'rectangle'
|
||||
| 'restrictionsite' | 'ribosite' | 'rnastab' | 'rpromoter' | 'septagon'
|
||||
| 'signature' | 'square' | 'star' | 'tab' | 'terminator' | 'threepoverhang'
|
||||
| 'trapezium' | 'triangle' | 'tripleoctagon' | 'underline' | 'utr'
|
||||
|
||||
export { NodeShapes };
|
20
lib/enums/Styles.ts
Normal file
20
lib/enums/Styles.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
type EdgeStyles =
|
||||
'' | 'bold' | 'dashed' | 'dotted' | 'solid'
|
||||
|
||||
|
||||
type NodeStyles =
|
||||
'' | 'bold' | 'dashed' | 'diagonals' | 'dotted'
|
||||
| 'filled' | 'invis' | 'rounded' | 'solid' | 'striped' | 'wedged'
|
||||
|
||||
|
||||
type GraphStyles =
|
||||
'bold' | 'dashed' | 'dotted' | 'filled' | 'rounded' | 'solid' | 'striped'
|
||||
|
||||
export { EdgeStyles, NodeStyles, GraphStyles };
|
26
lib/index.ts
26
lib/index.ts
@ -1,7 +1,19 @@
|
||||
export { Graph } from './Graph';
|
||||
export { Node } from './Node';
|
||||
export { Element } from './Element';
|
||||
export { Edge } from './Edge';
|
||||
export { Color } from './Color';
|
||||
export { EdgeStyles, NodeStyles, GraphStyles } from './Styles';
|
||||
export { GraphLayouts } from './GraphLayouts';
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { Graph } from './classes/Graph';
|
||||
|
||||
export * from './classes/Color';
|
||||
export * from './classes/Edge';
|
||||
export * from './classes/Element';
|
||||
export { Graph };
|
||||
export * from './classes/GraphStream';
|
||||
export * from './classes/Node';
|
||||
export * from './enums/GraphLayouts';
|
||||
export * from './enums/Styles';
|
||||
export * from './interfaces/Graphable';
|
||||
export default Graph;
|
||||
|
13
lib/interfaces/GraphStreamJSON.ts
Normal file
13
lib/interfaces/GraphStreamJSON.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { GraphStreamCommand } from '../enums/GraphStreamCommand';
|
||||
|
||||
export interface GraphStreamJSON {
|
||||
type: GraphStreamCommand;
|
||||
args: string[];
|
||||
}
|
12
lib/interfaces/Graphable.ts
Normal file
12
lib/interfaces/Graphable.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { Graph } from '../classes/Graph';
|
||||
|
||||
export interface Graphable {
|
||||
to_graph(g: Graph, ...args: unknown[]): unknown;
|
||||
}
|
38
package.json
38
package.json
@ -1,20 +1,32 @@
|
||||
{
|
||||
"name": "@scode/graphviz-builder",
|
||||
"name": "@sapphirecode/graphviz-builder",
|
||||
"main": "dist/lib/index.js",
|
||||
"author": "Timo Hocker <timo@scode.ovh>",
|
||||
"version": "1.4.0",
|
||||
"author": {
|
||||
"name": "Timo Hocker",
|
||||
"email": "timo@scode.ovh"
|
||||
},
|
||||
"bugs": "https://redmine.scode.ovh/projects/graphviz-builder",
|
||||
"description": "constructing graphviz files using an easy typescript interface",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.scode.ovh:timo/graphviz-builder.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "^1.1.1",
|
||||
"@scode/eslint-config-ts": "^1.0.22",
|
||||
"ava": "^3.7.1",
|
||||
"eslint": "^6.8.0",
|
||||
"@sapphirecode/eslint-config-ts": "^1.1.4",
|
||||
"@types/jasmine": "^3.5.14",
|
||||
"@types/node": "^14.11.2",
|
||||
"eslint": "^7.0.0",
|
||||
"jasmine": "^3.6.1",
|
||||
"jasmine-ts": "^0.3.0",
|
||||
"nyc": "^15.0.1",
|
||||
"typescript": "^3.8.3"
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs",
|
||||
"test": "tsc && nyc ava",
|
||||
"ci": "yarn && node jenkins.js",
|
||||
"test": "nyc jasmine-ts --config=\"jasmine.json\"",
|
||||
"compile": "tsc"
|
||||
},
|
||||
"files": [
|
||||
@ -22,6 +34,10 @@
|
||||
"LICENSE"
|
||||
],
|
||||
"dependencies": {
|
||||
"@scode/encoding-helper": "^1.0.21"
|
||||
}
|
||||
"@sapphirecode/encoding-helper": "^1.0.38"
|
||||
},
|
||||
"keywords": [
|
||||
"graphviz",
|
||||
"generator"
|
||||
]
|
||||
}
|
||||
|
@ -1,20 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
node: true
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
'@scode/eslint-config-ts'
|
||||
],
|
||||
extends: [ '@sapphirecode/eslint-config-ts' ],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly'
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018
|
||||
},
|
||||
rules: {
|
||||
'node/no-unpublished-import': 'off'
|
||||
}
|
||||
}
|
||||
parserOptions: { ecmaVersion: 2018 },
|
||||
rules: { 'node/no-unpublished-import': 'off' }
|
||||
};
|
||||
|
@ -1,10 +0,0 @@
|
||||
import test from 'ava';
|
||||
import { Color } from '../lib';
|
||||
|
||||
test ('serialize', (t) => {
|
||||
const wh = Color.white;
|
||||
const tr = Color.transparent;
|
||||
|
||||
t.is (wh.toString (), '#ffffff');
|
||||
t.is (tr.toString (), '#00000000');
|
||||
});
|
22
test/Edge.ts
22
test/Edge.ts
@ -1,22 +0,0 @@
|
||||
import test from 'ava';
|
||||
import { Edge, Color, EdgeStyles } from '../lib';
|
||||
|
||||
test ('serialize', (t) => {
|
||||
const e = new Edge ('foo', 'bar', false);
|
||||
|
||||
e.color = Color.white;
|
||||
e.style = EdgeStyles.dashed;
|
||||
const serialized = e.toString ();
|
||||
|
||||
t.is (serialized, 'foo -- bar [style="dashed",color="#ffffff"]');
|
||||
});
|
||||
|
||||
test ('serialize directional', (t) => {
|
||||
const e = new Edge ('foo', 'bar', true);
|
||||
|
||||
e.color = Color.white;
|
||||
e.style = EdgeStyles.dashed;
|
||||
const serialized = e.toString ();
|
||||
|
||||
t.is (serialized, 'foo -> bar [style="dashed",color="#ffffff"]');
|
||||
});
|
121
test/Graph.ts
121
test/Graph.ts
@ -1,121 +0,0 @@
|
||||
import test from 'ava';
|
||||
import { Graph, GraphStyles, Color, NodeStyles, GraphLayouts } from '../lib';
|
||||
|
||||
const result = `digraph foo {
|
||||
subgraph cluster_foo_baz {
|
||||
color = #ff0000
|
||||
style = bold
|
||||
|
||||
subgraph cluster_foo_baz_nested {
|
||||
color = #808080
|
||||
style = dotted
|
||||
|
||||
subgraph cluster_foo_baz_nested_unnamed {
|
||||
color = #808080
|
||||
style = dotted
|
||||
}
|
||||
}
|
||||
|
||||
foo_baz_asd [label="asd"]
|
||||
foo_baz_test [style="bold", color="#808080"]
|
||||
}
|
||||
|
||||
foo_baz [label="baz"]
|
||||
foo_foo [label="foo"]
|
||||
|
||||
foo_foo -> foo_baz
|
||||
}`;
|
||||
|
||||
const non_directional = `graph foo {
|
||||
subgraph cluster_foo_bar {
|
||||
foo_bar_baz [label="baz"]
|
||||
foo_bar_asd [label="asd"]
|
||||
|
||||
foo_bar_baz -- foo_bar_asd
|
||||
}
|
||||
|
||||
foo_foo [label="foo"]
|
||||
|
||||
foo_bar_baz -- foo_foo
|
||||
}`;
|
||||
|
||||
const attributes = `digraph attr {
|
||||
color = #000000
|
||||
style = bold
|
||||
overlap = false
|
||||
splines = true
|
||||
layout = neato
|
||||
}`;
|
||||
|
||||
test ('serialize', (t) => {
|
||||
const g = new Graph ('foo');
|
||||
|
||||
t.is (g.full_name, 'foo');
|
||||
g.add_graph ((graph) => {
|
||||
graph.name = 'baz';
|
||||
graph.add_node ('asd');
|
||||
graph.add_node ((n) => {
|
||||
n.name = 'test';
|
||||
n.style = NodeStyles.bold;
|
||||
n.color = Color.gray;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
graph.add_graph ((g) => {
|
||||
g.style = GraphStyles.dotted;
|
||||
g.color = Color.gray;
|
||||
g.name = 'nested';
|
||||
|
||||
// eslint-disable-next-line no-shadow, max-nested-callbacks
|
||||
g.add_graph ((g) => {
|
||||
g.style = GraphStyles.dotted;
|
||||
g.color = Color.gray;
|
||||
});
|
||||
});
|
||||
graph.style = GraphStyles.bold;
|
||||
graph.color = Color.red;
|
||||
});
|
||||
|
||||
const baz = g.add_node ('baz');
|
||||
const foo = g.add_node ('foo');
|
||||
|
||||
g.add_edge (foo, baz);
|
||||
|
||||
const serialized = g.toString ();
|
||||
|
||||
t.is (serialized, result);
|
||||
});
|
||||
|
||||
test ('non directional', (t) => {
|
||||
const g = new Graph ('foo');
|
||||
|
||||
g.directional = false;
|
||||
|
||||
let n = '';
|
||||
|
||||
g.add_graph ((sub) => {
|
||||
sub.name = 'bar';
|
||||
n = sub.add_node ('baz');
|
||||
const n2 = sub.add_node ('asd');
|
||||
|
||||
sub.add_edge (n, n2);
|
||||
});
|
||||
|
||||
const f = g.add_node ('foo');
|
||||
|
||||
g.add_edge (n, f);
|
||||
|
||||
t.is (g.toString (), non_directional);
|
||||
});
|
||||
|
||||
test ('attributes', (t) => {
|
||||
const g = new Graph ('attr');
|
||||
|
||||
g.layout = GraphLayouts.neato;
|
||||
g.overlap = false;
|
||||
g.splines = true;
|
||||
g.color = Color.black;
|
||||
g.style = GraphStyles.bold;
|
||||
|
||||
t.is (g.toString (), attributes);
|
||||
});
|
70
test/Node.ts
70
test/Node.ts
@ -1,70 +0,0 @@
|
||||
import test from 'ava';
|
||||
import { NodeStyles, Node, Color } from '../lib';
|
||||
|
||||
const serialized_simple
|
||||
= 'bar_foo [label="baz", style="dashed", color="#00ff00"]';
|
||||
const serialized_table = `bar_foo [label=<<table>
|
||||
<tr><td>foo</td><td>bar</td><td>baz</td></tr>
|
||||
<tr><td>bar</td><td>baz</td><td>foo</td></tr>
|
||||
<tr><td>baz</td><td>foo</td><td>bar</td></tr>
|
||||
</table>>, style="invis", color="#00ff00"]`;
|
||||
|
||||
test ('serialize simple', (t) => {
|
||||
const g = new Node ('foo', 'bar', 'baz');
|
||||
|
||||
g.color = Color.green;
|
||||
g.style = NodeStyles.dashed;
|
||||
|
||||
const serialized = g.toString ();
|
||||
|
||||
t.is (g.full_name, 'bar_foo');
|
||||
t.is (serialized, serialized_simple);
|
||||
});
|
||||
|
||||
test ('serialize table', (t) => {
|
||||
const g = new Node ('foo', 'bar', 'baz');
|
||||
|
||||
g.color = Color.green;
|
||||
g.style = NodeStyles.invisible;
|
||||
|
||||
g.table_contents = [
|
||||
[
|
||||
'foo',
|
||||
'bar',
|
||||
'baz'
|
||||
],
|
||||
[
|
||||
'bar',
|
||||
'baz',
|
||||
'foo'
|
||||
],
|
||||
[
|
||||
'baz',
|
||||
'foo',
|
||||
'bar'
|
||||
]
|
||||
];
|
||||
g.is_table = true;
|
||||
|
||||
const serialized = g.toString ();
|
||||
|
||||
t.is (g.full_name, 'bar_foo');
|
||||
t.is (serialized, serialized_table);
|
||||
});
|
||||
|
||||
test ('adhere to naming convention', (t) => {
|
||||
const n = new Node ('invalid.name', 'parent');
|
||||
t.is (n.name, 'invalidname');
|
||||
});
|
||||
|
||||
test ('throw on invalid name', (t) => {
|
||||
t.throws (() => {
|
||||
const n = new Node ('564#+-.,/@', 'parent');
|
||||
return n.toString ();
|
||||
}, { message: 'invalid name specified' });
|
||||
});
|
||||
|
||||
test ('leave numbers after the first letter', (t) => {
|
||||
const n = new Node ('i123nvalid.name', 'parent');
|
||||
t.is (n.name, 'i123nvalidname');
|
||||
});
|
20
test/spec/Color.ts
Normal file
20
test/spec/Color.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { Color } from '../../lib';
|
||||
|
||||
describe ('color', () => {
|
||||
it ('should serialize', () => {
|
||||
const wh = Color.white;
|
||||
const tr = Color.transparent;
|
||||
|
||||
expect (wh.toString ())
|
||||
.toEqual ('#ffffff');
|
||||
expect (tr.toString ())
|
||||
.toEqual ('#00000000');
|
||||
});
|
||||
});
|
32
test/spec/Edge.ts
Normal file
32
test/spec/Edge.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { Edge, Color } from '../../lib';
|
||||
|
||||
describe ('edge', () => {
|
||||
it ('should serialize', () => {
|
||||
const e = new Edge ('foo', 'bar', false);
|
||||
|
||||
e.color = Color.white;
|
||||
e.style = 'dashed';
|
||||
const serialized = e.toString ();
|
||||
|
||||
expect (serialized)
|
||||
.toEqual ('foo -- bar [style="dashed",color="#ffffff"]');
|
||||
});
|
||||
|
||||
it ('should serialize directional', () => {
|
||||
const e = new Edge ('foo', 'bar', true);
|
||||
|
||||
e.color = Color.white;
|
||||
e.style = 'dashed';
|
||||
const serialized = e.toString ();
|
||||
|
||||
expect (serialized)
|
||||
.toEqual ('foo -> bar [style="dashed",color="#ffffff"]');
|
||||
});
|
||||
});
|
135
test/spec/Graph.ts
Normal file
135
test/spec/Graph.ts
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
import { Graph, Color } from '../../lib';
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
describe ('graph', () => {
|
||||
const result = `digraph foo {
|
||||
subgraph cluster_foo_baz {
|
||||
color = #ff0000
|
||||
style = bold
|
||||
|
||||
subgraph cluster_foo_baz_nested {
|
||||
color = #808080
|
||||
style = dotted
|
||||
|
||||
subgraph cluster_foo_baz_nested_unnamed {
|
||||
color = #808080
|
||||
style = dotted
|
||||
}
|
||||
}
|
||||
|
||||
foo_baz_asd [label="asd"]
|
||||
foo_baz_test [style="bold", color="#808080"]
|
||||
}
|
||||
|
||||
foo_baz [label="baz"]
|
||||
foo_foo [label="foo"]
|
||||
|
||||
foo_foo -> foo_baz
|
||||
}`;
|
||||
|
||||
const non_directional = `graph foo {
|
||||
subgraph cluster_foo_bar {
|
||||
foo_bar_baz [label="baz"]
|
||||
foo_bar_asd [label="asd"]
|
||||
|
||||
foo_bar_baz -- foo_bar_asd
|
||||
}
|
||||
|
||||
foo_foo [label="foo"]
|
||||
|
||||
foo_bar_baz -- foo_foo
|
||||
}`;
|
||||
|
||||
const attributes = `digraph attr {
|
||||
color = #000000
|
||||
style = bold
|
||||
overlap = false
|
||||
splines = true
|
||||
layout = neato
|
||||
}`;
|
||||
|
||||
it ('should serialize', () => {
|
||||
const g = new Graph ('foo');
|
||||
|
||||
expect (g.full_name)
|
||||
.toEqual ('foo');
|
||||
g.add_graph ((graph) => {
|
||||
graph.name = 'baz';
|
||||
graph.add_node ('asd');
|
||||
graph.add_node ((n) => {
|
||||
n.name = 'test';
|
||||
n.style = 'bold';
|
||||
n.color = Color.gray;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
graph.add_graph ((g) => {
|
||||
g.style = 'dotted';
|
||||
g.color = Color.gray;
|
||||
g.name = 'nested';
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
g.add_graph ((g) => {
|
||||
g.style = 'dotted';
|
||||
g.color = Color.gray;
|
||||
});
|
||||
});
|
||||
graph.style = 'bold';
|
||||
graph.color = Color.red;
|
||||
});
|
||||
|
||||
const baz = g.add_node ('baz');
|
||||
const foo = g.add_node ('foo');
|
||||
|
||||
g.add_edge (foo, baz);
|
||||
|
||||
const serialized = g.toString ();
|
||||
|
||||
expect (serialized)
|
||||
.toEqual (result);
|
||||
});
|
||||
|
||||
it ('non directional', () => {
|
||||
const g = new Graph ('foo');
|
||||
|
||||
g.directional = false;
|
||||
|
||||
let n = '';
|
||||
|
||||
g.add_graph ((sub) => {
|
||||
sub.name = 'bar';
|
||||
n = sub.add_node ('baz');
|
||||
const n2 = sub.add_node ('asd');
|
||||
|
||||
sub.add_edge (n, n2);
|
||||
});
|
||||
|
||||
const f = g.add_node ('foo');
|
||||
|
||||
g.add_edge (n, f);
|
||||
|
||||
expect (g.toString ())
|
||||
.toEqual (non_directional);
|
||||
});
|
||||
|
||||
it ('attributes', () => {
|
||||
const g = new Graph ('attr');
|
||||
|
||||
g.layout = 'neato';
|
||||
g.overlap = false;
|
||||
g.splines = true;
|
||||
g.color = Color.black;
|
||||
g.style = 'bold';
|
||||
|
||||
expect (g.toString ())
|
||||
.toEqual (attributes);
|
||||
});
|
||||
});
|
87
test/spec/Node.ts
Normal file
87
test/spec/Node.ts
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, May 2020
|
||||
*/
|
||||
|
||||
import { Node, Color } from '../../lib';
|
||||
|
||||
const serialized_simple
|
||||
= 'bar_foo [label="baz", style="dashed",'
|
||||
+ ' shape="tripleoctagon", color="#00ff00"]';
|
||||
const serialized_table = `bar_foo [label=<<table>
|
||||
<tr><td>foo</td><td>bar</td><td>baz</td></tr>
|
||||
<tr><td>bar</td><td>baz</td><td>foo</td></tr>
|
||||
<tr><td>baz</td><td>foo</td><td>bar</td></tr>
|
||||
</table>>, style="invis", color="#00ff00"]`;
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
describe ('node', () => {
|
||||
it ('should serialize simple', () => {
|
||||
const g = new Node ('foo', 'bar', 'baz');
|
||||
|
||||
g.color = Color.green;
|
||||
g.style = 'dashed';
|
||||
g.shape = 'tripleoctagon';
|
||||
|
||||
const serialized = g.toString ();
|
||||
|
||||
expect (g.full_name)
|
||||
.toEqual ('bar_foo');
|
||||
expect (serialized)
|
||||
.toEqual (serialized_simple);
|
||||
});
|
||||
|
||||
it ('should serialize table', () => {
|
||||
const g = new Node ('foo', 'bar', 'baz');
|
||||
|
||||
g.color = Color.green;
|
||||
g.style = 'invis';
|
||||
|
||||
g.table_contents = [
|
||||
[
|
||||
'foo',
|
||||
'bar',
|
||||
'baz'
|
||||
],
|
||||
[
|
||||
'bar',
|
||||
'baz',
|
||||
'foo'
|
||||
],
|
||||
[
|
||||
'baz',
|
||||
'foo',
|
||||
'bar'
|
||||
]
|
||||
];
|
||||
g.is_table = true;
|
||||
|
||||
const serialized = g.toString ();
|
||||
|
||||
expect (g.full_name)
|
||||
.toEqual ('bar_foo');
|
||||
expect (serialized)
|
||||
.toEqual (serialized_table);
|
||||
});
|
||||
|
||||
it ('should adhere to naming convention', () => {
|
||||
const n = new Node ('invalid.name', 'parent');
|
||||
expect (n.name)
|
||||
.toEqual ('invalidname');
|
||||
});
|
||||
|
||||
it ('should throw on invalid name', () => {
|
||||
expect (() => {
|
||||
const n = new Node ('564#+-.,/@', 'parent');
|
||||
return n.toString ();
|
||||
})
|
||||
.toThrowError ('invalid node name 564#+-.,/@');
|
||||
});
|
||||
|
||||
it ('should leave numbers after the first letter', () => {
|
||||
const n = new Node ('i123nvalid.name', 'parent');
|
||||
expect (n.name)
|
||||
.toEqual ('i123nvalidname');
|
||||
});
|
||||
});
|
217
test/spec/Stream.ts
Normal file
217
test/spec/Stream.ts
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||
* This file is part of graphviz-builder which is released under MIT.
|
||||
* See file 'LICENSE' for full license details.
|
||||
* Created by Timo Hocker <timo@scode.ovh>, June 2020
|
||||
*/
|
||||
|
||||
import { GraphStream, Color } from '../../lib/index';
|
||||
|
||||
const simple = `graph foo {
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const complex = `digraph foo {
|
||||
subgraph cluster_foo_baz {
|
||||
color = #ff0000
|
||||
style = bold
|
||||
|
||||
subgraph cluster_foo_baz_nested {
|
||||
color = #808080
|
||||
style = dotted
|
||||
|
||||
subgraph cluster_foo_baz_nested_unnamed {
|
||||
color = #808080
|
||||
style = dotted
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foo_baz_asd [label = "asd"]
|
||||
foo_baz_test [style = "bold", color = "#808080"]
|
||||
}
|
||||
|
||||
foo_baz [label = "baz"]
|
||||
foo_foo [label = "foo"]
|
||||
foo_foo -> foo_baz
|
||||
foo_foo -> foo_baz_asd [label = "edge attr"]
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
describe ('stream', () => {
|
||||
it ('stream graph', () => new Promise<void> ((resolve) => {
|
||||
let output = '';
|
||||
const stream = (new GraphStream);
|
||||
stream.on ('data', (data) => {
|
||||
output += data;
|
||||
});
|
||||
stream.on ('end', () => {
|
||||
expect (output)
|
||||
.toEqual (simple);
|
||||
resolve ();
|
||||
});
|
||||
stream.create_graph ('foo', 'u');
|
||||
stream.end_graph ();
|
||||
stream.end ();
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line max-statements
|
||||
it ('complex stream graph', () => new Promise<void> ((resolve) => {
|
||||
let output = '';
|
||||
const stream = (new GraphStream);
|
||||
stream.on ('data', (data) => {
|
||||
output += data;
|
||||
});
|
||||
stream.on ('end', () => {
|
||||
expect (output)
|
||||
.toEqual (complex);
|
||||
resolve ();
|
||||
});
|
||||
stream.create_graph ('foo', 'd');
|
||||
stream.create_graph ('baz');
|
||||
stream.attributes ({ color: Color.red, style: 'bold' });
|
||||
stream.create_graph ('nested');
|
||||
stream.attributes ({ color: Color.gray, style: 'dotted' });
|
||||
stream.create_graph ('unnamed');
|
||||
stream.attributes ({ color: Color.gray, style: 'dotted' });
|
||||
stream.end_graph ();
|
||||
stream.end_graph ();
|
||||
const asd = stream.create_node ('asd');
|
||||
stream.attributes ({ label: 'asd' });
|
||||
stream.end_node ();
|
||||
stream.create_node ('test');
|
||||
stream.attributes ({ style: 'bold', color: Color.gray });
|
||||
stream.end_graph ();
|
||||
const baz = stream.create_node ('baz');
|
||||
stream.attributes ({ label: 'baz' });
|
||||
const foo = stream.create_node ('foo');
|
||||
stream.attributes ({ label: 'foo' });
|
||||
stream.create_edge (foo, baz);
|
||||
stream.create_edge (foo, asd);
|
||||
stream.attributes ({ label: 'edge attr' });
|
||||
stream.end_graph ();
|
||||
stream.end ();
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
it ('should reject invalid command sequences', () => {
|
||||
/**
|
||||
* command valid invalid
|
||||
* [s] cug, cdg eg, cn, csg, ce, at
|
||||
* cn eg, cn, csg, ce, at cug, cdg
|
||||
* cug eg, cn, csg, ce, at cug, cdg
|
||||
* cdg eg, cn, csg, ce, at cug, cdg
|
||||
* csg eg, cn, csg, ce, at cug, cdg
|
||||
* eg eg, cn, csg, ce cug, cdg, at
|
||||
* at eg, cn, csg, ce cug, cdg, at
|
||||
* ce eg, cn, csg, ce, at cug, cdg
|
||||
*/
|
||||
|
||||
const cn = (g: GraphStream) => {
|
||||
g.create_node ('foo');
|
||||
};
|
||||
const cug = (g: GraphStream) => {
|
||||
g.create_graph ('foo', 'u');
|
||||
};
|
||||
const cdg = (g: GraphStream) => {
|
||||
g.create_graph ('foo', 'd');
|
||||
};
|
||||
const csg = (g: GraphStream) => {
|
||||
g.create_graph ('foo', 's');
|
||||
};
|
||||
const eg = (g: GraphStream) => {
|
||||
g.end_graph ();
|
||||
};
|
||||
const at = (g: GraphStream) => {
|
||||
g.attributes ({ color: 'red' });
|
||||
};
|
||||
const ce = (g: GraphStream) => {
|
||||
g.create_edge ('foo', 'bar');
|
||||
};
|
||||
|
||||
const combinations = [
|
||||
{
|
||||
primary: (g: GraphStream) => {
|
||||
cug (g);
|
||||
cn (g);
|
||||
},
|
||||
secondary: [
|
||||
cug,
|
||||
cdg
|
||||
]
|
||||
},
|
||||
{
|
||||
primary: cug,
|
||||
secondary: [
|
||||
cug,
|
||||
cdg
|
||||
]
|
||||
},
|
||||
{
|
||||
primary: cdg,
|
||||
secondary: [
|
||||
cug,
|
||||
cdg
|
||||
]
|
||||
},
|
||||
{
|
||||
primary: (g: GraphStream) => {
|
||||
cug (g);
|
||||
csg (g);
|
||||
},
|
||||
secondary: [
|
||||
cug,
|
||||
cdg
|
||||
]
|
||||
},
|
||||
{
|
||||
primary: (g: GraphStream) => {
|
||||
cug (g);
|
||||
csg (g);
|
||||
eg (g);
|
||||
},
|
||||
secondary: [
|
||||
cug,
|
||||
cdg,
|
||||
at
|
||||
]
|
||||
},
|
||||
{
|
||||
primary: (g: GraphStream) => {
|
||||
cug (g);
|
||||
at (g);
|
||||
},
|
||||
secondary: [
|
||||
cug,
|
||||
cdg,
|
||||
at
|
||||
]
|
||||
},
|
||||
{
|
||||
primary: (g: GraphStream) => {
|
||||
cug (g);
|
||||
ce (g);
|
||||
},
|
||||
secondary: [
|
||||
cug,
|
||||
cdg
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
for (const comb of combinations) {
|
||||
for (const secondary of comb.secondary) {
|
||||
const g = (new GraphStream);
|
||||
comb.primary (g);
|
||||
expect (() => secondary (g))
|
||||
.toThrowError (
|
||||
/invalid state to execute command.*?expected:.*?actual:/uis
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user