UNPKG

yuml2svg

Version:

UML diagramming tool based on the yUML syntax

181 lines (165 loc) 6.14 kB
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>.", ), ); } } } };