Skip to content

Commit df513ef

Browse files
committed
feat: add gqm_gen script
1 parent 6ac9339 commit df513ef

File tree

10 files changed

+991
-1
lines changed

10 files changed

+991
-1
lines changed

measuring/questions/who-uses.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[⬑ back to the overall graph](../use_gqm.md)
22

3-
# Question: Who uses the InnerSource project?
3+
# **Question:** Who uses the InnerSource project?
44

55
Depending on the InnerSource project, usage of the project could look something like:
66

scripts/gqm_gen/.gitignore

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
6+
# Runtime data
7+
pids
8+
*.pid
9+
*.seed
10+
*.pid.lock
11+
12+
# Directory for instrumented libs generated by jscoverage/JSCover
13+
lib-cov
14+
15+
# Coverage directory used by tools like istanbul
16+
coverage
17+
18+
# nyc test coverage
19+
.nyc_output
20+
21+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22+
.grunt
23+
24+
# node-waf configuration
25+
.lock-wscript
26+
27+
# Compiled binary addons (http://nodejs.org/api/addons.html)
28+
build/Release
29+
30+
# Dependency directories
31+
node_modules
32+
jspm_packages
33+
34+
# Optional npm cache directory
35+
.npm
36+
37+
# Optional REPL history
38+
.node_repl_history
39+
40+
# TypeScript
41+
*.map
42+
*.js
43+
44+
# Project generated files
45+
*.pdf
46+
*.xlsx
47+
slides/*.md

scripts/gqm_gen/gqm.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
3+
4+
```mermaid
5+
graph TB
6+
find-projects.md[Find InnerSource Projects]
7+
reduce-duplication.md[Reduce duplication]
8+
who-contributes.md[Who contributes to the InnerSource project?]
9+
who-uses.md[Who uses the InnerSource project?]
10+
code-contributions.md[Code contributions]
11+
usage-count.md[Usage count]
12+
find-projects.md-->who-uses.md
13+
find-projects.md-->who-contributes.md
14+
reduce-duplication.md-->who-uses.md
15+
who-contributes.md-->code-contributions.md
16+
who-uses.md-->usage-count.md
17+
18+
```

scripts/gqm_gen/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as Commonmark from 'commonmark';
2+
export declare function getLinks(parsed: Commonmark.Node): {
3+
url: string | null;
4+
text: string | null | undefined;
5+
}[];

scripts/gqm_gen/index.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as Commonmark from "commonmark";
2+
import test from "node:test";
3+
import assert from "node:assert";
4+
import { FileLink, Graph, LinkType } from "./types";
5+
import {
6+
getLinks,
7+
getFileLinks,
8+
generateMermaidDiagram,
9+
appendToGraph,
10+
getGQMFileLinks
11+
} from "./index";
12+
13+
test("can get links", () => {
14+
const node = new Commonmark.Node("paragraph");
15+
const links = getLinks(node);
16+
assert(links.length === 0);
17+
});
18+
19+
test("can get links from file", () => {
20+
const fileLinks: FileLink[] = getFileLinks(
21+
LinkType.GOAL,
22+
"../../measuring/goals/"
23+
);
24+
assert(fileLinks.length > 0);
25+
});
26+
27+
test("can generate mermaid diagram", () => {
28+
const graph: Graph = {
29+
nodes: [],
30+
edges: [],
31+
};
32+
const diagram = generateMermaidDiagram(graph);
33+
assert(diagram.indexOf("graph TB") > 1);
34+
});
35+
36+
test("can generate mermaid diagram from file", { only: true }, () => {
37+
38+
39+
const graph = getGQMFileLinks();
40+
41+
const diagram = generateMermaidDiagram(graph);
42+
console.log(diagram)
43+
assert(diagram.indexOf("graph TB") > 1);
44+
});

scripts/gqm_gen/index.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import * as Commonmark from "commonmark";
2+
import * as fs from "fs";
3+
import * as path from "path";
4+
import { Node, Edge, Graph, Link, LinkType, NodeShape, ArrowType } from "./types";
5+
import { FileLink } from "./types";
6+
7+
const graph = getGQMFileLinks();
8+
9+
export function getGQMFileLinks() {
10+
const graph: Graph = {
11+
nodes: [],
12+
edges: [],
13+
};
14+
15+
const goalsPath = "../../measuring/goals/";
16+
const questionsPath = "../../measuring/questions/";
17+
const metricsPath = "../../measuring/metrics/";
18+
19+
const goalFileLinks: FileLink[] = getFileLinks(LinkType.GOAL, goalsPath);
20+
appendToGraph(graph, goalFileLinks);
21+
22+
const questionFileLinks: FileLink[] = getFileLinks(
23+
LinkType.QUESTION,
24+
questionsPath
25+
);
26+
appendToGraph(graph, questionFileLinks);
27+
28+
const metricFileLinks: FileLink[] = getFileLinks(LinkType.METRIC, metricsPath);
29+
appendToGraph(graph, metricFileLinks);
30+
31+
return graph;
32+
}
33+
34+
export function appendToGraph(graph: Graph, goalFileLinks: FileLink[]) {
35+
goalFileLinks.forEach((fileLink) => {
36+
const node: Node = {
37+
id: fileLink.file,
38+
shape: NodeShape.RECT,
39+
label: fileLink.label,
40+
};
41+
if (node.label) {
42+
graph.nodes.push(node);
43+
}
44+
45+
fileLink.links.forEach((link) => {
46+
const edge: Edge = {
47+
from: fileLink.file,
48+
to: link.name,
49+
arrowType: "arrow",
50+
};
51+
if(edge.from && edge.to){
52+
graph.edges.push(edge);
53+
}
54+
});
55+
});
56+
return graph
57+
}
58+
59+
export function getFileLinks(linkType: LinkType, filePath: string) {
60+
const parser = new Commonmark.Parser();
61+
62+
const goalFiles = fs.readdirSync(filePath);
63+
let fileLinks: FileLink[] = [];
64+
goalFiles.forEach((fileName) => {
65+
if (fileName.endsWith("template.md")) return;
66+
const data = fs.readFileSync(`${filePath}/${fileName}`, "utf-8");
67+
const parsed = parser.parse(data);
68+
const label = getHeading(parsed);
69+
const links = getLinks(parsed);
70+
71+
const fileLink: FileLink = {
72+
linkType: linkType,
73+
file: fileName,
74+
label: label,
75+
links,
76+
};
77+
fileLinks.push(fileLink);
78+
});
79+
return fileLinks;
80+
}
81+
82+
export function getHeading(parsed: Commonmark.Node) {
83+
const walker = parsed.walker();
84+
let event, node;
85+
let heading: string = "No Heading";
86+
while ((event = walker.next())) {
87+
node = event.node;
88+
if (event.entering && node.type === "heading") {
89+
heading = node.lastChild?.literal as string;
90+
break;
91+
}
92+
}
93+
return heading.trim();
94+
}
95+
96+
export function getLinks(parsed: Commonmark.Node) {
97+
const walker = parsed.walker();
98+
let event, node;
99+
const links: Link[] = [];
100+
while ((event = walker.next())) {
101+
node = event.node;
102+
if (event.entering && node.type === "link") {
103+
const destination = node.destination as string;
104+
const text = node.firstChild?.literal as string;
105+
const link: Link = {
106+
url: destination,
107+
text: text,
108+
name: path.parse(destination).base,
109+
};
110+
111+
if (link.url.indexOf('.md') > -1 && link.url.indexOf('use_gqm') === -1) {
112+
links.push(link);
113+
}
114+
}
115+
}
116+
return links;
117+
}
118+
119+
export function getNodeShapeSyntax(node: Node) {
120+
switch (node.shape) {
121+
case 'rect':
122+
return `[${node.label}]`;
123+
case 'circ':
124+
return `((${node.label}))`;
125+
case 'roundrect':
126+
return `((${node.label}))`;
127+
case 'diamond':
128+
return `{${node.label}}`;
129+
default:
130+
return `[${node.label}]`;
131+
}
132+
}
133+
134+
export function generateMermaidDiagram(graph: Graph) {
135+
const nodes = graph.nodes;
136+
const edges = graph.edges;
137+
138+
let mermaidSyntax = "```mermaid\ngraph TB\n";
139+
140+
nodes.forEach((node) => {
141+
const nodeSyntax = getNodeShapeSyntax(node);
142+
mermaidSyntax += `${node.id}${nodeSyntax}\n`;
143+
});
144+
145+
edges.forEach((edge) => {
146+
const arrowSyntax: string = ArrowType.ARROW;
147+
mermaidSyntax += `${edge.from}${arrowSyntax}${edge.to}\n`;
148+
});
149+
mermaidSyntax += "\n```";
150+
return mermaidSyntax;
151+
}
152+
153+
console.log(generateMermaidDiagram(graph))

0 commit comments

Comments
 (0)