Skip to content

Commit

Permalink
feat: added cli support for decorators and vocab (#37)
Browse files Browse the repository at this point in the history
* feat: added support for applying vocab and decorators to a model

Signed-off-by: Muskan Bararia <[email protected]>

* feat: added support for applying vocab and decorators to a model

Signed-off-by: Muskan Bararia <[email protected]>

* feat: Added support for output and standardised arg params

Signed-off-by: Muskan Bararia <[email protected]>

* feat: Added support for output and standardised arg params

Signed-off-by: Muskan Bararia <[email protected]>

* fix:Updated aliases for models

Signed-off-by: Muskan Bararia <[email protected]>

* fix: updated jsdoc for options

Signed-off-by: Muskan Bararia <[email protected]>

---------

Signed-off-by: Muskan Bararia <[email protected]>
Co-authored-by: Muskan Bararia <[email protected]>
  • Loading branch information
muskanbararia and Muskan Bararia authored Nov 14, 2023
1 parent cecce4d commit 3258869
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@ Copyright 2018-2019 Clause, Inc. All trademarks are the property of their respec
[developers]: https://github.com/accordproject/web-components/blob/master/DEVELOPERS.md

[apache]: https://github.com/accordproject/web-components/blob/master/LICENSE
[creativecommons]: http://creativecommons.org/licenses/by/4.0/
[creativecommons]: http://creativecommons.org/licenses/by/4.0/
48 changes: 48 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,54 @@ require('yargs')
Logger.error(err.message);
});
})
.command('decorate', 'apply the decorators and vocabs to the target models from given list of dcs files and vocab files', yargs => {
yargs.demandOption('models', 'Please provide a model');
yargs.option('models', {
describe: 'The file location of the source models',
alias: 'model',
type: 'string',
array:true,
});
yargs.option('decorator', {
describe: 'List of dcs files to be applied to model',
type: 'string',
array:true
});
yargs.option('vocabulary', {
describe: 'List of vocab files to be applied to model',
type: 'string',
array:true
});
yargs.option('format', {
describe: 'Output format (json or cto)',
type: 'string',
default:'cto',
choices: ['json', 'cto']
});
yargs.option('output', {
describe: 'output directory path',
type: 'string',
});
yargs.check((args) => {
// Custom validation to ensure at least one of the two options is provided
if (!args.decorator && !args.vocabulary) {
throw new Error('You must provide at least one of dcs files or voc files');
}
return true;
});
}, argv => {
let options={};
options.format=argv.format;
options.output=argv.output;

return Commands.decorate(argv.models, argv.decorator,argv.vocabulary,options)
.then(obj => {
console.log(obj);

Check warning on line 453 in index.js

View workflow job for this annotation

GitHub Actions / Unit Tests (18.x, ubuntu-latest)

Unexpected console statement

Check warning on line 453 in index.js

View workflow job for this annotation

GitHub Actions / Unit Tests (16.x, ubuntu-latest)

Unexpected console statement

Check warning on line 453 in index.js

View workflow job for this annotation

GitHub Actions / Unit Tests (18.x, macOS-latest)

Unexpected console statement

Check warning on line 453 in index.js

View workflow job for this annotation

GitHub Actions / Unit Tests (16.x, macOS-latest)

Unexpected console statement

Check warning on line 453 in index.js

View workflow job for this annotation

GitHub Actions / Unit Tests (16.x, windows-latest)

Unexpected console statement

Check warning on line 453 in index.js

View workflow job for this annotation

GitHub Actions / Unit Tests (18.x, windows-latest)

Unexpected console statement
})
.catch((err) => {
Logger.error(err.message);
});
})
.option('verbose', {
alias: 'v',
default: false
Expand Down
61 changes: 60 additions & 1 deletion lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const Concerto = require('@accordproject/concerto-core').Concerto;
const CodeGen = require('@accordproject/concerto-codegen').CodeGen;

const { Compare, compareResultToString } = require('@accordproject/concerto-analysis');
const { ModelFile, ModelManager } = require('@accordproject/concerto-core');
const { ModelFile, ModelManager,DecoratorManager } = require('@accordproject/concerto-core');
const { VocabularyManager } = require('@accordproject/concerto-vocabulary');

/**
* Utility class that implements the commands exposed by the CLI.
Expand Down Expand Up @@ -628,6 +629,64 @@ class Commands {
inferredConcertoJsonModel.models[0]
);
}

/**
* Decorate a given model with given list of dcs and vocab files and print the result
* @param {string} modelFiles - the model to which vocab and decorator has to be applied
* @param {string[]} dcsFiles - the decorator files to be applied to model
* @param {string[]} vocFiles - the vocab files to be applied to model
* @param {object} options - optional parameters
* @param {string} options.format - format of output models (CTO or JSON)
* @param {string} options.output - the output directory where the decorated models are to be written
* @returns {string} - Depending on the options, CTO string or JSON string
*/
static async decorate(modelFiles, dcsFiles,vocFiles,options) {
try {
const allModelContent = modelFiles.map(file => fs.readFileSync(file, 'utf-8'));
const allDCSFiles = dcsFiles ? dcsFiles.map(file => fs.readFileSync(file, 'utf-8')) : [];
const allVocsFiles = vocFiles ? vocFiles.map(file => fs.readFileSync(file, 'utf-8')) : [];
let modelManager = new ModelManager();
allModelContent.forEach(modelContent => {
modelManager.addModel(modelContent);
});
allDCSFiles.forEach(content => {
modelManager = DecoratorManager.decorateModels(modelManager, JSON.parse(content));
});
const vocManager = new VocabularyManager({ missingTermGenerator: VocabularyManager.englishMissingTermGenerator });
const namespace = modelManager.getNamespaces().filter(namespace=>namespace!=='[email protected]' && namespace!=='concerto');
allVocsFiles.forEach(content => {
vocManager.addVocabulary(content);
});
const vocabKeySet=[];
namespace.forEach(name=>{
let vocab = vocManager.getVocabulariesForNamespace(name);
vocab.forEach(voc=>vocabKeySet.push(voc.getLocale()));
});
vocabKeySet.map(voc=>{
let commandSet = vocManager.generateDecoratorCommands(modelManager, voc);
modelManager = DecoratorManager.decorateModels(modelManager, commandSet);
});
let result=[];
const extension = (options.format === 'cto') ? '.cto':'.json';
namespace.forEach(name=>{
let model = modelManager.getModelFile(name);
let modelAst=model.getAst();
let data = (options.format === 'cto') ? Printer.toCTO(modelAst):JSON.stringify(modelAst);
if (options.output) {
if (!fs.existsSync(options.output)) {
// If it doesn't exist, create the directory
fs.mkdirSync(options.output);
}
const filePath = path.join(options.output, model.namespace.split('@')[0]+extension);
fs.writeFileSync(filePath, data);
}
result.push(data);
});
return result;
} catch (e) {
throw new Error(e);
}
}
}

module.exports = Commands;
104 changes: 104 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -682,4 +682,108 @@ describe('concerto-cli', () => {
expect(obj.fatherName.length >= 1).to.be.true;
});
});

describe('#decorate', () => {
it('should apply the list of decorators to model and output in cto by default', async () => {
const dir = await tmp.dir({ unsafeCleanup: true });
const model = [(path.resolve(__dirname, 'models', 'decorate-model.cto'))];
const decorators = [path.resolve(__dirname, 'data', 'decorate-dcs.json')];
const vocabs = undefined;
const options={
format:'cto'
};
const expected = fs.readFileSync(path.resolve(__dirname, 'models', 'decorate-model-expected-with-dcs.cto'),'utf-8');
const result =await Commands.decorate(model,decorators,vocabs,options);
result[0].replace(/[\r\n]+/g, '\n').should.equal(expected.replace(/[\r\n]+/g, '\n'));
dir.cleanup();
});

it('should apply the list of vocabs to the model and output in cto by default', async () => {
const dir = await tmp.dir({ unsafeCleanup: true });
const model = [path.resolve(__dirname, 'models', 'decorate-model.cto')];
const vocabs = [path.resolve(__dirname, 'data', 'decorate-voc')];
const decorators = undefined;
const options={
format:'cto'
};
const expected = fs.readFileSync(path.resolve(__dirname, 'models', 'decorate-model-expected-with-vocabs-only.cto'),'utf-8');
const result =await Commands.decorate(model,decorators,vocabs,options);
result[0].replace(/[\r\n]+/g, '\n').should.equal(expected.replace(/[\r\n]+/g, '\n'));
dir.cleanup();
});

it('should apply the list of vocabs and list of decorators to the model and output in asked format', async () => {
const dir = await tmp.dir({ unsafeCleanup: true });
const model = [path.resolve(__dirname, 'models', 'decorate-model.cto')];
const vocabs = [path.resolve(__dirname, 'data', 'decorate-voc')];
const decorators = [path.resolve(__dirname, 'data', 'decorate-dcs.json')];
const options={
format:'json'
};
const result =await Commands.decorate(model,decorators,vocabs,options);
let jsonObj=JSON.parse(result);
(typeof jsonObj).should.equal('object');
dir.cleanup();
});
it('should throw error if data is invalid', async () => {
const dir = await tmp.dir({ unsafeCleanup: true });
const model = [path.resolve(__dirname, 'models', 'decorate-invalid-model.cto')];
const vocabs = [path.resolve(__dirname, 'data', 'decorate-voc')];
const decorators =undefined;
const options={
format:'json'
};
const expected = fs.readFileSync(path.resolve(__dirname, 'models', 'decorate-model-expected-with-vocabs-and-deco.json'),'utf-8');
try {
const result =await Commands.decorate(model,decorators,vocabs,options);
result.should.eql(expected);
} catch (err) {
(typeof err).should.equal('object');
}
dir.cleanup();
});
it('should write to a file if output is provided', async () => {
const output = await tmp.dir({ unsafeCleanup: true });
const model = [path.resolve(__dirname, 'models', 'decorate-model.cto')];
const vocabs = [path.resolve(__dirname, 'data', 'decorate-voc')];
const decorators =undefined;
const options={
format:'json',
output:output.path
};
await Commands.decorate(model,decorators,vocabs,options);
const files = fs.readdirSync(output.path);
const anyFileExists = files.length > 0;
expect(anyFileExists).to.be.true;
output.cleanup();
});
it('should write to a file if output is provided and create the directory if it does not exist', async () => {
const output = await tmp.dir({ unsafeCleanup: true });
const model = [path.resolve(__dirname, 'models', 'decorate-model.cto')];
const vocabs = [path.resolve(__dirname, 'data', 'decorate-voc')];
const decorators =undefined;
const options={
format:'json',
output:output.path+'_output'
};
await Commands.decorate(model,decorators,vocabs,options);
const files = fs.readdirSync(output.path+'_output');
const anyFileExists = files.length > 0;
expect(anyFileExists).to.be.true;
output.cleanup();
});
it('should apply decorators when the model files are having dependency', async () => {
const dir = await tmp.dir({ unsafeCleanup: true });
const model = [path.resolve(__dirname, 'models', 'version-c.cto'),path.resolve(__dirname, 'models', 'version-b.cto')];
const vocabs = undefined;
const decorators =[path.resolve(__dirname, 'data', 'decorate-dcs.json')];
const options={
format:'cto',
};
const expected = fs.readFileSync(path.resolve(__dirname, 'models', 'decorate-model-expected-with-dependency.cto'),'utf-8');
const result =await Commands.decorate(model,decorators,vocabs,options);
result[0].replace(/[\r\n]+/g, '\n').should.equal(expected.replace(/[\r\n]+/g, '\n'));
dir.cleanup();
});
});
});
51 changes: 51 additions & 0 deletions test/data/decorate-dcs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"$class": "org.accordproject.decoratorcommands.DecoratorCommandSet",
"name": "pii",
"version": "1.0.0",
"commands": [
{
"$class": "org.accordproject.decoratorcommands.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands.CommandTarget",
"property": "ssn"
},
"decorator": {
"$class": "[email protected]",
"name": "PII",
"arguments": [
]
}
},
{
"$class": "org.accordproject.decoratorcommands.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands.CommandTarget",
"property": "bar"
},
"decorator": {
"$class": "[email protected]",
"name": "PII",
"arguments": [
]
}
},
{
"$class": "org.accordproject.decoratorcommands.Command",
"type": "UPSERT",
"target": {
"$class": "org.accordproject.decoratorcommands.CommandTarget",
"type": "[email protected]"
},
"decorator": {
"$class": "[email protected]",
"name": "Hide",
"arguments": [{
"$class" : "[email protected]",
"value" : "object"
}]
}
}
]
}
6 changes: 6 additions & 0 deletions test/data/decorate-voc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
locale: en-gb
namespace: [email protected]
declarations:
- Driver: A driver of a vehicle
properties:
- favoriteColor: favourite colour
Empty file.
23 changes: 23 additions & 0 deletions test/models/decorate-model-expected-with-dcs.cto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace [email protected]

abstract concept Person identified by ssn {
o String firstName
o String lastName
@PII()
o String ssn
}

concept Driver extends Person {
o String favoriteColor
}

concept Employee {
@PII()
o String ssn
}

concept Car identified by vin {
o String vin
@Hide("object")
o Person owner
}
6 changes: 6 additions & 0 deletions test/models/decorate-model-expected-with-dependency.cto
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace [email protected]

concept Boo {
@PII()
o String bar
}
Loading

0 comments on commit 3258869

Please sign in to comment.