diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 23be2927e..709811362 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -54,9 +54,13 @@ class Concerto { + string getNamespace(obj) } + object setCurrentTime() +class DecoratorExtractor { + + void constructor(boolean,string,string,Object) +} 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) diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index 66500462d..c65e01c24 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,6 +24,9 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 3.13.3 {b286dfdeeb654d25be7c5f9cc6305e38} 2023-11-07 +- Added DCS and vocabulary extraction support for decoratorManager + Version 3.13.2 {dccc690753912cf87e7ceec56d949058} 2023-10-18 - Add getNamespace method to key type and value type of maps diff --git a/packages/concerto-core/lib/decoratorextractor.js b/packages/concerto-core/lib/decoratorextractor.js new file mode 100644 index 000000000..8c15e0b48 --- /dev/null +++ b/packages/concerto-core/lib/decoratorextractor.js @@ -0,0 +1,390 @@ +/* + * 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 ModelManager = require('./modelmanager'); +const ModelUtil = require('./modelutil'); +const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); + +/** + * Utility functions to work with + * [DecoratorCommandSet](https://models.accordproject.org/concerto/decorators.cto) + * @memberof module:concerto-core + */ +class DecoratorExtractor { + /** + * Create the DecoratorExtractor. + * @constructor + * @param {boolean} removeDecoratorsFromModel - flag to determine whether to remove decorators from source model + * @param {string} locale - locale for extracted vocabularies + * @param {string} dcs_version - version string + * @param {Object} sourceModelAst - the ast of source models + */ + constructor(removeDecoratorsFromModel, locale, dcs_version, sourceModelAst) { + this.extractionDictionary = {}; + this.removeDecoratorsFromModel = removeDecoratorsFromModel; + this.locale = locale; + this.dcs_version = dcs_version; + this.sourceModelAst = sourceModelAst; + this.updatedModelAst = sourceModelAst; + } + /** + * Adds a key-value pair to a dictionary (object) if the key exists, + * or creates a new key with the provided value. + * + * @param {string} key - The key to add or update. + * @param {any} value - The value to add or update. + * @param {Object} options - options containing target + * @param {string} options.declaration - Target declaration + * @param {string} options.property - Target property + * @param {string} options.mapElement - Target map element + * @private + */ + constructDCSDictionary( key, value, options) { + const val = { + declaration:options?.declaration || '', + property:options?.property || '', + mapElement:options?.mapElement || '', + dcs: JSON.stringify(value), + }; + if (this.extractionDictionary[key] && Array.isArray(this.extractionDictionary[key])) { + this.extractionDictionary[key].push(val); + } else { + this.extractionDictionary[key] = [val]; + } + } + /** + * Transforms the collected decorators into proper decorator command sets + * @param {Array} dcsObjects - the collection of collected decorators + * @param {string} namespace - the current namespace + * @param {Array} decoratorData - the collection of existing decorator command sets + * @returns {Array} - the collection of decorator command sets + * @private + */ + transformNonVocabularyDecorators(dcsObjects, namespace, decoratorData){ + const {name, version} = ModelUtil.parseNamespace(namespace); + const nameOfDcs = name; + const versionOfDcs = version; + if (dcsObjects?.length > 0){ + const dcmsForNamespace = { + '$class': `org.accordproject.decoratorcommands@${this.dcs_version}.DecoratorCommandSet`, + 'name': nameOfDcs, + 'version': versionOfDcs, + 'commands': dcsObjects + }; + decoratorData.push(dcmsForNamespace); + } + return decoratorData; + } + /** + * Transforms the collected vocabularies into proper vocabulary command sets + * @param {Array} vocabObject - the collection of collected vocabularies + * @param {string} namespace - the current namespace + * @param {Array} vocabData - the collection of existing vocabularies command sets + * @returns {Array} - the collection of vocabularies command sets + * @private + */ + transformVocabularyDecorators(vocabObject, namespace, vocabData){ + if (Object.keys(vocabObject).length > 0 ){ + let strVoc = ''; + strVoc = strVoc + `locale: ${this.locale}\n`; + strVoc = strVoc + `namespace: ${namespace}\n`; + strVoc = strVoc + 'declarations:\n'; + Object.keys(vocabObject).forEach(decl =>{ + if (vocabObject[decl].term){ + strVoc += ` - ${decl}: ${vocabObject[decl].term}\n`; + const otherProps = Object.keys(vocabObject[decl]).filter((str)=>str !== 'term' && str !== 'propertyVocabs'); + otherProps.forEach(key =>{ + strVoc += ` ${key}: ${vocabObject[decl][key]}\n`; + }); + } + if (vocabObject[decl].propertyVocabs && Object.keys(vocabObject[decl].propertyVocabs).length > 0){ + if (!vocabObject[decl].term){ + strVoc += ` - ${decl}: ${decl}\n`; + } + strVoc += ' properties:\n'; + Object.keys(vocabObject[decl].propertyVocabs).forEach(prop =>{ + strVoc += ` - ${prop}: ${vocabObject[decl].propertyVocabs[prop].term}\n`; + const otherProps = Object.keys(vocabObject[decl].propertyVocabs[prop]).filter((str)=>str !== 'term'); + otherProps.forEach(key =>{ + strVoc += ` ${key}: ${vocabObject[decl].propertyVocabs[prop][key]}\n`; + }); + }); + } + }); + vocabData.push(strVoc); + } + return vocabData; + } + /** + * Constructs Target object for a given model + * @param {string} namespace - the current namespace + * @param {Object} obj - the ast of the model + * @returns {Object} - the target object + * @private + */ + constructTarget(namespace, obj){ + const target = { + '$class': `org.accordproject.decoratorcommands@${this.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; + } + return target; + } + + /** + * Parses the dict data into an array of decorator jsons + * @param {Array} dcsObjects - the array of collected dcs objects + * @param {Object} dcs - the current dcs json to be parsed + * @param {String} DCS_VERSION - the version string + * @param {Object} target - target object for the command + * @returns {Array} - the array of collected dcs objects with the current dcs + * @private + */ + parseNonVocabularyDecorators(dcsObjects, dcs, DCS_VERSION, target){ + const decotatorObj = { + '$class': 'concerto.metamodel@1.0.0.Decorator', + '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); + return dcsObjects; + } + /** + * @param {Object} dictVoc - the collection of collected vocabularies + * @param {Object} decl - the declaration object + * @param {Object} dcs - the current dcs json to be parsed + * @returns {Object} - the collection of collected vocabularies with current dcs + * @private + */ + parseVocabularies(dictVoc, decl, dcs){ + dictVoc[decl.declaration] = dictVoc[decl.declaration] || { propertyVocabs: {} }; + if (decl.property !== ''){ + if (!dictVoc[decl.declaration].propertyVocabs[decl.property]){ + dictVoc[decl.declaration].propertyVocabs[decl.property] = {}; + } + if (dcs.name === 'Term'){ + dictVoc[decl.declaration].propertyVocabs[decl.property].term = dcs.arguments[0].value; + } + else { + const extensionKey = dcs.name.split('Term_')[1]; + dictVoc[decl.declaration].propertyVocabs[decl.property][extensionKey] = dcs.arguments[0].value; + } + } + else { + if (dcs.name === 'Term'){ + dictVoc[decl.declaration].term = dcs.arguments[0].value; + } + else { + const extensionKey = dcs.name.split('Term_')[1]; + dictVoc[decl.declaration][extensionKey] = dcs.arguments[0].value; + } + } + return dictVoc; + } + /** + * parses the extracted decorators and generates arrays of decorator command set and vocabularies + * + * @returns {Object} - constructed DCS Dict and processed models ast + * @private + */ + transformDecoratorsAndVocabularies(){ + let decoratorData = []; + let vocabData = []; + Object.keys(this.extractionDictionary).forEach(namespace => { + const jsonData = this.extractionDictionary[namespace]; + const patternToDetermineVocab = /^Term_/i; + let dcsObjects = []; + let vocabObject = {}; + jsonData.forEach(obj =>{ + const decos = JSON.parse(obj.dcs); + const target = this.constructTarget(namespace, obj); + decos.forEach(dcs =>{ + if (dcs.name !== 'Term' && !patternToDetermineVocab.test(dcs.name)){ + dcsObjects = this.parseNonVocabularyDecorators(dcsObjects, dcs, this.dcs_version, target); + } + else { + vocabObject = this.parseVocabularies(vocabObject, obj, dcs); + } + }); + }); + decoratorData = this.transformNonVocabularyDecorators(dcsObjects, namespace, decoratorData); + vocabData = this.transformVocabularyDecorators(vocabObject, namespace, vocabData); + }); + return { + decoratorCommandSet: decoratorData, + vocabularies: vocabData + }; + } + + /** + * Process the map declarations to extract the decorators. + * + * @param {Object} declaration - The source AST of the model + * @param {string} namespace - namespace of the model + * @returns {Object} - processed map declarations ast + * @private + */ + processMapDeclaration(declaration, namespace){ + if (declaration.key){ + if (declaration.key.decorators){ + const constructOptions = { + declaration: declaration.name, + mapElement: 'KEY' + }; + this.constructDCSDictionary(namespace, declaration.key.decorators, constructOptions); + if (this.removeDecoratorsFromModel){ + declaration.key.decorators = undefined; + } + } + } + if (declaration.value){ + if (declaration.value.decorators){ + const constructOptions = { + declaration: declaration.name, + mapElement: 'VALUE' + }; + this.constructDCSDictionary(namespace, declaration.value.decorators, constructOptions); + if (this.removeDecoratorsFromModel){ + declaration.value.decorators = undefined; + } + } + } + return declaration; + } + + /** + * Process the properties to extract the decorators. + * + * @param {Object} sourceProperties - The source AST of the property + * @param {string} declarationName - The name of source declaration + * @param {string} namespace - namespace of the model + * @returns {Object} - processed properties ast + * @private + */ + processProperties(sourceProperties, declarationName, namespace){ + const processedProperties = sourceProperties.map(property => { + if (property.decorators){ + const constructOptions = { + declaration: declarationName, + property: property.name + }; + this.constructDCSDictionary(namespace, property.decorators, constructOptions ); + } + if (this.removeDecoratorsFromModel){ + property.decorators = undefined; + } + return property; + }); + return processedProperties; + } + + /** + * Process the declarations to extract the decorators. + * + * @param {Object} sourceDecl - The source AST of the model + * @param {string} namespace - namespace of the model + * @returns {Object} - processed declarations ast + * @private + */ + processDeclarations(sourceDecl, namespace){ + const processedDecl = sourceDecl.map(decl => { + if (decl.decorators) { + const constructOptions = { + declaration: decl.name, + }; + this.constructDCSDictionary(namespace, decl.decorators, constructOptions); + } + if (this.removeDecoratorsFromModel){ + decl.decorators = undefined; + } + if (decl.$class === `${MetaModelNamespace}.MapDeclaration`) { + const processedMapDecl = this.processMapDeclaration(decl, namespace); + decl = processedMapDecl; + } + if (decl.properties) { + const processedProperties = this.processProperties(decl.properties, decl.name, namespace); + decl.properties = processedProperties; + } + return decl; + }); + return processedDecl; + } + + /** + * Process the models to extract the decorators. + * + * @private + */ + processModels(){ + const processedModels = this.sourceModelAst.models.map(model =>{ + if ((model?.decorators.length > 0)){ + this.constructDCSDictionary(model.namespace, model.decorators, {}); + if (this.removeDecoratorsFromModel){ + model.decorators = undefined; + } + } + const processedDecl = this.processDeclarations(model.declarations, model.namespace); + model.declarations = processedDecl; + return model; + }); + this.updatedModelAst = { + ...this.updatedModelAst, + models: processedModels + }; + } + + /** + * Collects the decorators and vocabularies and updates the modelManager depending + * on the options. + * + * @returns {Object} - constructed DCS Dict and processed models ast + * @private + */ + extract() { + this.processModels(); + const updatedModelManager = new ModelManager(); + updatedModelManager.fromAst(this.updatedModelAst); + const extractedDecosAndVocabs = this.transformDecoratorsAndVocabularies(); + return { + updatedModelManager, + decoratorCommandSet: extractedDecosAndVocabs.decoratorCommandSet, + vocabularies: extractedDecosAndVocabs.vocabularies + }; + } +} +module.exports = DecoratorExtractor; diff --git a/packages/concerto-core/lib/decoratormanager.js b/packages/concerto-core/lib/decoratormanager.js index 63a045934..fc7860093 100644 --- a/packages/concerto-core/lib/decoratormanager.js +++ b/packages/concerto-core/lib/decoratormanager.js @@ -20,6 +20,7 @@ const Factory = require('./factory'); const ModelUtil = require('./modelutil'); const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const semver = require('semver'); +const DecoratorExtractor = require('./decoratorextractor'); // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -147,7 +148,7 @@ class DecoratorManager { metamodelValidation: true, addMetamodel: true, }); - if(modelFiles) { + if (modelFiles) { validationModelManager.addModelFiles(modelFiles); } validationModelManager.addCTOModel( @@ -252,6 +253,40 @@ class DecoratorManager { newModelManager.fromAst(decoratedAst); return newModelManager; } + /** + * @typedef decoratorCommandSet + * @type {object} + * @typedef vocabularies + * @type {string} + * @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 + */ + /** + * 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) { + options = { + removeDecoratorsFromModel: false, + locale:'en', + ...options + }; + const sourceAst = modelManager.getAst(true); + const decoratorExtrator = new DecoratorExtractor(options.removeDecoratorsFromModel, options.locale, DCS_VERSION, sourceAst); + const collectionResp = decoratorExtrator.extract(); + return { + modelManager: collectionResp.updatedModelManager, + decoratorCommandSet: collectionResp.decoratorCommandSet, + vocabularies: collectionResp.vocabularies + }; + } /** * Throws an error if the decoractor command is invalid @@ -421,7 +456,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); diff --git a/packages/concerto-core/test/data/decoratorcommands/extract-test.cto b/packages/concerto-core/test/data/decoratorcommands/extract-test.cto new file mode 100644 index 000000000..a8797eb67 --- /dev/null +++ b/packages/concerto-core/test/data/decoratorcommands/extract-test.cto @@ -0,0 +1,46 @@ +namespace test@1.0.0 +@deco +enum Dummy { + @one + o One +} +@scc +scalar SSN extends String default="000-00-0000" + +@M1 +participant participantName identified by participantKey { + @M3 + o String participantKey +} + +@Dummy("term1",2) +asset assetName identified by assetKey { + o String assetKey +} + +map mapName { + @deco(1) + o String + o String +} + +@Term("Person Class") +@Editable +concept Person { + @Term("HI") + @Custom + @Form("inputType", "text") + @New + o String firstName + @Form("inputType", "text") + @New + o String lastName + @Term("some") + @Term_cus("con") + @Form("inputType", "textArea") + @New + o String bio + @Form("inputType", "text") + @New + o String ssn +} \ No newline at end of file diff --git a/packages/concerto-core/test/data/decoratorcommands/model-without-vocab.cto b/packages/concerto-core/test/data/decoratorcommands/model-without-vocab.cto new file mode 100644 index 000000000..1d3e80547 --- /dev/null +++ b/packages/concerto-core/test/data/decoratorcommands/model-without-vocab.cto @@ -0,0 +1,13 @@ +namespace test@1.0.0 + +@Editable +concept Person { + @Custom + o String firstName + o String lastName + o String bio + o String address1 + o String address2 + o String city + o String country +} \ No newline at end of file diff --git a/packages/concerto-core/test/data/decoratorcommands/test-decorated-model.cto b/packages/concerto-core/test/data/decoratorcommands/test-decorated-model.cto new file mode 100644 index 000000000..3618e1829 --- /dev/null +++ b/packages/concerto-core/test/data/decoratorcommands/test-decorated-model.cto @@ -0,0 +1,57 @@ +namespace test@1.0.0 + +@Editable +concept Person { + @Custom + @Form("inputType", "text") + @New + o String firstName + @Form("inputType", "text") + @New + o String lastName + @Form("inputType", "textArea") + @New + o String bio + @Form("inputType", "text") + @New + o String ssn +} + +@Editable +concept PersonMain { + @Custom + @Form("inputType", "text") + @New + o String firstName + @Form("inputType", "text") + @New + o String lastName + @Form("inputType", "textArea") + @New + o String bio + @Form("inputType", 0) + @New + o String ssn +} + +@Term("A driver of a vehicle") +concept Driver extends Person { + @Term("favourite colour") + @Term_description("some color") + o String favoriteColor +} + +@Term("Employee") +concept Employee { + @Term("Ssn of the Employee") + o String ssn +} + +@Term("Car") +concept Car identified by vin { + @Term("Vin of the Car") + o String vin + @Term("Owner of the Car") + @Hide("object") + o Person owner +} \ No newline at end of file diff --git a/packages/concerto-core/test/data/decoratorcommands/test-decorator-without-version.cto b/packages/concerto-core/test/data/decoratorcommands/test-decorator-without-version.cto new file mode 100644 index 000000000..0819b64e9 --- /dev/null +++ b/packages/concerto-core/test/data/decoratorcommands/test-decorator-without-version.cto @@ -0,0 +1,49 @@ +namespace test + +@Editable +concept Person { + @Custom + @Form("inputType", "text") + @New + o String firstName + @Form("inputType", "text") + @New + o String lastName + @Form("inputType", "textArea") + @New + o String bio + @Form("inputType", "text") + @New + o String ssn +} + +@Editable +concept PersonMain { + @Custom + @Form("inputType", "text") + @New + o String firstName + @Form("inputType", "text") + @New + o String lastName + @Form("inputType", "textArea") + @New + o String bio + @Form("inputType", 0) + @New + o String ssn +} + +concept Driver extends Person { + o String favoriteColor +} + +concept Employee { + o String ssn +} + +concept Car identified by vin { + o String vin + @Hide("object") + o Person owner +} \ No newline at end of file diff --git a/packages/concerto-core/test/decoratormanager.js b/packages/concerto-core/test/decoratormanager.js index 6ef3fa6a7..1cf115e1e 100644 --- a/packages/concerto-core/test/decoratormanager.js +++ b/packages/concerto-core/test/decoratormanager.js @@ -17,6 +17,8 @@ const fs = require('fs'); const DecoratorManager = require('../lib/decoratormanager'); const ModelManager = require('../lib/modelmanager'); +const VocabularyManager= require('../../concerto-vocabulary/lib/vocabularymanager'); +const Printer= require('../../concerto-cto/lib/printer'); const chai = require('chai'); require('chai').should(); @@ -486,4 +488,82 @@ describe('DecoratorManager', () => { }).should.throw(/Model violation in the "concerto.metamodel@1.0.0.Decorator" instance. Invalid enum value of "INVALID" for the field "CommandType"/); }); }); -}); + + describe('#extractDecorators', function() { + it('should be able to extract decorators and vocabs from a model withoup options', async function() { + const testModelManager = new ModelManager({strict:true,}); + const modelText = fs.readFileSync('./test/data/decoratorcommands/extract-test.cto', 'utf-8'); + testModelManager.addCTOModel(modelText, 'test.cto'); + const resp = DecoratorManager.extractDecorators( testModelManager); + const dcs = resp.decoratorCommandSet; + dcs.should.not.be.null; + }); + it('should be able to extract decorators and vocabs from a model without namespace version', async function() { + const testModelManager = new ModelManager(); + const modelTextWithoutNamespace = fs.readFileSync('./test/data/decoratorcommands/test-decorator-without-version.cto', 'utf-8'); + testModelManager.addCTOModel(modelTextWithoutNamespace, 'test.cto'); + const options = { + removeDecoratorsFromModel:true, + locale:'en' + }; + const resp = DecoratorManager.extractDecorators( testModelManager, options); + const vocabs = resp.vocabularies; + vocabs.should.not.be.null; + }); + it('should ensure that extraction and re-application of decorators and vocabs from a model is an identity operation', async function() { + const testModelManager = new ModelManager(); + const sourceCTO = []; + const updatedCTO = []; + const modelTextWithoutNamespace = fs.readFileSync('./test/data/decoratorcommands/extract-test.cto', 'utf-8'); + testModelManager.addCTOModel(modelTextWithoutNamespace, 'test.cto'); + const options = { + removeDecoratorsFromModel:true, + locale:'en' + }; + const namespaceSource = testModelManager.getNamespaces().filter(namespace=>namespace!=='concerto@1.0.0' && namespace!=='concerto'); + namespaceSource.forEach(name=>{ + let model = testModelManager.getModelFile(name); + let modelAst=model.getAst(); + let data = Printer.toCTO(modelAst); + sourceCTO.push(data); + }); + const resp = DecoratorManager.extractDecorators( testModelManager, options); + const dcs = resp.decoratorCommandSet; + const vocabs= resp.vocabularies; + let newModelManager=resp.modelManager; + const vocManager = new VocabularyManager(); + vocabs.forEach(content => { + vocManager.addVocabulary(content); + }); + const vocabKeySet=[]; + const namespaceUpdated = newModelManager.getNamespaces().filter(namespace=>namespace!=='concerto@1.0.0' && namespace!=='concerto'); + namespaceUpdated.forEach(name=>{ + let vocab = vocManager.getVocabulariesForNamespace(name); + vocab.forEach(voc=>vocabKeySet.push(voc.getLocale())); + }); + vocabKeySet.map(voc=>{ + let commandSet = vocManager.generateDecoratorCommands(newModelManager, voc); + newModelManager = DecoratorManager.decorateModels(newModelManager, commandSet); + }); + dcs.forEach(content => { + newModelManager = DecoratorManager.decorateModels(newModelManager, (content)); + }); + namespaceUpdated.forEach(name=>{ + let model = newModelManager.getModelFile(name); + let modelAst=model.getAst(); + let data = Printer.toCTO(modelAst); + updatedCTO.push(data); + }); + sourceCTO.should.be.deep.equal(updatedCTO); + }); + it('should give proper response in there is no vocabulary on any model', async function() { + const testModelManager = new ModelManager({strict:true,}); + const modelText = fs.readFileSync('./test/data/decoratorcommands/model-without-vocab.cto', 'utf-8'); + testModelManager.addCTOModel(modelText, 'test.cto'); + const resp = DecoratorManager.extractDecorators( testModelManager); + const vocab = resp.vocabularies; + vocab.should.be.deep.equal([]); + }); + }); + +}); \ No newline at end of file diff --git a/packages/concerto-core/types/lib/decoratorextractor.d.ts b/packages/concerto-core/types/lib/decoratorextractor.d.ts new file mode 100644 index 000000000..432b3b642 --- /dev/null +++ b/packages/concerto-core/types/lib/decoratorextractor.d.ts @@ -0,0 +1,129 @@ +export = DecoratorExtractor; +/** + * Utility functions to work with + * [DecoratorCommandSet](https://models.accordproject.org/concerto/decorators.cto) + * @memberof module:concerto-core + */ +declare class DecoratorExtractor { + /** + * Create the DecoratorExtractor. + * @constructor + * @param {boolean} removeDecoratorsFromModel - flag to determine whether to remove decorators from source model + * @param {string} locale - locale for extracted vocabularies + * @param {string} dcs_version - version string + * @param {Object} sourceModelAst - the ast of source models + */ + constructor(removeDecoratorsFromModel: boolean, locale: string, dcs_version: string, sourceModelAst: any); + extractionDictionary: {}; + removeDecoratorsFromModel: boolean; + locale: string; + dcs_version: string; + sourceModelAst: any; + updatedModelAst: any; + /** + * Adds a key-value pair to a dictionary (object) if the key exists, + * or creates a new key with the provided value. + * + * @param {string} key - The key to add or update. + * @param {any} value - The value to add or update. + * @param {Object} options - options containing target + * @param {string} options.declaration - Target declaration + * @param {string} options.property - Target property + * @param {string} options.mapElement - Target map element + * @private + */ + private constructDCSDictionary; + /** + * Transforms the collected decorators into proper decorator command sets + * @param {Array} dcsObjects - the collection of collected decorators + * @param {string} namespace - the current namespace + * @param {Array} decoratorData - the collection of existing decorator command sets + * @returns {Array} - the collection of decorator command sets + * @private + */ + private transformNonVocabularyDecorators; + /** + * Transforms the collected vocabularies into proper vocabulary command sets + * @param {Array} vocabObject - the collection of collected vocabularies + * @param {string} namespace - the current namespace + * @param {Array} vocabData - the collection of existing vocabularies command sets + * @returns {Array} - the collection of vocabularies command sets + * @private + */ + private transformVocabularyDecorators; + /** + * Constructs Target object for a given model + * @param {string} namespace - the current namespace + * @param {Object} obj - the ast of the model + * @returns {Object} - the target object + * @private + */ + private constructTarget; + /** + * Parses the dict data into an array of decorator jsons + * @param {Array} dcsObjects - the array of collected dcs objects + * @param {Object} dcs - the current dcs json to be parsed + * @param {String} DCS_VERSION - the version string + * @param {Object} target - target object for the command + * @returns {Array} - the array of collected dcs objects with the current dcs + * @private + */ + private parseNonVocabularyDecorators; + /** + * @param {Object} dictVoc - the collection of collected vocabularies + * @param {Object} decl - the declaration object + * @param {Object} dcs - the current dcs json to be parsed + * @returns {Object} - the collection of collected vocabularies with current dcs + * @private + */ + private parseVocabularies; + /** + * parses the extracted decorators and generates arrays of decorator command set and vocabularies + * + * @returns {Object} - constructed DCS Dict and processed models ast + * @private + */ + private transformDecoratorsAndVocabularies; + /** + * Process the map declarations to extract the decorators. + * + * @param {Object} declaration - The source AST of the model + * @param {string} namespace - namespace of the model + * @returns {Object} - processed map declarations ast + * @private + */ + private processMapDeclaration; + /** + * Process the properties to extract the decorators. + * + * @param {Object} sourceProperties - The source AST of the property + * @param {string} declarationName - The name of source declaration + * @param {string} namespace - namespace of the model + * @returns {Object} - processed properties ast + * @private + */ + private processProperties; + /** + * Process the declarations to extract the decorators. + * + * @param {Object} sourceDecl - The source AST of the model + * @param {string} namespace - namespace of the model + * @returns {Object} - processed declarations ast + * @private + */ + private processDeclarations; + /** + * Process the models to extract the decorators. + * + * @private + */ + private processModels; + /** + * Collects the decorators and vocabularies and updates the modelManager depending + * on the options. + * + * @returns {Object} - constructed DCS Dict and processed models ast + * @private + */ + private extract; +} diff --git a/packages/concerto-core/types/lib/decoratormanager.d.ts b/packages/concerto-core/types/lib/decoratormanager.d.ts index 4fa7ce2f1..3bab84250 100644 --- a/packages/concerto-core/types/lib/decoratormanager.d.ts +++ b/packages/concerto-core/types/lib/decoratormanager.d.ts @@ -54,6 +54,38 @@ declare class DecoratorManager { validateCommands?: boolean; migrate?: boolean; }): ModelManager; + /** + * @typedef decoratorCommandSet + * @type {object} + * @typedef vocabularies + * @type {string} + * @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 + */ + /** + * 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: ModelManager, options: { + removeDecoratorsFromModel: boolean; + locale: string; + }): { + /** + * - A model manager containing models stripped without decorators + */ + modelManager: ModelManager; + /** + * - Stripped out decorators, formed into decorator command sets + */ + object: {}; + }; /** * Throws an error if the decoractor command is invalid * @param {ModelManager} validationModelManager the validation model manager