diff --git a/jenkins.js b/jenkins.js index 0a0c545..0c9ab17 100644 --- a/jenkins.js +++ b/jenkins.js @@ -7,6 +7,7 @@ const fs = require ('fs'); const child_process = require ('child_process'); const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8')); + [ ,, pkg.version ] = process.argv; diff --git a/lib/Edge.ts b/lib/Edge.ts index 2ba92ca..3709f04 100644 --- a/lib/Edge.ts +++ b/lib/Edge.ts @@ -6,15 +6,18 @@ export class Edge { public target: string; public style?: EdgeStyles; public color?: Color; + private _directional: boolean; - public constructor (origin: string, target: string) { + public constructor (origin: string, target: string, directional: boolean) { this.origin = origin; this.target = target; + this._directional = directional; } // eslint-disable-next-line @typescript-eslint/naming-convention public toString (): string { const attributes = []; + if (this.style) attributes.push ({ name: 'style', value: this.style.toString () }); if (this.color) @@ -23,7 +26,9 @@ export class Edge { const attr_string = ` [${attributes.map ((v) => `${v.name}="${v.value}"`) .join (',')}]`; - return `${this.origin} -> ${this.target}${attributes.length > 0 + return `${this.origin} -${ + this._directional ? '>' : '-' + } ${this.target}${attributes.length > 0 ? attr_string : ''}`; } diff --git a/lib/Element.ts b/lib/Element.ts index 9c625d3..f0502e6 100644 --- a/lib/Element.ts +++ b/lib/Element.ts @@ -25,6 +25,13 @@ export class Element { } 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.parent_name = parent; } diff --git a/lib/Graph.ts b/lib/Graph.ts index 6582c77..24beed0 100644 --- a/lib/Graph.ts +++ b/lib/Graph.ts @@ -3,6 +3,7 @@ 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; @@ -18,19 +19,35 @@ export class Graph extends Element { public edges: Array = []; public style?: GraphStyles; 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 public toString (): string { const header = this.parent ? `subgraph cluster_${this.full_name}` - : `digraph ${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 () }); + : `${this.directional ? 'di' : ''}graph ${this.full_name}`; - let attrs = `\n${attributes.map ((v) => `${v.name} = ${v.value}`) + let attrs = `\n${this.attributes.map ((v) => `${v.name} = ${v.value}`) .join ('\n')}\n`; let children = `\n${this.children.map ((c) => c.toString ()) .join ('\n')}\n`; @@ -75,6 +92,8 @@ export class Graph extends Element { public add_graph (constructor: ((g: Graph) => void) | string): string { const graph = new Graph ('unnamed', this.full_name); + graph.directional = this.directional; + if (typeof constructor === 'string') graph.name = constructor; else @@ -85,6 +104,6 @@ export class Graph extends Element { } public add_edge (origin: string, target: string): void { - this.edges.push (new Edge (origin, target)); + this.edges.push (new Edge (origin, target, this.directional)); } } diff --git a/lib/GraphLayouts.ts b/lib/GraphLayouts.ts new file mode 100644 index 0000000..29ae2ba --- /dev/null +++ b/lib/GraphLayouts.ts @@ -0,0 +1,10 @@ +export enum GraphLayouts { + neato = 'neato', + dot = 'dot', + circo = 'circo', + fdp = 'fdp', + sfdp = 'sfdp', + osage = 'osage', + twopi = 'twopi', + patchwork = 'patchwork' +} diff --git a/lib/Node.ts b/lib/Node.ts index 815712e..0f72262 100644 --- a/lib/Node.ts +++ b/lib/Node.ts @@ -20,6 +20,7 @@ export class Node extends Element { const mapped_columns = this.table_contents .map ((val) => `${val.join ('')}`); + return `\n ${ mapped_columns.join ('\n ') }\n
`; @@ -28,6 +29,7 @@ export class Node extends Element { // eslint-disable-next-line @typescript-eslint/naming-convention public toString (): string { const attributes = []; + if (this.label || this.is_table) { attributes.push ({ name: 'label', @@ -51,6 +53,7 @@ export class Node extends Element { '"', '"' ]; + return `${v.name}=${d[0]}${v.value}${d[1]}`; }) .join (', '); diff --git a/lib/index.ts b/lib/index.ts index d1f0946..e029ed0 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -4,3 +4,4 @@ export { Element } from './Element'; export { Edge } from './Edge'; export { Color } from './Color'; export { EdgeStyles, NodeStyles, GraphStyles } from './Styles'; +export { GraphLayouts } from './GraphLayouts'; diff --git a/package.json b/package.json index 84e6669..53b8f27 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "compile": "tsc" }, "files": [ - "/dist/", + "/dist/lib/", "LICENSE" ], "dependencies": { diff --git a/test/Color.ts b/test/Color.ts index 5a77f1f..75b020a 100644 --- a/test/Color.ts +++ b/test/Color.ts @@ -4,6 +4,7 @@ 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'); }); diff --git a/test/Edge.ts b/test/Edge.ts index 9cd51c9..faa2610 100644 --- a/test/Edge.ts +++ b/test/Edge.ts @@ -2,9 +2,21 @@ import test from 'ava'; import { Edge, Color, EdgeStyles } from '../lib'; test ('serialize', (t) => { - const e = new Edge ('foo', 'bar'); + 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"]'); }); diff --git a/test/Graph.ts b/test/Graph.ts index e591755..4208017 100644 --- a/test/Graph.ts +++ b/test/Graph.ts @@ -1,5 +1,5 @@ import test from 'ava'; -import { Graph, GraphStyles, Color, NodeStyles } from '../lib'; +import { Graph, GraphStyles, Color, NodeStyles, GraphLayouts } from '../lib'; const result = `digraph foo { subgraph cluster_foo_baz { @@ -26,6 +26,27 @@ const result = `digraph 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'); @@ -57,9 +78,44 @@ test ('serialize', (t) => { 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); +}); diff --git a/test/Node.ts b/test/Node.ts index 561fa24..18a447d 100644 --- a/test/Node.ts +++ b/test/Node.ts @@ -11,16 +11,19 @@ const serialized_table = `bar_foo [label=< 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; @@ -44,6 +47,7 @@ test ('serialize table', (t) => { g.is_table = true; const serialized = g.toString (); + t.is (g.full_name, 'bar_foo'); t.is (serialized, serialized_table); }); @@ -55,7 +59,12 @@ test ('adhere to naming convention', (t) => { test ('throw on invalid name', (t) => { t.throws (() => { +<<<<<<< HEAD const n = new Node ('564#+-.,/@', 'parent'); +======= + const n = new Node ('invalid.name', 'parent'); + +>>>>>>> f553396eee29e1e0820624345e5d23dc3471a4a4 return n.toString (); }, { message: 'invalid name specified' }); }); diff --git a/yarn.lock b/yarn.lock index b641695..3b27672 100644 --- a/yarn.lock +++ b/yarn.lock @@ -228,31 +228,31 @@ fastq "^1.6.0" "@scode/encoding-helper@^1.0.21": - version "1.0.25" - resolved "https://npm.scode.ovh/@scode%2fencoding-helper/-/encoding-helper-1.0.25.tgz#a6e1fe47525d99fe24b0f9df554d688c8d9e5ab0" - integrity sha512-cMLfuCKhawpqasdkG8DuYMKmh1p2WzB7MuCiiFTglSU6zseISBCmEPmX1UduELY5+qt42KxmXiwgkC4/xK3MHQ== + version "1.0.26" + resolved "https://npm.scode.ovh/@scode%2fencoding-helper/-/encoding-helper-1.0.26.tgz#d0e4627e16885ecb10bdc3b1b87b5bb9aae39455" + integrity sha512-f7hJzrHjI5D+yC8KnwGV1q/vARE0ZOUHe0f3fybCmzU38wobs0Jm+XgYi98ViXqFmvt18cz1NI1c4q9sdrcsQQ== "@scode/eslint-config-es6@^1.0.1": - version "1.0.22" - resolved "https://npm.scode.ovh/@scode%2feslint-config-es6/-/eslint-config-es6-1.0.22.tgz#be1a200c821137e74b829ec0029acc81734f1fca" - integrity sha512-/BnuycrNMFm4KKk2rz7JF0DBAN/JvWKpE0uK2fLz8m4o88TX5AKrlUg/w4c6ctPTzhD9t9tS9tuZFn/Vs0J3mg== + version "1.0.24" + resolved "https://npm.scode.ovh/@scode%2feslint-config-es6/-/eslint-config-es6-1.0.24.tgz#c3c88ac98beb9dc73da7812f614e8f289cec763b" + integrity sha512-FyMzNbI6ZgGTr7bG1av+VSuYYWZIRbUp+ZvjVwtZduKkijXotOTg3jw0vMWi09IEq7esusQQZ/lZInAjvXJWKA== dependencies: "@scode/eslint-config" "^2.0.1" eslint-plugin-import "^2.20.1" "@scode/eslint-config-ts@^1.0.22": - version "1.0.23" - resolved "https://npm.scode.ovh/@scode%2feslint-config-ts/-/eslint-config-ts-1.0.23.tgz#fc29e278fffd3737657a1acdf69ade81cd0736c5" - integrity sha512-V+wzFQvl5sypixhTeNxO0lTaCDp6TSNMvbNfpFsNUl4kEZVNtlPY9+9Bf7Xnn7GY0qoA5d/fdqn4Qkxo96UhTA== + version "1.0.27" + resolved "https://npm.scode.ovh/@scode%2feslint-config-ts/-/eslint-config-ts-1.0.27.tgz#d3d068df6f287273041029f4549378ecaa17972b" + integrity sha512-KapsP1enFNw4kBjI4L3WKBnwKeKp6Ka9ml3OoT7zpGZZLd9nzvyD1SpGks9PQ1ZJse9nBy2HVuRVPAHzfIBvmw== dependencies: "@scode/eslint-config-es6" "^1.0.1" "@typescript-eslint/eslint-plugin" "^2.26.0" "@typescript-eslint/parser" "^2.26.0" "@scode/eslint-config@^2.0.1": - version "2.0.11" - resolved "https://npm.scode.ovh/@scode%2feslint-config/-/eslint-config-2.0.11.tgz#3cc3cd71f3bc3ac39868bf608e0517bee09da58f" - integrity sha512-K8DpFdmepU1FNp0QJn5gbXS45g7k04rFUJp2OKQDqSa+3iywBvi44pMzJNOdZjkj+t3dGcOdeWcYOngz2MjdlA== + version "2.0.13" + resolved "https://npm.scode.ovh/@scode%2feslint-config/-/eslint-config-2.0.13.tgz#e7e4d3c9185449de7a7d810f3ee783459b779e8a" + integrity sha512-ans0dulrnReK+9+XD5nw04kKEdveEVbRL9AKH3PTr8jUQJBY/pzeDznkf6oWnLPBKqbDn/MEKlS5MOExAgooWw== dependencies: eslint-plugin-jsdoc "^24.0.0" eslint-plugin-node "^11.0.0" @@ -315,39 +315,39 @@ integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== "@typescript-eslint/eslint-plugin@^2.26.0": - version "2.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.29.0.tgz#c9efab7624e3dd6d144a0e4577a541d1bd42c2ac" - integrity sha512-X/YAY7azKirENm4QRpT7OVmzok02cSkqeIcLmdz6gXUQG4Hk0Fi9oBAynSAyNXeGdMRuZvjBa0c1Lu0dn/u6VA== + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.30.0.tgz#312a37e80542a764d96e8ad88a105316cdcd7b05" + integrity sha512-PGejii0qIZ9Q40RB2jIHyUpRWs1GJuHP1pkoCiaeicfwO9z7Fx03NQzupuyzAmv+q9/gFNHu7lo1ByMXe8PNyg== dependencies: - "@typescript-eslint/experimental-utils" "2.29.0" + "@typescript-eslint/experimental-utils" "2.30.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.29.0": - version "2.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.29.0.tgz#3cb8060de9265ba131625a96bbfec31ba6d4a0fe" - integrity sha512-H/6VJr6eWYstyqjWXBP2Nn1hQJyvJoFdDtsHxGiD+lEP7piGnGpb/ZQd+z1ZSB1F7dN+WsxUDh8+S4LwI+f3jw== +"@typescript-eslint/experimental-utils@2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz#9845e868c01f3aed66472c561d4b6bac44809dd0" + integrity sha512-L3/tS9t+hAHksy8xuorhOzhdefN0ERPDWmR9CclsIGOUqGKy6tqc/P+SoXeJRye5gazkuPO0cK9MQRnolykzkA== dependencies: "@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-utils "^2.0.0" "@typescript-eslint/parser@^2.26.0": - version "2.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.29.0.tgz#6e3c4e21ed6393dc05b9d8b47f0b7e731ef21c9c" - integrity sha512-H78M+jcu5Tf6m/5N8iiFblUUv+HJDguMSdFfzwa6vSg9lKR8Mk9BsgeSjO8l2EshKnJKcbv0e8IDDOvSNjl0EA== + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.30.0.tgz#7681c305a6f4341ae2579f5e3a75846c29eee9ce" + integrity sha512-9kDOxzp0K85UnpmPJqUzdWaCNorYYgk1yZmf4IKzpeTlSAclnFsrLjfwD9mQExctLoLoGAUXq1co+fbr+3HeFw== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.29.0" - "@typescript-eslint/typescript-estree" "2.29.0" + "@typescript-eslint/experimental-utils" "2.30.0" + "@typescript-eslint/typescript-estree" "2.30.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.29.0": - version "2.29.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.29.0.tgz#1be6612bb02fc37ac9f466521c1459a4744e8d3a" - integrity sha512-3YGbtnWy4az16Egy5Fj5CckkVlpIh0MADtAQza+jiMADRSKkjdpzZp/5WuvwK/Qib3Z0HtzrDFeWanS99dNhnA== +"@typescript-eslint/typescript-estree@2.30.0": + version "2.30.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz#1b8e848b55144270255ffbfe4c63291f8f766615" + integrity sha512-nI5WOechrA0qAhnr+DzqwmqHsx7Ulr/+0H7bWCcClDhhWkSyZR5BmTvnBEyONwJCTWHfc5PAQExX24VD26IAVw== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0"