forked from jaime-olivares/vscode-yuml
-
Notifications
You must be signed in to change notification settings - Fork 5
/
index.mjs
180 lines (165 loc) · 6 KB
/
index.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
const diagramTypes = {
class: "class-diagram",
usecase: "usecase-diagram",
activity: "activity-diagram",
state: "state-diagram",
deployment: "deployment-diagram",
package: "package-diagram",
sequence: "sequence-diagram",
};
const directions = {
topDown: "TB",
leftToRight: "LR",
rightToLeft: "RL",
};
/**
* Generates SVG diagram.
* @param {string | Buffer | Readable} input The yUML document to parse
* @param {object} [options] - The options to be set for generating the SVG
* @param {string} [options.dir] - The direction of the diagram "TB" (default) - topDown, "LR" - leftToRight, "RL" - rightToLeft
* @param {string} [options.type] - The type of SVG - "class" (default), "usecase", "activity", "state", "deployment", "package".
* @param {string} [options.isDark] - Option to get dark or light diagram
* @param {object} [options.dotHeaderOverrides] - Dot HEADER overrides (Not supported for Sequence diagrams)
* @param {object} [vizOptions] - @see https://github.com/mdaines/viz.js/wiki/API#new-vizoptions (should be undefined for back-end rendering)
* @param {string|URL} [vizOptions.workerUrl] - URL of one of the rendering script files
* @param {Worker} [vizOptions.worker] - Worker instance constructed with the URL or path of one of the rendering script files
* @param {object} [renderOptions] - @see https://github.com/mdaines/viz.js/wiki/API#render-options
* @param {string} [renderOptions.engine] - layout engine
* @param {string} [renderOptions.format] - desired output format (only "svg" is supported)
* @param {boolean} [renderOptions.yInvert] - invert the y coordinate in output (not supported with "svg" format output)
* @param {object[]} [renderOptions.images] - image dimensions to use when rendering nodes with image attributes
* @param {object[]} [renderOptions.files] - files to make available to Graphviz using Emscripten's in-memory filesystem
* @returns {Promise<string>} The rendered diagram as a SVG document (or other format if specified in renderOptions)
*/
export default (input, options, vizOptions, renderOptions) => {
if (!options) options = {};
if (!options.dir) options.dir = "TB";
if (!options.type) options.type = "class";
if (!options.isDark) options.isDark = false;
const diagramInstructions = [];
if (input.read && "function" === typeof input.read) {
return import(`./src/utils/handle-stream.mjs`)
.then(module => module.default)
.then(handleStream =>
handleStream(input, processLine(options, diagramInstructions)),
)
.then(() =>
processYumlData(
diagramInstructions,
options,
vizOptions,
renderOptions,
),
);
} else {
input
.toString()
.split(/\r|\n/)
.forEach(processLine(options, diagramInstructions));
return processYumlData(
diagramInstructions,
options,
vizOptions,
renderOptions,
);
}
};
const processYumlData = (
diagramInstructions,
options,
vizOptions,
renderOptions,
) => {
if (diagramInstructions.length === 0) {
return Promise.resolve("");
}
if (!options.hasOwnProperty("type")) {
return Promise.reject(
new Error("Error: Missing mandatory 'type' directive"),
);
}
if (options.type in diagramTypes) {
const { isDark, dotHeaderOverrides } = options;
try {
const renderingPromise = import(
`./src/diagrams/${diagramTypes[options.type]}.mjs`
).then(module => module.default(diagramInstructions, options));
// Sequence diagrams are rendered as SVG, not dot file -- and have no embedded images (I guess)
return options.type === "sequence"
? renderingPromise
: Promise.all([
Promise.all([
import(`./src/utils/dot2svg.mjs`).then(module => module.default),
import(`./src/utils/wrapDotDocument.mjs`).then(
module => module.default,
),
renderingPromise,
]).then(([dot2svg, wrapDotDocument, dotDocument]) =>
dot2svg(
wrapDotDocument(dotDocument, isDark, dotHeaderOverrides),
vizOptions,
renderOptions,
),
),
import(`./src/utils/svg-utils.mjs`).then(module => module.default),
]).then(([svg, processEmbeddedImages]) =>
processEmbeddedImages(svg, isDark),
);
} catch (err) {
return Promise.reject(err);
}
} else {
return Promise.reject(new Error("Invalid diagram type"));
}
};
const processLine = (options, diagramInstructions) => line => {
line = line.trim();
if (line.startsWith("//")) {
processDirectives(line, options);
} else if (line.length) {
diagramInstructions.push(line);
}
};
const processDirectives = function (line, options) {
const keyValue = /^\/\/\s+\{\s*([\w]+)\s*:\s*([\w]+)\s*\}$/.exec(line); // extracts directives as: // {key:value}
if (keyValue !== null && keyValue.length === 3) {
const [_, key, value] = keyValue;
switch (key) {
case "type":
if (value in diagramTypes) {
options.type = value;
} else {
console.warn(
new Error(
"Invalid value for 'type'. Allowed values are: " +
Object.keys(diagramTypes).join(", "),
),
);
}
break;
case "direction":
if (value in directions) {
options.dir = directions[value];
} else {
console.warn(
new Error(
"Invalid value for 'direction'. Allowed values are: " +
Object.keys(directions).join(", "),
),
);
}
break;
case "generate":
if (/^(true|false)$/.test(value)) {
options.generate = value === "true";
console.warn("Generate option is not supported");
} else {
console.warn(
new Error(
"Error: invalid value for 'generate'. Allowed values are: true, false <i>(default)</i>.",
),
);
}
}
}
};