mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 09:03:02 +00:00
refactor: move core-tools to monorepo
This commit is contained in:
257
packages/core-tools/src/uml/create-diagram.ts
Normal file
257
packages/core-tools/src/uml/create-diagram.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import {createWriteStream} from 'fs';
|
||||
import * as request from 'got';
|
||||
import {forEach, map, isEmpty} from 'lodash';
|
||||
import {expandTypeValue, isLightweightClass, isUnionOrIntersectionType} from '../easy-ast/ast-util';
|
||||
import {LightweightAliasDefinition} from '../easy-ast/types/lightweight-alias-definition';
|
||||
import {LightweightClassDefinition} from '../easy-ast/types/lightweight-class-definition';
|
||||
import {LightweightDefinition} from '../easy-ast/types/lightweight-definition';
|
||||
import {LightweightProperty} from '../easy-ast/types/lightweight-property';
|
||||
import {LightweightType} from '../easy-ast/types/lightweight-type';
|
||||
import {UMLConfig} from './uml-config';
|
||||
|
||||
/**
|
||||
* Converts the lightweight class/enum definitions according to the configuration,
|
||||
* to valid PlantUML Code, which will then be encoded, converted by the plantuml server
|
||||
* and saved as a .svg file in directory, in which this method was called
|
||||
*
|
||||
* @param definitions all type definitions of the project
|
||||
* @param config contains information on how the PlantUML should be generated
|
||||
* @param plantUmlBaseURL Hostname of the PlantUML-Server
|
||||
*/
|
||||
export async function createDiagram(
|
||||
definitions: LightweightDefinition[],
|
||||
config: UMLConfig,
|
||||
plantUmlBaseURL: string,
|
||||
): Promise<string> {
|
||||
// when non definitions were specified use all
|
||||
config.definitions = map(definitions, 'name');
|
||||
|
||||
// when providing definitions and either showing associations or inheritance the
|
||||
// inherited definitions will be added automatically
|
||||
if (config.showInheritance) {
|
||||
// TODO: showInheritance
|
||||
/*const inheritedDefinitions = gatherTypeAssociations(
|
||||
definitions,
|
||||
config.definitions,
|
||||
);*/
|
||||
// config.definitions = config.definitions.concat(inheritedDefinitions);
|
||||
}
|
||||
|
||||
// creates a UML definition for every specified definition name
|
||||
// however if no definitions were provided all definitions will be transformed
|
||||
const modelPlantUMLCode = map(
|
||||
definitions.filter(it => !config.definitions.includes(it.name)),
|
||||
definition =>
|
||||
isLightweightClass(definition)
|
||||
? createPlantUMLCodeForClass(config, definition)
|
||||
: createPlantUMLCodeForEnum(config, definition),
|
||||
).join('');
|
||||
|
||||
return createDiagramFromString(modelPlantUMLCode, plantUmlBaseURL, config.outputFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will encode the plantuml code and post the code to the plantuml server
|
||||
* The server will then parse the code and create a corresponding diagram
|
||||
*
|
||||
* @param modelPlantUMLCode raw PlantUML code
|
||||
* @param plantUmlBaseURL PlantUML server address that shall be used
|
||||
* @param outputFile filename of the output file without file extension
|
||||
*/
|
||||
export async function createDiagramFromString(
|
||||
modelPlantUMLCode: string,
|
||||
plantUmlBaseURL: string,
|
||||
outputFile = `Diagram-${new Date().toISOString()}`,
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires,unicorn/prefer-module
|
||||
const plantumlEncoder = require('plantuml-encoder');
|
||||
const plantUMLCode = plantumlEncoder.encode(`@startuml\n${modelPlantUMLCode}\n@enduml`);
|
||||
const url = `${plantUmlBaseURL}/svg/${plantUMLCode}`;
|
||||
let response;
|
||||
try {
|
||||
response = await request.default.get(url);
|
||||
const httpOK = 200;
|
||||
if (response.statusCode !== httpOK) {
|
||||
await Logger.error(`Plantuml Server responded with an error.\n${response.statusMessage}`);
|
||||
throw new Error('Response not okay');
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.log(
|
||||
`Please try using the public plantuml server:\nhttp://www.plantuml.com/plantuml/svg/${plantUMLCode}`,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
// attach file extension
|
||||
const fileName = `${outputFile}.svg`;
|
||||
try {
|
||||
createWriteStream(fileName).write(response.body);
|
||||
Logger.log(`Writen data to file: ${fileName}`);
|
||||
} catch {
|
||||
throw new Error('Could not write file. Are you missing permissions?');
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively iterates over all types, to find implemented generic types and parents
|
||||
*
|
||||
* @param definitions all type definitions of the project
|
||||
* @param abstractionNames currently known string values of inherited classes
|
||||
*/
|
||||
|
||||
/*function gatherTypeAssociations(
|
||||
definitions: LightweightDefinition[],
|
||||
abstractionNames: string[],
|
||||
): string[] {
|
||||
let abstractions: string[] = [];
|
||||
for (const name of abstractionNames) {
|
||||
const declaration = definitions.find(
|
||||
(definition) => definition.name === name,
|
||||
);
|
||||
if (isLightweightClass(declaration)) {
|
||||
const currentAbstractions: string[] = declaration.extendedDefinitions.concat(
|
||||
declaration.implementedDefinitions,
|
||||
);
|
||||
|
||||
abstractions = abstractions.concat(currentAbstractions);
|
||||
abstractions = abstractions.concat(
|
||||
gatherTypeAssociations(definitions, currentAbstractions),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return abstractions;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Collects all reference information of this type.
|
||||
*
|
||||
* Reference information is everything that is indirectly referencing a type or class by name.
|
||||
*
|
||||
* @param type Type with references to other types
|
||||
*/
|
||||
function getReferenceTypes(type: LightweightType): string[] {
|
||||
const types: string[] = [];
|
||||
if (type.referenceName !== undefined) {
|
||||
types.push(type.referenceName);
|
||||
}
|
||||
|
||||
forEach(type.genericsTypes, specificType => {
|
||||
for (const value of getReferenceTypes(specificType)) {
|
||||
types.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
if ((isUnionOrIntersectionType(type) && isEmpty(type.specificationTypes)) || type.isArray) {
|
||||
forEach(type.specificationTypes, specificType => {
|
||||
for (const value of getReferenceTypes(specificType)) {
|
||||
types.push(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Plant UML code according to the config for the provided class
|
||||
*
|
||||
* @param config Configuration for how the UML should be tweaked
|
||||
* @param readerClass Class or interface representation
|
||||
*/
|
||||
function createPlantUMLCodeForClass(config: UMLConfig, readerClass: LightweightClassDefinition): string {
|
||||
// create the definition header, what type the definition is, it's name and it's inheritance
|
||||
let model = `${readerClass.modifiers} ${readerClass.name}`;
|
||||
|
||||
if (readerClass.typeParameters?.length ?? 0 > 0) {
|
||||
model += `<${readerClass.typeParameters!.join(', ')}>`;
|
||||
}
|
||||
|
||||
if (config.showInheritance && (readerClass.extendedDefinitions?.length ?? 0 > 0)) {
|
||||
// PlantUML will automatically create links, when using extends
|
||||
model += ` extends ${readerClass.extendedDefinitions!.join(', ')}`;
|
||||
}
|
||||
if (config.showInheritance && (readerClass.implementedDefinitions?.length ?? 0 > 0)) {
|
||||
// PlantUML will automatically create links, when using implements
|
||||
model += ` implements ${readerClass.implementedDefinitions!.join(', ')}`;
|
||||
}
|
||||
model += '{';
|
||||
|
||||
// add the properties to the definition body
|
||||
if (config.showProperties) {
|
||||
forEach(readerClass.properties, property => {
|
||||
if (property.optional && !config.showOptionalProperties) {
|
||||
// don't show optional attributes
|
||||
return;
|
||||
}
|
||||
/*if (property.inherited && !config.showInheritedProperties) {
|
||||
// don't show inherited properties
|
||||
continue;
|
||||
}*/
|
||||
model += `\n\t${createPropertyLine(property)}`;
|
||||
});
|
||||
}
|
||||
|
||||
// close the definition body
|
||||
model += '\n}\n';
|
||||
|
||||
// add associations from properties with references
|
||||
forEach(readerClass.properties, property => {
|
||||
const types: string[] = getReferenceTypes(property.type);
|
||||
for (const type of types) {
|
||||
if (config.showAssociations) {
|
||||
/*if (property.inherited && !config.showInheritedProperties) {
|
||||
continue;
|
||||
}*/
|
||||
model += `${readerClass.name} -up-> ${type} : ${property.name} >\n`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates PlantUML code according to the config for the provided enum/-like definition
|
||||
*
|
||||
* @param config Configuration for how the UML should be tweaked
|
||||
* @param readerEnum Enum/-like representation
|
||||
*/
|
||||
function createPlantUMLCodeForEnum(config: UMLConfig, readerEnum: LightweightAliasDefinition): string {
|
||||
// create enum header
|
||||
let model = `enum ${readerEnum.name} {`;
|
||||
// add values
|
||||
if (config.showEnumValues) {
|
||||
forEach(readerEnum.type?.specificationTypes, value => {
|
||||
model += `\n\t${value.toString()}`;
|
||||
});
|
||||
}
|
||||
model += '\n}\n';
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a property PlantUML Line
|
||||
*/
|
||||
function createPropertyLine(property: LightweightProperty): string {
|
||||
const prefix = `${/*(property.inherited ? '/ ' : */ ''}${property.optional ? '? ' : ''}`;
|
||||
|
||||
return `${prefix}${property.name} : ${expandTypeValue(property.type)}`;
|
||||
}
|
||||
59
packages/core-tools/src/uml/uml-config.ts
Normal file
59
packages/core-tools/src/uml/uml-config.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds configuration information of how the UML code should be build
|
||||
*/
|
||||
export interface UMLConfig {
|
||||
/**
|
||||
* Defines which definitions are shown
|
||||
*/
|
||||
definitions: string[];
|
||||
|
||||
/**
|
||||
* Defines the output file name without file extension
|
||||
*/
|
||||
outputFileName?: string;
|
||||
|
||||
/**
|
||||
* Should the associations between definitions be shown
|
||||
*/
|
||||
showAssociations: boolean;
|
||||
|
||||
/**
|
||||
* Should enum/-like values be shown
|
||||
*/
|
||||
showEnumValues: boolean;
|
||||
|
||||
/**
|
||||
* Should the inheritance be shown
|
||||
*/
|
||||
showInheritance: boolean;
|
||||
|
||||
/**
|
||||
* Should the inherited properties be shown
|
||||
*/
|
||||
showInheritedProperties: boolean;
|
||||
|
||||
/**
|
||||
* Should optional properties be shown
|
||||
*/
|
||||
showOptionalProperties: boolean;
|
||||
|
||||
/**
|
||||
* Should properties be shown
|
||||
*/
|
||||
showProperties: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user