Merge branch 'master' of git.scode.ovh:timo/graphviz-builder

This commit is contained in:
Timo Hocker 2020-04-29 22:22:42 +02:00
commit 84bda96276
13 changed files with 167 additions and 43 deletions

View File

@ -7,6 +7,7 @@ const fs = require ('fs');
const child_process = require ('child_process'); const child_process = require ('child_process');
const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8')); const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8'));
[ [
,, pkg.version ,, pkg.version
] = process.argv; ] = process.argv;

View File

@ -6,15 +6,18 @@ export class Edge {
public target: string; public target: string;
public style?: EdgeStyles; public style?: EdgeStyles;
public color?: Color; public color?: Color;
private _directional: boolean;
public constructor (origin: string, target: string) { public constructor (origin: string, target: string, directional: boolean) {
this.origin = origin; this.origin = origin;
this.target = target; this.target = target;
this._directional = directional;
} }
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
public toString (): string { public toString (): string {
const attributes = []; const attributes = [];
if (this.style) if (this.style)
attributes.push ({ name: 'style', value: this.style.toString () }); attributes.push ({ name: 'style', value: this.style.toString () });
if (this.color) if (this.color)
@ -23,7 +26,9 @@ export class Edge {
const attr_string = ` [${attributes.map ((v) => `${v.name}="${v.value}"`) const attr_string = ` [${attributes.map ((v) => `${v.name}="${v.value}"`)
.join (',')}]`; .join (',')}]`;
return `${this.origin} -> ${this.target}${attributes.length > 0 return `${this.origin} -${
this._directional ? '>' : '-'
} ${this.target}${attributes.length > 0
? attr_string ? attr_string
: ''}`; : ''}`;
} }

View File

@ -25,6 +25,13 @@ export class Element {
} }
public constructor (name: string, parent = '') { public constructor (name: string, parent = '') {
<<<<<<< HEAD
=======
const regex = /^[a-z_][a-z_0-9]+$/iu;
if (!regex.test (name))
throw new Error ('invalid name specified');
>>>>>>> f553396eee29e1e0820624345e5d23dc3471a4a4
this.name = name; this.name = name;
this.parent_name = parent; this.parent_name = parent;
} }

View File

@ -3,6 +3,7 @@ import { Edge } from './Edge';
import { Node } from './Node'; import { Node } from './Node';
import { GraphStyles, NodeStyles } from './Styles'; import { GraphStyles, NodeStyles } from './Styles';
import { Color } from './Color'; import { Color } from './Color';
import { GraphLayouts } from './GraphLayouts';
interface NodeOptions { interface NodeOptions {
name: string; name: string;
@ -18,19 +19,35 @@ export class Graph extends Element {
public edges: Array<Edge> = []; public edges: Array<Edge> = [];
public style?: GraphStyles; public style?: GraphStyles;
public color?: Color; public color?: Color;
public directional = true;
public overlap?: boolean | string;
public splines?: boolean | string;
public layout?: GraphLayouts;
private get attributes (): Array<{name: string; value: string}> {
const attributes = [];
if (typeof this.color !== 'undefined')
attributes.push ({ name: 'color', value: this.color.toString () });
if (typeof this.style !== 'undefined')
attributes.push ({ name: 'style', value: this.style.toString () });
if (typeof this.overlap !== 'undefined')
attributes.push ({ name: 'overlap', value: this.overlap.toString () });
if (typeof this.splines !== 'undefined')
attributes.push ({ name: 'splines', value: this.splines.toString () });
if (typeof this.layout !== 'undefined')
attributes.push ({ name: 'layout', value: this.layout.toString () });
return attributes;
}
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
public toString (): string { public toString (): string {
const header = this.parent const header = this.parent
? `subgraph cluster_${this.full_name}` ? `subgraph cluster_${this.full_name}`
: `digraph ${this.full_name}`; : `${this.directional ? 'di' : ''}graph ${this.full_name}`;
const attributes = [];
if (this.color)
attributes.push ({ name: 'color', value: this.color.toString () });
if (this.style)
attributes.push ({ name: 'style', value: this.style.toString () });
let attrs = `\n${attributes.map ((v) => `${v.name} = ${v.value}`) let attrs = `\n${this.attributes.map ((v) => `${v.name} = ${v.value}`)
.join ('\n')}\n`; .join ('\n')}\n`;
let children = `\n${this.children.map ((c) => c.toString ()) let children = `\n${this.children.map ((c) => c.toString ())
.join ('\n')}\n`; .join ('\n')}\n`;
@ -75,6 +92,8 @@ export class Graph extends Element {
public add_graph (constructor: ((g: Graph) => void) | string): string { public add_graph (constructor: ((g: Graph) => void) | string): string {
const graph = new Graph ('unnamed', this.full_name); const graph = new Graph ('unnamed', this.full_name);
graph.directional = this.directional;
if (typeof constructor === 'string') if (typeof constructor === 'string')
graph.name = constructor; graph.name = constructor;
else else
@ -85,6 +104,6 @@ export class Graph extends Element {
} }
public add_edge (origin: string, target: string): void { public add_edge (origin: string, target: string): void {
this.edges.push (new Edge (origin, target)); this.edges.push (new Edge (origin, target, this.directional));
} }
} }

10
lib/GraphLayouts.ts Normal file
View File

@ -0,0 +1,10 @@
export enum GraphLayouts {
neato = 'neato',
dot = 'dot',
circo = 'circo',
fdp = 'fdp',
sfdp = 'sfdp',
osage = 'osage',
twopi = 'twopi',
patchwork = 'patchwork'
}

View File

@ -20,6 +20,7 @@ export class Node extends Element {
const mapped_columns = this.table_contents const mapped_columns = this.table_contents
.map ((val) => `<td>${val.join ('</td><td>')}</td>`); .map ((val) => `<td>${val.join ('</td><td>')}</td>`);
return `<table>\n <tr>${ return `<table>\n <tr>${
mapped_columns.join ('</tr>\n <tr>') mapped_columns.join ('</tr>\n <tr>')
}</tr>\n</table>`; }</tr>\n</table>`;
@ -28,6 +29,7 @@ export class Node extends Element {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
public toString (): string { public toString (): string {
const attributes = []; const attributes = [];
if (this.label || this.is_table) { if (this.label || this.is_table) {
attributes.push ({ attributes.push ({
name: 'label', name: 'label',
@ -51,6 +53,7 @@ export class Node extends Element {
'"', '"',
'"' '"'
]; ];
return `${v.name}=${d[0]}${v.value}${d[1]}`; return `${v.name}=${d[0]}${v.value}${d[1]}`;
}) })
.join (', '); .join (', ');

View File

@ -4,3 +4,4 @@ export { Element } from './Element';
export { Edge } from './Edge'; export { Edge } from './Edge';
export { Color } from './Color'; export { Color } from './Color';
export { EdgeStyles, NodeStyles, GraphStyles } from './Styles'; export { EdgeStyles, NodeStyles, GraphStyles } from './Styles';
export { GraphLayouts } from './GraphLayouts';

View File

@ -18,7 +18,7 @@
"compile": "tsc" "compile": "tsc"
}, },
"files": [ "files": [
"/dist/", "/dist/lib/",
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {

View File

@ -4,6 +4,7 @@ import { Color } from '../lib';
test ('serialize', (t) => { test ('serialize', (t) => {
const wh = Color.white; const wh = Color.white;
const tr = Color.transparent; const tr = Color.transparent;
t.is (wh.toString (), '#ffffff'); t.is (wh.toString (), '#ffffff');
t.is (tr.toString (), '#00000000'); t.is (tr.toString (), '#00000000');
}); });

View File

@ -2,9 +2,21 @@ import test from 'ava';
import { Edge, Color, EdgeStyles } from '../lib'; import { Edge, Color, EdgeStyles } from '../lib';
test ('serialize', (t) => { test ('serialize', (t) => {
const e = new Edge ('foo', 'bar'); const e = new Edge ('foo', 'bar', false);
e.color = Color.white; e.color = Color.white;
e.style = EdgeStyles.dashed; e.style = EdgeStyles.dashed;
const serialized = e.toString (); 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"]'); t.is (serialized, 'foo -> bar [style="dashed",color="#ffffff"]');
}); });

View File

@ -1,5 +1,5 @@
import test from 'ava'; import test from 'ava';
import { Graph, GraphStyles, Color, NodeStyles } from '../lib'; import { Graph, GraphStyles, Color, NodeStyles, GraphLayouts } from '../lib';
const result = `digraph foo { const result = `digraph foo {
subgraph cluster_foo_baz { subgraph cluster_foo_baz {
@ -26,6 +26,27 @@ const result = `digraph foo {
foo_foo -> foo_baz 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) => { test ('serialize', (t) => {
const g = new Graph ('foo'); const g = new Graph ('foo');
@ -57,9 +78,44 @@ test ('serialize', (t) => {
const baz = g.add_node ('baz'); const baz = g.add_node ('baz');
const foo = g.add_node ('foo'); const foo = g.add_node ('foo');
g.add_edge (foo, baz); g.add_edge (foo, baz);
const serialized = g.toString (); const serialized = g.toString ();
t.is (serialized, result); 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);
});

View File

@ -11,16 +11,19 @@ const serialized_table = `bar_foo [label=<<table>
test ('serialize simple', (t) => { test ('serialize simple', (t) => {
const g = new Node ('foo', 'bar', 'baz'); const g = new Node ('foo', 'bar', 'baz');
g.color = Color.green; g.color = Color.green;
g.style = NodeStyles.dashed; g.style = NodeStyles.dashed;
const serialized = g.toString (); const serialized = g.toString ();
t.is (g.full_name, 'bar_foo'); t.is (g.full_name, 'bar_foo');
t.is (serialized, serialized_simple); t.is (serialized, serialized_simple);
}); });
test ('serialize table', (t) => { test ('serialize table', (t) => {
const g = new Node ('foo', 'bar', 'baz'); const g = new Node ('foo', 'bar', 'baz');
g.color = Color.green; g.color = Color.green;
g.style = NodeStyles.invisible; g.style = NodeStyles.invisible;
@ -44,6 +47,7 @@ test ('serialize table', (t) => {
g.is_table = true; g.is_table = true;
const serialized = g.toString (); const serialized = g.toString ();
t.is (g.full_name, 'bar_foo'); t.is (g.full_name, 'bar_foo');
t.is (serialized, serialized_table); t.is (serialized, serialized_table);
}); });
@ -55,7 +59,12 @@ test ('adhere to naming convention', (t) => {
test ('throw on invalid name', (t) => { test ('throw on invalid name', (t) => {
t.throws (() => { t.throws (() => {
<<<<<<< HEAD
const n = new Node ('564#+-.,/@', 'parent'); const n = new Node ('564#+-.,/@', 'parent');
=======
const n = new Node ('invalid.name', 'parent');
>>>>>>> f553396eee29e1e0820624345e5d23dc3471a4a4
return n.toString (); return n.toString ();
}, { message: 'invalid name specified' }); }, { message: 'invalid name specified' });
}); });

View File

@ -228,31 +228,31 @@
fastq "^1.6.0" fastq "^1.6.0"
"@scode/encoding-helper@^1.0.21": "@scode/encoding-helper@^1.0.21":
version "1.0.25" version "1.0.26"
resolved "https://npm.scode.ovh/@scode%2fencoding-helper/-/encoding-helper-1.0.25.tgz#a6e1fe47525d99fe24b0f9df554d688c8d9e5ab0" resolved "https://npm.scode.ovh/@scode%2fencoding-helper/-/encoding-helper-1.0.26.tgz#d0e4627e16885ecb10bdc3b1b87b5bb9aae39455"
integrity sha512-cMLfuCKhawpqasdkG8DuYMKmh1p2WzB7MuCiiFTglSU6zseISBCmEPmX1UduELY5+qt42KxmXiwgkC4/xK3MHQ== integrity sha512-f7hJzrHjI5D+yC8KnwGV1q/vARE0ZOUHe0f3fybCmzU38wobs0Jm+XgYi98ViXqFmvt18cz1NI1c4q9sdrcsQQ==
"@scode/eslint-config-es6@^1.0.1": "@scode/eslint-config-es6@^1.0.1":
version "1.0.22" version "1.0.24"
resolved "https://npm.scode.ovh/@scode%2feslint-config-es6/-/eslint-config-es6-1.0.22.tgz#be1a200c821137e74b829ec0029acc81734f1fca" resolved "https://npm.scode.ovh/@scode%2feslint-config-es6/-/eslint-config-es6-1.0.24.tgz#c3c88ac98beb9dc73da7812f614e8f289cec763b"
integrity sha512-/BnuycrNMFm4KKk2rz7JF0DBAN/JvWKpE0uK2fLz8m4o88TX5AKrlUg/w4c6ctPTzhD9t9tS9tuZFn/Vs0J3mg== integrity sha512-FyMzNbI6ZgGTr7bG1av+VSuYYWZIRbUp+ZvjVwtZduKkijXotOTg3jw0vMWi09IEq7esusQQZ/lZInAjvXJWKA==
dependencies: dependencies:
"@scode/eslint-config" "^2.0.1" "@scode/eslint-config" "^2.0.1"
eslint-plugin-import "^2.20.1" eslint-plugin-import "^2.20.1"
"@scode/eslint-config-ts@^1.0.22": "@scode/eslint-config-ts@^1.0.22":
version "1.0.23" version "1.0.27"
resolved "https://npm.scode.ovh/@scode%2feslint-config-ts/-/eslint-config-ts-1.0.23.tgz#fc29e278fffd3737657a1acdf69ade81cd0736c5" resolved "https://npm.scode.ovh/@scode%2feslint-config-ts/-/eslint-config-ts-1.0.27.tgz#d3d068df6f287273041029f4549378ecaa17972b"
integrity sha512-V+wzFQvl5sypixhTeNxO0lTaCDp6TSNMvbNfpFsNUl4kEZVNtlPY9+9Bf7Xnn7GY0qoA5d/fdqn4Qkxo96UhTA== integrity sha512-KapsP1enFNw4kBjI4L3WKBnwKeKp6Ka9ml3OoT7zpGZZLd9nzvyD1SpGks9PQ1ZJse9nBy2HVuRVPAHzfIBvmw==
dependencies: dependencies:
"@scode/eslint-config-es6" "^1.0.1" "@scode/eslint-config-es6" "^1.0.1"
"@typescript-eslint/eslint-plugin" "^2.26.0" "@typescript-eslint/eslint-plugin" "^2.26.0"
"@typescript-eslint/parser" "^2.26.0" "@typescript-eslint/parser" "^2.26.0"
"@scode/eslint-config@^2.0.1": "@scode/eslint-config@^2.0.1":
version "2.0.11" version "2.0.13"
resolved "https://npm.scode.ovh/@scode%2feslint-config/-/eslint-config-2.0.11.tgz#3cc3cd71f3bc3ac39868bf608e0517bee09da58f" resolved "https://npm.scode.ovh/@scode%2feslint-config/-/eslint-config-2.0.13.tgz#e7e4d3c9185449de7a7d810f3ee783459b779e8a"
integrity sha512-K8DpFdmepU1FNp0QJn5gbXS45g7k04rFUJp2OKQDqSa+3iywBvi44pMzJNOdZjkj+t3dGcOdeWcYOngz2MjdlA== integrity sha512-ans0dulrnReK+9+XD5nw04kKEdveEVbRL9AKH3PTr8jUQJBY/pzeDznkf6oWnLPBKqbDn/MEKlS5MOExAgooWw==
dependencies: dependencies:
eslint-plugin-jsdoc "^24.0.0" eslint-plugin-jsdoc "^24.0.0"
eslint-plugin-node "^11.0.0" eslint-plugin-node "^11.0.0"
@ -315,39 +315,39 @@
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
"@typescript-eslint/eslint-plugin@^2.26.0": "@typescript-eslint/eslint-plugin@^2.26.0":
version "2.29.0" version "2.30.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.29.0.tgz#c9efab7624e3dd6d144a0e4577a541d1bd42c2ac" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.30.0.tgz#312a37e80542a764d96e8ad88a105316cdcd7b05"
integrity sha512-X/YAY7azKirENm4QRpT7OVmzok02cSkqeIcLmdz6gXUQG4Hk0Fi9oBAynSAyNXeGdMRuZvjBa0c1Lu0dn/u6VA== integrity sha512-PGejii0qIZ9Q40RB2jIHyUpRWs1GJuHP1pkoCiaeicfwO9z7Fx03NQzupuyzAmv+q9/gFNHu7lo1ByMXe8PNyg==
dependencies: dependencies:
"@typescript-eslint/experimental-utils" "2.29.0" "@typescript-eslint/experimental-utils" "2.30.0"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
regexpp "^3.0.0" regexpp "^3.0.0"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@2.29.0": "@typescript-eslint/experimental-utils@2.30.0":
version "2.29.0" version "2.30.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.29.0.tgz#3cb8060de9265ba131625a96bbfec31ba6d4a0fe" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz#9845e868c01f3aed66472c561d4b6bac44809dd0"
integrity sha512-H/6VJr6eWYstyqjWXBP2Nn1hQJyvJoFdDtsHxGiD+lEP7piGnGpb/ZQd+z1ZSB1F7dN+WsxUDh8+S4LwI+f3jw== integrity sha512-L3/tS9t+hAHksy8xuorhOzhdefN0ERPDWmR9CclsIGOUqGKy6tqc/P+SoXeJRye5gazkuPO0cK9MQRnolykzkA==
dependencies: dependencies:
"@types/json-schema" "^7.0.3" "@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.29.0" "@typescript-eslint/typescript-estree" "2.30.0"
eslint-scope "^5.0.0" eslint-scope "^5.0.0"
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
"@typescript-eslint/parser@^2.26.0": "@typescript-eslint/parser@^2.26.0":
version "2.29.0" version "2.30.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.29.0.tgz#6e3c4e21ed6393dc05b9d8b47f0b7e731ef21c9c" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.30.0.tgz#7681c305a6f4341ae2579f5e3a75846c29eee9ce"
integrity sha512-H78M+jcu5Tf6m/5N8iiFblUUv+HJDguMSdFfzwa6vSg9lKR8Mk9BsgeSjO8l2EshKnJKcbv0e8IDDOvSNjl0EA== integrity sha512-9kDOxzp0K85UnpmPJqUzdWaCNorYYgk1yZmf4IKzpeTlSAclnFsrLjfwD9mQExctLoLoGAUXq1co+fbr+3HeFw==
dependencies: dependencies:
"@types/eslint-visitor-keys" "^1.0.0" "@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "2.29.0" "@typescript-eslint/experimental-utils" "2.30.0"
"@typescript-eslint/typescript-estree" "2.29.0" "@typescript-eslint/typescript-estree" "2.30.0"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/typescript-estree@2.29.0": "@typescript-eslint/typescript-estree@2.30.0":
version "2.29.0" version "2.30.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.29.0.tgz#1be6612bb02fc37ac9f466521c1459a4744e8d3a" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz#1b8e848b55144270255ffbfe4c63291f8f766615"
integrity sha512-3YGbtnWy4az16Egy5Fj5CckkVlpIh0MADtAQza+jiMADRSKkjdpzZp/5WuvwK/Qib3Z0HtzrDFeWanS99dNhnA== integrity sha512-nI5WOechrA0qAhnr+DzqwmqHsx7Ulr/+0H7bWCcClDhhWkSyZR5BmTvnBEyONwJCTWHfc5PAQExX24VD26IAVw==
dependencies: dependencies:
debug "^4.1.1" debug "^4.1.1"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"