Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core):added extract decorator method #754

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bb0ef5e
feat(core):added extract decorator method
Nov 6, 2023
809c932
feat(core):added extract decorator method
Nov 6, 2023
34be7eb
chore:Merged with main
Nov 6, 2023
a512353
fix: Added bugfix for no decorators
Nov 6, 2023
249fbf6
fix: Removed mutability and typos fixed
Nov 7, 2023
deb7331
fix: typo fix for jsdoc
Nov 7, 2023
8863f95
fix: call Model functions for getting version
Nov 7, 2023
c5438f3
feat: Extract decorators and vocab via visitor pattern
Nov 8, 2023
5ff26dd
feat: Extract decorators and vocab via visitor pattern
Nov 8, 2023
f0afdfd
feat: Extract decorators and vocab via visitor pattern
Nov 8, 2023
c85329c
Merge branch 'main' of https://github.com/muskanbararia/concerto into…
Nov 9, 2023
ae03cba
feat: Extract decorators and vocab for all types including maps and a…
Nov 9, 2023
55a2547
fix:minor linting fixes
Nov 9, 2023
a53454d
feat: Added new testcase and created a Util for decorators
Nov 10, 2023
398ba83
feat: Added new testcase and created a Util for decorators
Nov 10, 2023
29e390e
fix: broke down functions so they are more readable
Nov 10, 2023
5d7d4ef
fix: added relevant types
Nov 10, 2023
b0a286d
fix: removed two loops in parsing and better breakdown
Nov 13, 2023
26da05a
fix: better breakdown by adding transform functions
Nov 13, 2023
bb83859
fix: better breakdown by adding constructor functions
Nov 13, 2023
578cbfa
fix: created decorator extractor class
Nov 14, 2023
4cfefe8
fix: created decorator extractor class
Nov 14, 2023
079f3a5
chore: Merge branch 'main' of https://github.com/muskanbararia/concer…
Nov 14, 2023
d86d203
chore: added linting fix
Nov 14, 2023
b030c98
fix: added proper check to test for map type
Nov 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/concerto-core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class Concerto {
class DecoratorManager {
+ ModelManager validate(decoratorCommandSet,ModelFile[]) throws Error
+ ModelManager decorateModels(ModelManager,decoratorCommandSet,object?,boolean?,boolean?,boolean?)
+ ExtractDecoratorsResult extractDecorators(ModelManager,object,boolean,string)
+ void validateCommand(ModelManager,command)
+ Boolean falsyOrEqual(string||,string[])
+ void applyDecorator(decorated,string,newDecorator)
Expand Down
3 changes: 3 additions & 0 deletions packages/concerto-core/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
# Note that the latest public API is documented using JSDocs and is available in api.txt.
#

Version 3.14.2 {c8e20253a3695fcaed97472d697e282d} 2023-11-07
- Added DCS and vocabulary extraction support for decoratorManager

Version 3.13.1 {f5a9a1ea6a64865843a3abb77798cbb0} 2023-10-18
- Add migrate option to DecoratorManager options

Expand Down
104 changes: 101 additions & 3 deletions packages/concerto-core/lib/decoratormanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const Factory = require('./factory');
const ModelUtil = require('./modelutil');
const { MetaModelNamespace } = require('@accordproject/concerto-metamodel');
const semver = require('semver');
const DecoratorUtil = require('./decoratorutil');

// Types needed for TypeScript generation.
/* eslint-disable no-unused-vars */
Expand Down Expand Up @@ -147,7 +148,7 @@ class DecoratorManager {
metamodelValidation: true,
addMetamodel: true,
});
if(modelFiles) {
if (modelFiles) {
validationModelManager.addModelFiles(modelFiles);
}
validationModelManager.addCTOModel(
Expand Down Expand Up @@ -252,6 +253,103 @@ class DecoratorManager {
newModelManager.fromAst(decoratedAst);
return newModelManager;
}
/**
* @typedef ExtractDecoratorsResult
* @type {object}
* @property {ModelManager} modelManager - A model manager containing models stripped without decorators
* @property {decoratorCommandSet} object[] - Stripped out decorators, formed into decorator command sets
* @property {vocabularies} object[] - Stripped out vocabularies, formed into vocabulary files
*/
mttrbrts marked this conversation as resolved.
Show resolved Hide resolved
/**
* Extracts all the decorator commands from all the models in modelManager
* @param {ModelManager} modelManager the input model manager
* @param {object} options - decorator models options
* @param {boolean} options.removeDecoratorsFromModel - flag to strip out decorators from models
* @param {string} options.locale - locale for extracted vocabulary set
* @returns {ExtractDecoratorsResult} - a new model manager with the decorations removed and a list of extracted decorator jsons and vocab yamls
*/
static extractDecorators(modelManager,options) {
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
options = {
removeDecoratorsFromModel: false,
locale:'en',
...options
};
const decoratedAst = modelManager.getAst(true);
let extractionDictionary = {};
const processedModels = decoratedAst.models.map(model =>{
if ((model?.decorators.length > 0)){
extractionDictionary = DecoratorUtil.constructDCSDictionary(extractionDictionary, model.namespace, model.decorators, {});
if (options.removeDecoratorsFromModel){
model.decorators = undefined;
}
}
const processedDecl = model.declarations.map(decl => {
if (decl.decorators) {
const constructOptions = {
declaration: decl.name,
};
extractionDictionary = DecoratorUtil.constructDCSDictionary(extractionDictionary, model.namespace, decl.decorators, constructOptions);
}
if (options.removeDecoratorsFromModel){
decl.decorators = undefined;
}
if (decl.$class.endsWith('.MapDeclaration')) {
if (decl.key){
if (decl.key.decorators){
const constructOptions = {
declaration: decl.name,
mapElement: 'KEY'
};
extractionDictionary = DecoratorUtil.constructDCSDictionary(extractionDictionary, model.namespace, decl.key.decorators, constructOptions);
decl.key.decorators = undefined;
}
}
if (decl.value){
if (decl.value.decorators){
const constructOptions = {
declaration: decl.name,
mapElement: 'VALUE'
};
extractionDictionary = DecoratorUtil.constructDCSDictionary(extractionDictionary, model.namespace, decl.value.decorators, constructOptions);
decl.value.decorators = undefined;
}
}
}
if (decl.properties) {
const processedProperties = decl.properties.map(property => {
if (property.decorators){
const constructOptions = {
declaration: decl.name,
property: property.name
};
extractionDictionary = DecoratorUtil.constructDCSDictionary(extractionDictionary, model.namespace, property.decorators, constructOptions );
}
if (options.removeDecoratorsFromModel){
property.decorators = undefined;
}
return property;
});
decl.properties = processedProperties;
}
return decl;
});
model.declarations = processedDecl;
return model;
});
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
const processedAST={
...decoratedAst,
models:processedModels,
};
const newModelManager = new ModelManager();
newModelManager.fromAst(processedAST);
const decoratorCommandSet = DecoratorUtil.parseNonVocabularyDecorators(extractionDictionary, DCS_VERSION);
const vocabularies = DecoratorUtil.parseVocabularies(extractionDictionary, options.locale);
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
return {
modelManager: newModelManager,
decoratorCommandSet,
vocabularies
};
}

/**
* Throws an error if the decoractor command is invalid
Expand Down Expand Up @@ -421,7 +519,7 @@ class DecoratorManager {

if (declaration.$class === `${MetaModelNamespace}.MapDeclaration`) {
if (target.mapElement) {
switch(target.mapElement) {
switch (target.mapElement) {
case 'KEY':
case 'VALUE':
this.applyDecoratorForMapElement(target.mapElement, target, declaration, type, decorator);
Expand Down Expand Up @@ -476,4 +574,4 @@ class DecoratorManager {
}
}

module.exports = DecoratorManager;
module.exports = DecoratorManager;
197 changes: 197 additions & 0 deletions packages/concerto-core/lib/decoratorutil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const ModelUtil = require('./modelutil');
/**
* Internal Decorator Utility Class
* @private
* @class
* @memberof module:concerto-core
*/
class DecoratorUtil {
/**
* Parses the dict data into an array of decorator jsons
* @param {Object} decoratorDict the input dict
* @param {String} DCS_VERSION - the version string
* @returns {Array<Object>} the parsed decorator command set array
* @private
*/
static parseNonVocabularyDecorators(decoratorDict, DCS_VERSION){
const data = [];
Object.keys(decoratorDict).forEach(namespace =>{
const {name, version} = ModelUtil.parseNamespace(namespace);
const nameOfDcs = name;
const versionOfDcs = version;
const dcsObjects = [];
const jsonData = decoratorDict[namespace];
const patternToDetermineVocab = /^Term_/i;
jsonData.forEach(obj =>{
const decos = JSON.parse(obj.dcs);
const target = {
'$class': `org.accordproject.decoratorcommands@${DCS_VERSION}.CommandTarget`,
'namespace':namespace
};
if (obj.declaration && obj.declaration!==''){
target.declaration = obj.declaration;
}
if (obj.property && obj.property!==''){
target.property = obj.property;
}
if (obj.mapElement && obj.mapElement!==''){
target.mapElement = obj.mapElement;
}
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
decos.forEach(dcs =>{
if (dcs.name !== 'Term' && !patternToDetermineVocab.test(dcs.name)){
const decotatorObj = {
'$class': '[email protected]',
'name': dcs.name,
};
if (dcs.arguments){
const args = dcs.arguments.map((arg)=>{
return {
'$class':arg.$class,
'value':arg.value
};
});
decotatorObj.arguments = args;
}
let dcsObject = {
'$class': `org.accordproject.decoratorcommands@${DCS_VERSION}.Command`,
'type': 'UPSERT',
'target': target,
'decorator': decotatorObj,
};
dcsObjects.push(dcsObject);
}
});
});
const dcmsForNamespace = {
'$class': `org.accordproject.decoratorcommands@${DCS_VERSION}.DecoratorCommandSet`,
'name': nameOfDcs,
'version': versionOfDcs,
'commands': dcsObjects
};
data.push(dcmsForNamespace);
});
return data;
}
/**
* Parses the dict data into an array of decorator jsons
* @param {Object} decoratorDict the input dict
* @param {String} locale locale for target vocab
* @returns {Array<Object>} the parsed decorator command set array
* @private
*/
static parseVocabularies(decoratorDict,locale){
const data = [];
const patternToDetermineVocab = /^Term_/i;
Object.keys(decoratorDict).forEach((namespace)=>{
if (JSON.stringify(decoratorDict[namespace]).includes('Term') ||
JSON.stringify(decoratorDict[namespace]).includes('Term_')){
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
let strVoc = '';
strVoc = strVoc+`locale: ${locale}\n`;
strVoc = strVoc+`namespace: ${namespace}\n`;
strVoc = strVoc+'declarations:\n';
const jsonData = decoratorDict[namespace];
const dictVoc = {};
jsonData.forEach(obj =>{
if (!dictVoc[obj.declaration]){
dictVoc[obj.declaration] = {
propertyVocabs: {}
};
}
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
const decos = JSON.parse(obj.dcs);
decos.forEach(dcs =>{
if (dcs.name === 'Term' || patternToDetermineVocab.test(dcs.name)){
if (obj.property !== ''){
if (!dictVoc[obj.declaration].propertyVocabs[obj.property]){
dictVoc[obj.declaration].propertyVocabs[obj.property] = {};
}
if (dcs.name === 'Term'){
dictVoc[obj.declaration].propertyVocabs[obj.property].term = dcs.arguments[0].value;
}
else {
const extensionKey = dcs.name.split('Term_')[1];
dictVoc[obj.declaration].propertyVocabs[obj.property][extensionKey] = dcs.arguments[0].value;
}
}
else {
if (dcs.name === 'Term'){
dictVoc[obj.declaration].term = dcs.arguments[0].value;
}
else {
const extensionKey = dcs.name.split('Term_')[1];
dictVoc[obj.declaration][extensionKey] = dcs.arguments[0].value;
}
}
}
});

});
Object.keys(dictVoc).forEach(decl =>{
if (dictVoc[decl].term){
strVoc += ` - ${decl}: ${dictVoc[decl].term}\n`;
const otherProps = Object.keys(dictVoc[decl]).filter((str)=>str!=='term' && str!=='propertyVocabs');
otherProps.forEach(key =>{
strVoc += ` ${key}: ${dictVoc[decl][key]}\n`;
});
}
if (dictVoc[decl].propertyVocabs && Object.keys(dictVoc[decl].propertyVocabs).length>0){
if (!dictVoc[decl].term){
strVoc += ` - ${decl}: ${decl}\n`;
}
strVoc += ' properties:\n';
Object.keys(dictVoc[decl].propertyVocabs).forEach(prop =>{
strVoc += ` - ${prop}: ${dictVoc[decl].propertyVocabs[prop].term}\n`;
const otherProps = Object.keys(dictVoc[decl].propertyVocabs[prop]).filter((str)=>str!=='term');
otherProps.forEach(key =>{
strVoc += ` ${key}: ${dictVoc[decl].propertyVocabs[prop][key]}\n`;
});
});
}
});
data.push(strVoc);
}
});
return data;
}
/**
* Adds a key-value pair to a dictionary (object) if the key exists,
* or creates a new key with the provided value.
*
* @param {Object} dictionary - The dictionary (object) to which to add the key-value pair.
* @param {string} key - The key to add or update.
* @param {any} value - The value to add or update.
* @param {Object} options - The target declaration, property and mapElement.
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
* @returns {Object} - constructed DCS Dict
* @private
*/
static constructDCSDictionary(dictionary, key, value, options) {
const val = {
declaration:options.declaration || '',
property:options.property || '',
mapElement:options.mapElement || '',
muskanbararia marked this conversation as resolved.
Show resolved Hide resolved
dcs: JSON.stringify(value),
};
if (dictionary[key] && Array.isArray( dictionary[key])) {
dictionary[key].push(val);
} else {
dictionary[key] = [val];
}
return dictionary;
}
}
module.exports = DecoratorUtil;
Loading
Loading