diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index 0ffc33830..fddb420ef 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -55,13 +55,12 @@ 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?,boolean?) + ExtractDecoratorsResult extractDecorators(ModelManager,object,boolean,string) + + ExtractDecoratorsResult extractVocabularies(ModelManager,object,boolean,string) + + ExtractDecoratorsResult extractNonVocabDecorators(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 3f9ccdcc9..076001bd4 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,6 +24,11 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 3.17.3 {1c15de121dd80375cf531bc2244efdd0} 2024-07-26 +- Added new methods to extract vocab or non-vocab decorators from model +- Fixed some minor bugs related to vocabulary parsing +- Exposed method to validate locale + Version 3.17.2 {eb3903401fdcf7c26cca1f3f8a029171} 2024-07-17 - Added new optimized methods for decorating models with decorator commandsets - fixed other decorator command set bugs diff --git a/packages/concerto-core/lib/decoratorextractor.js b/packages/concerto-core/lib/decoratorextractor.js index 8c15e0b48..28aeeb4fb 100644 --- a/packages/concerto-core/lib/decoratorextractor.js +++ b/packages/concerto-core/lib/decoratorextractor.js @@ -22,8 +22,18 @@ const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); * Utility functions to work with * [DecoratorCommandSet](https://models.accordproject.org/concerto/decorators.cto) * @memberof module:concerto-core + * @private */ class DecoratorExtractor { + /** + * The action to be performed to extract all, only vocab or only non-vocab decorators + */ + static Action = { + EXTRACT_ALL: 0, + EXTRACT_VOCAB: 1, + EXTRACT_NON_VOCAB: 2 + }; + /** * Create the DecoratorExtractor. * @constructor @@ -31,14 +41,26 @@ class DecoratorExtractor { * @param {string} locale - locale for extracted vocabularies * @param {string} dcs_version - version string * @param {Object} sourceModelAst - the ast of source models + * @param {int} [action=DecoratorExtractor.Action.EXTRACT_ALL] - the action to be performed */ - constructor(removeDecoratorsFromModel, locale, dcs_version, sourceModelAst) { + constructor(removeDecoratorsFromModel, locale, dcs_version, sourceModelAst, action = DecoratorExtractor.Action.EXTRACT_ALL) { this.extractionDictionary = {}; this.removeDecoratorsFromModel = removeDecoratorsFromModel; this.locale = locale; this.dcs_version = dcs_version; this.sourceModelAst = sourceModelAst; this.updatedModelAst = sourceModelAst; + this.action = Object.values(DecoratorExtractor.Action).includes(action)? action : DecoratorExtractor.Action.EXTRACT_ALL; + } + + /** + * Returns if the decorator is vocab or not + * @param {string} decoractorName - the name of decorator + * @returns {boolean} - returns true if the decorator is a vocabulary decorator else false + * @private + */ + isVocabDecorator(decoractorName) { + return decoractorName === 'Term' || decoractorName.startsWith('Term_'); } /** * Adds a key-value pair to a dictionary (object) if the key exists, @@ -105,18 +127,24 @@ class DecoratorExtractor { 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'); + } + const otherProps = Object.keys(vocabObject[decl]).filter((str)=>str !== 'term' && str !== 'propertyVocabs'); + //If a declaration does not have any Term decorator, then add Term_ decorators to yaml + if(otherProps.length > 0){ + if (!vocabObject[decl].term){ + strVoc += ` - ${decl}: ${decl}\n`; + } otherProps.forEach(key =>{ strVoc += ` ${key}: ${vocabObject[decl][key]}\n`; }); } if (vocabObject[decl].propertyVocabs && Object.keys(vocabObject[decl].propertyVocabs).length > 0){ - if (!vocabObject[decl].term){ + if (!vocabObject[decl].term && otherProps.length === 0){ strVoc += ` - ${decl}: ${decl}\n`; } strVoc += ' properties:\n'; Object.keys(vocabObject[decl].propertyVocabs).forEach(prop =>{ - strVoc += ` - ${prop}: ${vocabObject[decl].propertyVocabs[prop].term}\n`; + 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`; @@ -205,6 +233,18 @@ class DecoratorExtractor { dictVoc[decl.declaration].propertyVocabs[decl.property][extensionKey] = dcs.arguments[0].value; } } + else if (decl.mapElement !== ''){ + if (!dictVoc[decl.declaration].propertyVocabs[decl.mapElement]){ + dictVoc[decl.declaration].propertyVocabs[decl.mapElement] = {}; + } + if (dcs.name === 'Term'){ + dictVoc[decl.declaration].propertyVocabs[decl.mapElement].term = dcs.arguments[0].value; + } + else { + const extensionKey = dcs.name.split('Term_')[1]; + dictVoc[decl.declaration].propertyVocabs[decl.mapElement][extensionKey] = dcs.arguments[0].value; + } + } else { if (dcs.name === 'Term'){ dictVoc[decl.declaration].term = dcs.arguments[0].value; @@ -227,30 +267,54 @@ class DecoratorExtractor { 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)){ + const isVocab = this.isVocabDecorator(dcs.name); + if (!isVocab && this.action !== DecoratorExtractor.Action.EXTRACT_VOCAB){ dcsObjects = this.parseNonVocabularyDecorators(dcsObjects, dcs, this.dcs_version, target); } - else { + if (isVocab && this.action !== DecoratorExtractor.Action.EXTRACT_NON_VOCAB){ vocabObject = this.parseVocabularies(vocabObject, obj, dcs); } }); }); - decoratorData = this.transformNonVocabularyDecorators(dcsObjects, namespace, decoratorData); - vocabData = this.transformVocabularyDecorators(vocabObject, namespace, vocabData); + if(this.action !== DecoratorExtractor.Action.EXTRACT_VOCAB){ + decoratorData = this.transformNonVocabularyDecorators(dcsObjects, namespace, decoratorData); + } + if(this.action !== DecoratorExtractor.Action.EXTRACT_NON_VOCAB){ + vocabData = this.transformVocabularyDecorators(vocabObject, namespace, vocabData); + } }); return { decoratorCommandSet: decoratorData, vocabularies: vocabData }; } + /** + * Filter vocab or non-vocab decorators + * @param {Object} decorators - the collection of decorators + * @returns {Object} - the collection of filtered decorators + * @private + */ + filterOutDecorators(decorators){ + if(!this.removeDecoratorsFromModel){ + return decorators; + } + if (this.action === DecoratorExtractor.Action.EXTRACT_ALL){ + return undefined; + } + else if(this.action === DecoratorExtractor.Action.EXTRACT_VOCAB){ + return decorators.filter((dcs) => !this.isVocabDecorator(dcs.name)); + } + else{ + return decorators.filter((dcs) => this.isVocabDecorator(dcs.name)); + } + } /** * Process the map declarations to extract the decorators. * @@ -267,9 +331,7 @@ class DecoratorExtractor { mapElement: 'KEY' }; this.constructDCSDictionary(namespace, declaration.key.decorators, constructOptions); - if (this.removeDecoratorsFromModel){ - declaration.key.decorators = undefined; - } + declaration.key.decorators = this.filterOutDecorators(declaration.key.decorators); } } if (declaration.value){ @@ -279,9 +341,7 @@ class DecoratorExtractor { mapElement: 'VALUE' }; this.constructDCSDictionary(namespace, declaration.value.decorators, constructOptions); - if (this.removeDecoratorsFromModel){ - declaration.value.decorators = undefined; - } + declaration.value.decorators = this.filterOutDecorators(declaration.value.decorators); } } return declaration; @@ -304,9 +364,7 @@ class DecoratorExtractor { property: property.name }; this.constructDCSDictionary(namespace, property.decorators, constructOptions ); - } - if (this.removeDecoratorsFromModel){ - property.decorators = undefined; + property.decorators = this.filterOutDecorators(property.decorators); } return property; }); @@ -328,9 +386,7 @@ class DecoratorExtractor { declaration: decl.name, }; this.constructDCSDictionary(namespace, decl.decorators, constructOptions); - } - if (this.removeDecoratorsFromModel){ - decl.decorators = undefined; + decl.decorators = this.filterOutDecorators(decl.decorators); } if (decl.$class === `${MetaModelNamespace}.MapDeclaration`) { const processedMapDecl = this.processMapDeclaration(decl, namespace); @@ -352,11 +408,9 @@ class DecoratorExtractor { */ processModels(){ const processedModels = this.sourceModelAst.models.map(model =>{ - if ((model?.decorators.length > 0)){ + if ((model?.decorators?.length > 0)){ this.constructDCSDictionary(model.namespace, model.decorators, {}); - if (this.removeDecoratorsFromModel){ - model.decorators = undefined; - } + model.decorators = this.filterOutDecorators(model.decorators); } const processedDecl = this.processDeclarations(model.declarations, model.namespace); model.declarations = processedDecl; diff --git a/packages/concerto-core/lib/decoratormanager.js b/packages/concerto-core/lib/decoratormanager.js index b866a6238..6da75939c 100644 --- a/packages/concerto-core/lib/decoratormanager.js +++ b/packages/concerto-core/lib/decoratormanager.js @@ -424,15 +424,11 @@ class DecoratorManager { 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 + * @property {*} decoratorCommandSet - Stripped out decorators, formed into decorator command sets + * @property {string[]} vocabularies - Stripped out vocabularies, formed into vocabulary files */ /** * Extracts all the decorator commands from all the models in modelManager @@ -449,7 +445,7 @@ class DecoratorManager { ...options }; const sourceAst = modelManager.getAst(true); - const decoratorExtrator = new DecoratorExtractor(options.removeDecoratorsFromModel, options.locale, DCS_VERSION, sourceAst); + const decoratorExtrator = new DecoratorExtractor(options.removeDecoratorsFromModel, options.locale, DCS_VERSION, sourceAst, DecoratorExtractor.Action.EXTRACT_ALL); const collectionResp = decoratorExtrator.extract(); return { modelManager: collectionResp.updatedModelManager, @@ -457,7 +453,50 @@ class DecoratorManager { vocabularies: collectionResp.vocabularies }; } - + /** + * Extracts all the vocab 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 vocab decorators from models + * @param {string} options.locale - locale for extracted vocabulary set + * @returns {ExtractDecoratorsResult} - a new model manager with/without the decorators and vocab yamls + */ + static extractVocabularies(modelManager,options) { + options = { + removeDecoratorsFromModel: false, + locale:'en', + ...options + }; + const sourceAst = modelManager.getAst(true); + const decoratorExtrator = new DecoratorExtractor(options.removeDecoratorsFromModel, options.locale, DCS_VERSION, sourceAst, DecoratorExtractor.Action.EXTRACT_VOCAB); + const collectionResp = decoratorExtrator.extract(); + return { + modelManager: collectionResp.updatedModelManager, + vocabularies: collectionResp.vocabularies + }; + } + /** + * Extracts all the non-vocab 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 non-vocab decorators from models + * @param {string} options.locale - locale for extracted vocabulary set + * @returns {ExtractDecoratorsResult} - a new model manager with/without the decorators and a list of extracted decorator jsons + */ + static extractNonVocabDecorators(modelManager,options) { + options = { + removeDecoratorsFromModel: false, + locale:'en', + ...options + }; + const sourceAst = modelManager.getAst(true); + const decoratorExtrator = new DecoratorExtractor(options.removeDecoratorsFromModel, options.locale, DCS_VERSION, sourceAst, DecoratorExtractor.Action.EXTRACT_NON_VOCAB); + const collectionResp = decoratorExtrator.extract(); + return { + modelManager: collectionResp.updatedModelManager, + decoratorCommandSet: collectionResp.decoratorCommandSet + }; + } /** * Throws an error if the decoractor command is invalid * @param {ModelManager} validationModelManager the validation model manager diff --git a/packages/concerto-core/test/data/decoratorcommands/extract-test-dcs.json b/packages/concerto-core/test/data/decoratorcommands/extract-test-dcs.json new file mode 100644 index 000000000..8aebf9688 --- /dev/null +++ b/packages/concerto-core/test/data/decoratorcommands/extract-test-dcs.json @@ -0,0 +1,338 @@ +[ + { + "$class": "org.accordproject.decoratorcommands@0.3.0.DecoratorCommandSet", + "name": "test", + "version": "1.0.0", + "commands": [ + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Dummy" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "deco" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Dummy", + "property": "One" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "one" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "SSN" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "scc" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "participantName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "M1" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "participantName", + "property": "participantKey" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "M3" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "assetName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "Dummy", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "term1" + }, + { + "$class": "concerto.metamodel@1.0.0.DecoratorNumber", + "value": 2 + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "mapName", + "mapElement": "KEY" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "deco", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorNumber", + "value": 1 + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "Editable" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "firstName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "Custom" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "firstName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "Form", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "inputType" + }, + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "text" + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "firstName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "New" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "lastName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "term", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "custom" + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "lastName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "term_desc", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "custom desc" + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "lastName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "Form", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "inputType" + }, + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "text" + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "lastName" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "New" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "bio" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "Form", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "inputType" + }, + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "textArea" + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "bio" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "New" + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "ssn" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "Form", + "arguments": [ + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "inputType" + }, + { + "$class": "concerto.metamodel@1.0.0.DecoratorString", + "value": "text" + } + ] + } + }, + { + "$class": "org.accordproject.decoratorcommands@0.3.0.Command", + "type": "UPSERT", + "target": { + "$class": "org.accordproject.decoratorcommands@0.3.0.CommandTarget", + "namespace": "test@1.0.0", + "declaration": "Person", + "property": "ssn" + }, + "decorator": { + "$class": "concerto.metamodel@1.0.0.Decorator", + "name": "New" + } + } + ] + } + ] diff --git a/packages/concerto-core/test/data/decoratorcommands/extract-test-vocab.json b/packages/concerto-core/test/data/decoratorcommands/extract-test-vocab.json new file mode 100644 index 000000000..5951c5485 --- /dev/null +++ b/packages/concerto-core/test/data/decoratorcommands/extract-test-vocab.json @@ -0,0 +1 @@ +["locale: en\nnamespace: test@1.0.0\ndeclarations:\n - mapName: Map Name\n properties:\n - KEY: some key\n desc: some key description\n - Person: Person Class\n desc: Person Class Description\n properties:\n - firstName: HI\n - bio: some\n cus: con\n"] diff --git a/packages/concerto-core/test/data/decoratorcommands/extract-test.cto b/packages/concerto-core/test/data/decoratorcommands/extract-test.cto index a8797eb67..d56f0851b 100644 --- a/packages/concerto-core/test/data/decoratorcommands/extract-test.cto +++ b/packages/concerto-core/test/data/decoratorcommands/extract-test.cto @@ -18,13 +18,17 @@ asset assetName identified by assetKey { o String assetKey } +@Term("Map Name") map mapName { + @Term("some key") + @Term_desc("some key description") @deco(1) o String o String } @Term("Person Class") +@Term_desc("Person Class Description") @Editable concept Person { @Term("HI") @@ -32,6 +36,8 @@ concept Person { @Form("inputType", "text") @New o String firstName + @term("custom") + @term_desc("custom desc") @Form("inputType", "text") @New o String lastName @@ -43,4 +49,4 @@ concept Person { @Form("inputType", "text") @New o String ssn -} \ No newline at end of file +} diff --git a/packages/concerto-core/test/decoratormanager.js b/packages/concerto-core/test/decoratormanager.js index 7be7f9d63..28dcad49b 100644 --- a/packages/concerto-core/test/decoratormanager.js +++ b/packages/concerto-core/test/decoratormanager.js @@ -600,7 +600,7 @@ describe('DecoratorManager', () => { }); describe('#extractDecorators', function() { - it('should be able to extract decorators and vocabs from a model withoup options', async function() { + it('should be able to extract decorators and vocabs from a model without options', async function() { const testModelManager = new ModelManager({strict:true,}); const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test.cto'), 'utf-8'); testModelManager.addCTOModel(modelText, 'test.cto'); @@ -674,6 +674,34 @@ describe('DecoratorManager', () => { const vocab = resp.vocabularies; vocab.should.be.deep.equal([]); }); + it('should be able to extract vocabs from a model', async function() { + const testModelManager = new ModelManager({strict:true,}); + const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test.cto'), 'utf-8'); + const expectedVocabs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test-vocab.json'), 'utf-8'); + testModelManager.addCTOModel(modelText, 'test.cto'); + const options = { + removeDecoratorsFromModel:true, + locale:'en' + }; + const resp = DecoratorManager.extractVocabularies( testModelManager, options); + const vocab = resp.vocabularies; + vocab.should.be.deep.equal(JSON.parse(expectedVocabs)); + vocab[0].should.not.include('custom'); + }); + it('should be able to extract non-vocab decorators from a model', async function() { + const testModelManager = new ModelManager({strict:true,}); + const modelText = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test.cto'), 'utf-8'); + const expectedDcs = fs.readFileSync(path.join(__dirname,'/data/decoratorcommands/extract-test-dcs.json'), 'utf-8'); + testModelManager.addCTOModel(modelText, 'test.cto'); + const options = { + removeDecoratorsFromModel:true, + locale:'en' + }; + const resp = DecoratorManager.extractNonVocabDecorators( testModelManager, options); + const dcs = resp.decoratorCommandSet; + dcs.should.be.deep.equal(JSON.parse(expectedDcs)); + JSON.stringify(dcs).should.include('term_desc'); + }); }); describe('#executePropertyCommand', () => { diff --git a/packages/concerto-vocabulary/lib/vocabulary.js b/packages/concerto-vocabulary/lib/vocabulary.js index 6a446b48c..32a866b1a 100644 --- a/packages/concerto-vocabulary/lib/vocabulary.js +++ b/packages/concerto-vocabulary/lib/vocabulary.js @@ -47,19 +47,16 @@ class Vocabulary { throw new Error('Vocabulary object must have declarations'); } - if(!voc.locale) { - throw new Error('A vocabulary must specify a locale'); - } if(!voc.namespace) { throw new Error('A vocabulary must specify a namespace'); } - // validate the locale - new Intl.Locale(voc.locale); - if(voc.locale !== voc.locale.toLowerCase()) { - throw new Error('Locale should be lowercase with dashes'); + if(!voc.locale) { + throw new Error('A vocabulary must specify a locale'); } + Vocabulary.validateLocale(voc.locale); + this.vocabularyManager = vocabularyManager; this.content = voc; } @@ -72,6 +69,18 @@ class Vocabulary { return this.content.namespace; } + /** + * Validates a locale + * @param {string} locale the locale to validate + * @throws {Error} if the locale is invalid + */ + static validateLocale(locale) { + new Intl.Locale(locale); + if(locale !== locale.toLowerCase()) { + throw new Error('Locale should be lowercase with dashes'); + } + } + /** * Returns the locale for the vocabulary * @returns {string} the locale for this vocabulary diff --git a/packages/concerto-vocabulary/test/vocabulary.js b/packages/concerto-vocabulary/test/vocabulary.js index 50acf386a..c959080db 100644 --- a/packages/concerto-vocabulary/test/vocabulary.js +++ b/packages/concerto-vocabulary/test/vocabulary.js @@ -99,4 +99,12 @@ describe('Vocabulary', () => { const json = voc.toJSON(); json.should.not.be.null; }); + + it('isValidLocale - invalid locale', () => { + should.Throw(() => Vocabulary.validateLocale('en-US'), Error); + }); + + it('isValidLocale - valid locale', () => { + should.not.Throw(() => Vocabulary.validateLocale('en-us'), Error); + }); });