Skip to content

Commit 1d81787

Browse files
committed
feat(cicero-core): add support for Concerto vocabularies (#711)
Add locale-specific vocabulary support to Cicero templates using Concerto's VocabularyManager. Templates can now include .voc files in a vocab/ directory to provide localized labels for model concepts and properties. Changes: - Load/save .voc files from vocab/ directory and archives - Add VocabularyManager to Template with getVocabulary() fallback - Add defaultLocale to Metadata (package.json accordproject section) - Add 'cicero vocabulary' CLI command with --locale option - Add test fixtures with English and French vocabularies - 20 new tests covering loading, round-trip, fallback, and CLI Closes #711 Signed-off-by: Adarsh Singh <anexus5919@gmail.com>
1 parent d5fba8f commit 1d81787

39 files changed

+1149
-6
lines changed

package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cicero-cli/index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,40 @@ require('yargs')
160160
return;
161161
}
162162
})
163+
.command('vocabulary', 'list or query vocabulary terms for a template', (yargs) => {
164+
yargs.option('template', {
165+
describe: 'path to the template',
166+
type: 'string'
167+
});
168+
yargs.option('locale', {
169+
describe: 'the BCP-47 locale to query (e.g. en, fr, en-ca)',
170+
type: 'string',
171+
default: null
172+
});
173+
yargs.option('warnings', {
174+
describe: 'print warnings',
175+
type: 'boolean',
176+
default: false
177+
});
178+
}, (argv) => {
179+
if (argv.verbose) {
180+
Logger.info(`list vocabularies for ${argv.template}`);
181+
}
182+
183+
try {
184+
argv = Commands.validateVocabularyArgs(argv);
185+
const options = {
186+
warnings: argv.warnings,
187+
};
188+
return Commands.vocabulary(argv.template, argv.locale, options)
189+
.catch((err) => {
190+
Logger.error(err.message);
191+
});
192+
} catch (err){
193+
Logger.error(err.message);
194+
return;
195+
}
196+
})
163197
.command('get', 'save local copies of external dependencies', (yargs) => {
164198
yargs.option('template', {
165199
describe: 'path to the template',

packages/cicero-cli/lib/commands.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,75 @@ class Commands {
222222
return argv;
223223
}
224224

225+
/**
226+
* Set default params before we list vocabularies
227+
*
228+
* @param {object} argv the inbound argument values object
229+
* @returns {object} a modfied argument object
230+
*/
231+
static validateVocabularyArgs(argv) {
232+
return Commands.validateCommonArgs(argv);
233+
}
234+
235+
/**
236+
* List or query vocabulary terms for a template
237+
*
238+
* @param {string} templatePath - path to the template directory or archive
239+
* @param {string} locale - the BCP-47 locale to query
240+
* @param {Object} [options] - an optional set of options
241+
* @returns {object} Promise to the vocabulary information
242+
*/
243+
static vocabulary(templatePath, locale, options) {
244+
return Commands.loadTemplate(templatePath, options)
245+
.then((template) => {
246+
const vocManager = template.getVocabularyManager();
247+
const vocFiles = template.getVocFiles();
248+
const defaultLocale = template.getMetadata().getDefaultLocale();
249+
250+
if (vocFiles.length === 0) {
251+
Logger.info('No vocabulary files found in this template.');
252+
return {};
253+
}
254+
255+
Logger.info(`Found ${vocFiles.length} vocabulary file(s). Default locale: ${defaultLocale || 'not set'}`);
256+
257+
if (locale) {
258+
// Query terms for a specific locale
259+
const modelFiles = template.getModelManager().getModelFiles().filter(mf => !mf.isSystemModelFile?.());
260+
const results = {};
261+
262+
modelFiles.forEach(mf => {
263+
const ns = mf.getNamespace();
264+
const voc = template.getVocabulary(ns, locale);
265+
if (voc) {
266+
Logger.info(`\nVocabulary for ${ns} (locale: ${voc.getLocale()}):`);
267+
const terms = voc.getTerms();
268+
terms.forEach(decl => {
269+
const declName = Object.keys(decl)[0];
270+
Logger.info(` ${declName}: ${decl[declName]}`);
271+
if (decl.properties) {
272+
decl.properties.forEach(prop => {
273+
const propName = Object.keys(prop)[0];
274+
Logger.info(` ${propName}: ${prop[propName]}`);
275+
});
276+
}
277+
});
278+
results[ns] = terms;
279+
} else {
280+
Logger.info(`\nNo vocabulary found for ${ns} in locale: ${locale}`);
281+
}
282+
});
283+
return results;
284+
} else {
285+
// List available vocabularies
286+
vocFiles.forEach(file => {
287+
Logger.info(` - ${file.name}`);
288+
});
289+
return { files: vocFiles.map(f => f.name), defaultLocale };
290+
}
291+
});
292+
}
293+
225294
/**
226295
* Fetches all external for a set of models dependencies and
227296
* saves all the models to a target directory

packages/cicero-cli/test/cli.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,42 @@ describe('#get', async () => {
269269
});
270270
});
271271

272+
describe('#validateVocabularyArgs', () => {
273+
it('no args specified', () => {
274+
process.chdir(path.resolve(__dirname, 'data/latedeliveryandpenalty-vocab/'));
275+
const args = Commands.validateVocabularyArgs({
276+
_: ['vocabulary']
277+
});
278+
args.template.should.match(/cicero-cli[/\\]test[/\\]data[/\\]latedeliveryandpenalty-vocab$/);
279+
});
280+
it('template arg specified', () => {
281+
process.chdir(path.resolve(__dirname));
282+
const args = Commands.validateVocabularyArgs({
283+
_: ['vocabulary', 'data/latedeliveryandpenalty-vocab/']
284+
});
285+
args.template.should.match(/cicero-cli[/\\]test[/\\]data[/\\]latedeliveryandpenalty-vocab$/);
286+
});
287+
});
288+
289+
describe('#vocabulary', async () => {
290+
it('should list vocabulary files for a template', async () => {
291+
const templatePath = path.resolve(__dirname, 'data/latedeliveryandpenalty-vocab/');
292+
const result = await Commands.vocabulary(templatePath, null, {});
293+
result.files.length.should.equal(2);
294+
result.defaultLocale.should.equal('en');
295+
});
296+
it('should query vocabulary terms for a specific locale', async () => {
297+
const templatePath = path.resolve(__dirname, 'data/latedeliveryandpenalty-vocab/');
298+
const result = await Commands.vocabulary(templatePath, 'en', {});
299+
result.should.not.be.null;
300+
});
301+
it('should report no vocabularies for a template without vocab files', async () => {
302+
const templatePath = path.resolve(__dirname, 'data/latedeliveryandpenalty/');
303+
const result = await Commands.vocabulary(templatePath, null, {});
304+
result.should.deep.equal({});
305+
});
306+
});
307+
272308
describe('#validateVerfiyArgs', () => {
273309
it('no args specified', () => {
274310
process.chdir(path.resolve(__dirname, 'data/signedArchive/'));
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
# Clause Template: Late Delivery And Penalty
3+
4+
## Sample
5+
6+
Late Delivery and Penalty. In case of delayed delivery except for Force Majeure cases, the Seller shall pay to the Buyer for every 2 days of delay penalty amounting to 10.5% of total value of the Equipment whose delivery has been delayed. Any fractional part of a day is to be considered a full day. The total amount of penalty shall not, however, exceed 55% of the total value of the Equipment involved in late delivery. If the delay is more than 15 days, the Buyer is entitled to terminate this Contract.
7+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
concerto version "^3.0.0"
16+
17+
namespace org.accordproject.contract@0.2.0
18+
19+
/**
20+
* Contract Data
21+
* -- Describes the structure of contracts and clauses
22+
*/
23+
24+
/* A contract is a asset -- This contains the contract data */
25+
abstract asset Contract identified by contractId {
26+
o String contractId
27+
}
28+
29+
/* A clause is an asset -- This contains the clause data */
30+
abstract asset Clause identified by clauseId {
31+
o String clauseId
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
concerto version "^3.0.0"
16+
17+
namespace org.accordproject.runtime@0.2.0
18+
19+
import org.accordproject.contract@0.2.0.Contract from https://models.accordproject.org/accordproject/contract@0.2.0.cto
20+
21+
/**
22+
* Runtime API
23+
* -- Describes input and output of calls to a contract's clause
24+
*/
25+
26+
/* A request is a transaction */
27+
transaction Request {
28+
}
29+
30+
/* A response is a transaction */
31+
transaction Response {
32+
}
33+
34+
/* An event that represents an obligation that needs to be fulfilled */
35+
abstract event Obligation identified {
36+
/* A back reference to the governing contract that emitted this obligation */
37+
--> Contract contract
38+
39+
/* The party that is obligated */
40+
--> Participant promisor optional
41+
42+
/* The party that receives the performance */
43+
--> Participant promisee optional
44+
45+
/* The time before which the obligation is fulfilled */
46+
o DateTime deadline optional
47+
}
48+
49+
/* A contract state is an asset -- The runtime state of the contract */
50+
asset State {
51+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
concerto version "^3.0.0"
15+
16+
namespace org.accordproject.time@0.3.0
17+
18+
/**
19+
* Months of the year
20+
*/
21+
enum Month {
22+
o January
23+
o February
24+
o March
25+
o April
26+
o May
27+
o June
28+
o July
29+
o August
30+
o September
31+
o October
32+
o November
33+
o December
34+
}
35+
36+
/**
37+
* Days of the week
38+
*/
39+
enum Day {
40+
o Monday
41+
o Tuesday
42+
o Wednesday
43+
o Thursday
44+
o Friday
45+
o Saturday
46+
o Sunday
47+
}
48+
49+
/**
50+
* Units for a duration.
51+
*/
52+
enum TemporalUnit {
53+
o seconds
54+
o minutes
55+
o hours
56+
o days
57+
o weeks
58+
}
59+
60+
/**
61+
* A duration. For example, 6 hours.
62+
*/
63+
concept Duration {
64+
o Long amount
65+
o TemporalUnit unit
66+
}
67+
68+
/**
69+
* Units for a time period.
70+
*/
71+
enum PeriodUnit {
72+
o days
73+
o weeks
74+
o months
75+
o quarters
76+
o years
77+
}
78+
79+
/**
80+
* A time period. For example, 2 months.
81+
*/
82+
concept Period {
83+
o Long amount
84+
o PeriodUnit unit
85+
}

0 commit comments

Comments
 (0)