1
0
Fork 0

feat: add graphviz output mode

This commit is contained in:
ssube 2020-08-16 23:01:02 -05:00 committed by BZ Libby
parent cbf5c5195f
commit ddc1d6b9c8
3 changed files with 187 additions and 0 deletions

View File

@ -4,6 +4,7 @@ import { VERSION_INFO } from '../version';
export enum Commands {
UNKNOWN = 'unknown',
GRAPH = 'dot-graph',
ISSUES = 'sync-issues',
LABELS = 'sync-labels',
}
@ -24,6 +25,11 @@ type Modeback = (mode: string) => void;
export function createParser(modeset: Modeback): Parser<ParsedArgs> {
/* eslint-disable-next-line sonarjs/prefer-immediate-return */
const parser = usage(`Usage: ${VERSION_INFO.package.name} <mode> [options]`)
.command({
command: Commands.GRAPH,
describe: 'graph label state changes',
handler: () => modeset(Commands.GRAPH),
})
.command({
command: Commands.ISSUES,
describe: 'sync issue labels',

176
src/graph.ts Normal file
View File

@ -0,0 +1,176 @@
import { BaseLabel, FlagLabel, getValueName, StateLabel } from './labels';
import { ChangeVerb } from './resolve';
export interface Node {
name: string;
}
export interface Edge {
source: string;
target: string;
type: ChangeVerb;
}
export interface Graph {
edges: Array<Edge>;
name: string;
nodes: Array<Node>;
subs: Array<Graph>;
}
export interface GraphOptions {
flags: Array<FlagLabel>;
name: string;
states: Array<StateLabel>;
}
function labelEdges(label: BaseLabel, edges: Array<Edge>) {
for (const add of label.adds) {
edges.push({
source: label.name,
target: add.name,
type: ChangeVerb.CREATED,
});
}
for (const remove of label.removes) {
edges.push({
source: label.name,
target: remove.name,
type: ChangeVerb.REMOVED,
});
}
for (const require of label.requires) {
edges.push({
source: label.name,
target: require.name,
type: ChangeVerb.REQUIRED,
});
}
}
export function graphLabels(options: GraphOptions): Graph {
const edges: Array<Edge> = [];
const nodes: Array<Node> = [];
for (const flag of options.flags) {
nodes.push({
name: flag.name,
});
labelEdges(flag, edges);
}
const subs: Array<Graph> = [];
for (const state of options.states) {
const sub: Graph = {
edges: [],
name: state.name,
nodes: [],
subs: [],
};
for (const value of state.values) {
const name = getValueName(state, value);
sub.nodes.push({
name,
});
labelEdges({
...value,
name,
}, edges);
for (const become of value.becomes) {
const matchNames = become.matches.map((it) => it.name);
const becomeName = [name, 'with'].concat(matchNames).join(',');
sub.edges.push({
source: name,
target: becomeName,
type: ChangeVerb.EXISTING,
});
labelEdges({
adds: become.adds,
name: becomeName,
priority: value.priority,
removes: become.removes,
requires: become.matches,
}, sub.edges);
}
}
subs.push(sub);
}
return {
edges,
name: options.name,
nodes,
subs,
};
}
export function cleanName(name: string): string {
return name.replace(/[^a-z0-9_]/g, '_');
}
export function edgeStyle(edge: Edge) {
switch (edge.type) {
case ChangeVerb.CREATED:
return '[color="green"]';
case ChangeVerb.EXISTING:
return '[color="purple"]';
case ChangeVerb.REMOVED:
return '[color="red"]';
case ChangeVerb.CONFLICTED:
return '[color="orange"]';
case ChangeVerb.REQUIRED:
return '[color="blue"]';
default:
return '';
}
}
export function dotGraph(graph: Graph): string {
const lines = [];
const name = cleanName(graph.name);
lines.push(`digraph ${name} {`);
for (const sub of graph.subs) {
const subName = cleanName(sub.name);
lines.push(`subgraph cluster_${subName} {`);
lines.push(`label = "${subName}";`);
lines.push('color = blue');
for (const edge of sub.edges) {
const source = cleanName(edge.source);
const target = cleanName(edge.target);
lines.push(`${source} -> ${target} ${edgeStyle(edge)};`);
}
for (const node of sub.nodes) {
const nodeName = cleanName(node.name);
lines.push(`${nodeName} [style=filled];`);
}
lines.push('}');
}
for (const edge of graph.edges) {
const source = cleanName(edge.source);
const target = cleanName(edge.target);
lines.push(`${source} -> ${target} ${edgeStyle(edge)};`);
}
for (const node of graph.nodes) {
const nodeName = cleanName(node.name);
lines.push(`${nodeName} [style=filled];`);
}
lines.push('}');
return lines.join('\n');
}

View File

@ -11,6 +11,7 @@ import { BunyanLogger } from './logger/bunyan';
import { GithubRemote } from './remote/github';
import { syncIssueLabels, SyncOptions, syncProjectLabels } from './sync';
import { VERSION_INFO } from './version';
import { graphLabels, dotGraph } from './graph';
export { FlagLabel, StateLabel } from './labels';
export { Remote, RemoteOptions } from './remote';
@ -85,6 +86,10 @@ export async function main(argv: Array<string>): Promise<number> {
states,
};
switch (mode) {
case Commands.GRAPH:
const graph = graphLabels(project);
process.stdout.write(dotGraph(graph));
break;
case Commands.ISSUES:
await syncIssueLabels(options);
break;