Skip to content

Commit 6acbe5a

Browse files
authored
feat (decorators) : add utility and tests for working with decorators (#333)
* feat (decorators) : add utility and tests for working with decorators Signed-off-by: Dan Selman <[email protected]> * fix(decorators) : correct error message Signed-off-by: Dan Selman <[email protected]>
1 parent 1a0c263 commit 6acbe5a

17 files changed

+14214
-5155
lines changed

package-lock.json

+13,695-5,082
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/markdown-cicero/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ module.exports.CiceroMarkModel = require('./lib/externalModels/CiceroMarkModel')
2525
module.exports.CiceroMarkTransformer = require('./lib/CiceroMarkTransformer');
2626
module.exports.FromCiceroEditVisitor = require('./lib/FromCiceroEditVisitor');
2727
module.exports.ToCommonMarkVisitor = require('./lib/ToCommonMarkVisitor');
28+
29+
module.exports.Decorators = require('./lib/Decorators');

packages/markdown-cicero/lib/CiceroMarkTransformer.js

-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ class CiceroMarkTransformer {
244244
const validJson = this.serializer.fromJSON(json);
245245
return this.serializer.toJSON(validJson);
246246
}
247-
248247
}
249248

250249
module.exports = CiceroMarkTransformer;
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
'use strict';
16+
17+
/**
18+
* A class to retrieve decorators on CiceroMark nodes
19+
*/
20+
class Decorators {
21+
/**
22+
* Construct an instance, based on a CiceroMark node
23+
* Note that decorator arguments must be specified as an
24+
* array of [name (string),value] pairs, even though this is
25+
* not enforced by the Concerto grammar.
26+
* @param {object} node the CiceroMark node
27+
*/
28+
constructor(node) {
29+
this.data = {};
30+
if(node.decorators) {
31+
node.decorators.forEach( (d) => {
32+
if(d.arguments.length % 2 !==0) {
33+
throw new Error('Arguments must be [name, value] pairs');
34+
}
35+
const args = {};
36+
for( let n=0; n < d.arguments.length-1; n=n+2) {
37+
const arg = d.arguments[n];
38+
if(arg.$class && arg.$class !== 'concerto.metamodel.DecoratorString') {
39+
throw new Error(`Argument names must be strings. Found ${arg.$class}`);
40+
}
41+
const argValue = d.arguments[n+1];
42+
args[arg.value] = argValue.$class === 'concerto.metamodel.DecoratorIdentifier' ? argValue.identifier : argValue.value;
43+
}
44+
this.data[d.name] = args;
45+
});
46+
}
47+
}
48+
49+
/**
50+
* Returns true is the decorator is present
51+
* @param {string} decoratorName the name of the decorator
52+
* @returns {boolean} true is the decorator is present
53+
*/
54+
hasDecorator(decoratorName) {
55+
return !!this.data[decoratorName];
56+
}
57+
58+
/**
59+
* Get the arguments for a named decorator
60+
* @param {string} decoratorName the name of the decorator
61+
* @returns {array} an array of arguments, or null
62+
*/
63+
getArguments(decoratorName) {
64+
return this.data[decoratorName];
65+
}
66+
67+
/**
68+
* Get the arguments for a named decorator
69+
* @param {string} decoratorName the name of the decorator
70+
* @param {string} argumentName the name of the decorator argument
71+
* @returns {object} the value of the argument or null if the decorator
72+
* is missing or undefined if the argument is missing
73+
*/
74+
getDecoratorValue(decoratorName, argumentName) {
75+
const args = this.getArguments(decoratorName);
76+
if(args) {
77+
return args[argumentName];
78+
}
79+
return null;
80+
}
81+
}
82+
83+
module.exports = Decorators;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
// @ts-nocheck
16+
/* eslint-disable no-undef */
17+
'use strict';
18+
19+
const fs = require('fs');
20+
const Decorators = require('./Decorators');
21+
22+
/**
23+
* Load a test node from disk
24+
* @param {string} name the name of the file to load
25+
* @returns {object} the node
26+
*/
27+
function loadNode(name) {
28+
return JSON.parse(fs.readFileSync( __dirname + `/../test/data/decorators/${name}`, 'utf-8'));
29+
}
30+
31+
describe('decorators', () => {
32+
it('handles string decorator', () => {
33+
const decorators = new Decorators( loadNode('string.json'));
34+
expect(decorators.getDecoratorValue( 'Test', 'type')).toBe('value');
35+
});
36+
37+
it('handles boolean decorator', () => {
38+
const decorators = new Decorators( loadNode('boolean.json'));
39+
expect(decorators.getDecoratorValue( 'Test', 'type')).toBe(true);
40+
});
41+
42+
it('handles number decorator', () => {
43+
const decorators = new Decorators( loadNode('number.json'));
44+
expect(decorators.getDecoratorValue( 'Test', 'type')).toBe(3.14);
45+
});
46+
47+
it('handles identifier decorator', () => {
48+
const decorators = new Decorators( loadNode('identifier.json'));
49+
expect(decorators.getDecoratorValue( 'Test', 'type')).toBe('typeIdentifier');
50+
});
51+
52+
it('handles getting a value for a missing decorator', () => {
53+
const decorators = new Decorators( loadNode('string.json'));
54+
expect(decorators.getDecoratorValue( 'Missing', 'type')).toBe(null);
55+
});
56+
57+
it('handles getting a missing value for a decorator', () => {
58+
const decorators = new Decorators( loadNode('string.json'));
59+
expect(decorators.getDecoratorValue( 'Test', 'missing')).toBe(undefined);
60+
});
61+
62+
it('handles mutiple args decorator', () => {
63+
const decorators = new Decorators( loadNode('multi.json'));
64+
expect(decorators.getDecoratorValue( 'Test', 'type')).toBe('value');
65+
expect(decorators.getDecoratorValue( 'Test', 'second')).toBe('value2');
66+
});
67+
68+
it('hasDecorator', () => {
69+
const decorators = new Decorators( loadNode('multi.json'));
70+
expect(decorators.hasDecorator( 'Test')).toBe(true);
71+
});
72+
73+
it('getArguments', () => {
74+
const decorators = new Decorators( loadNode('multi.json'));
75+
expect(decorators.getArguments( 'Test')).toStrictEqual({second: 'value2', type: 'value'});
76+
});
77+
78+
it('fails with odd number of arguments', () => {
79+
expect(() => new Decorators( loadNode('invalid-odd.json'))).toThrow('Arguments must be [name, value] pairs');
80+
});
81+
82+
it('fails with argument names that are not strings', () => {
83+
expect(() => new Decorators( loadNode('invalid-arg-name.json'))).toThrow('Argument names must be strings');
84+
});
85+
86+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$class": "org.accordproject.templatemark.VariableDefinition",
3+
"decorators": [{
4+
"$class": "concerto.metamodel.Decorator",
5+
"arguments": [{
6+
"$class": "concerto.metamodel.DecoratorString",
7+
"value": "type"
8+
},
9+
{
10+
"$class": "concerto.metamodel.DecoratorBoolean",
11+
"value": true
12+
}
13+
],
14+
"name": "Test"
15+
}]
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"$class": "org.accordproject.templatemark.VariableDefinition",
3+
"decorators": [{
4+
"$class": "concerto.metamodel.Decorator",
5+
"arguments": [{
6+
"$class": "concerto.metamodel.DecoratorString",
7+
"value": "type"
8+
},
9+
{
10+
"$class": "concerto.metamodel.DecoratorIdentifier",
11+
"identifier": "typeIdentifier",
12+
"isArray" : false
13+
}
14+
],
15+
"name": "Test"
16+
}]
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$class": "org.accordproject.templatemark.VariableDefinition",
3+
"decorators": [{
4+
"$class": "concerto.metamodel.Decorator",
5+
"arguments": [{
6+
"$class": "concerto.metamodel.DecoratorNumber",
7+
"value": 1
8+
},
9+
{
10+
"$class": "concerto.metamodel.DecoratorString",
11+
"value": "value"
12+
}
13+
],
14+
"name": "Test"
15+
}]
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$class": "org.accordproject.templatemark.VariableDefinition",
3+
"decorators": [{
4+
"$class": "concerto.metamodel.Decorator",
5+
"arguments": [{
6+
"$class": "concerto.metamodel.DecoratorString",
7+
"value": "type"
8+
}
9+
],
10+
"name": "Test"
11+
}]
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$class": "org.accordproject.templatemark.VariableDefinition",
3+
"decorators": [{
4+
"$class": "concerto.metamodel.Decorator",
5+
"arguments": [{
6+
"$class": "concerto.metamodel.DecoratorString",
7+
"value": "type"
8+
},
9+
{
10+
"$class": "concerto.metamodel.DecoratorString",
11+
"value": "value"
12+
},
13+
{
14+
"$class": "concerto.metamodel.DecoratorString",
15+
"value": "second"
16+
},
17+
{
18+
"$class": "concerto.metamodel.DecoratorString",
19+
"value": "value2"
20+
}
21+
],
22+
"name": "Test"
23+
}]
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$class": "org.accordproject.templatemark.VariableDefinition",
3+
"decorators": [{
4+
"$class": "concerto.metamodel.Decorator",
5+
"arguments": [{
6+
"$class": "concerto.metamodel.DecoratorString",
7+
"value": "type"
8+
},
9+
{
10+
"$class": "concerto.metamodel.DecoratorNumber",
11+
"value": 3.14
12+
}
13+
],
14+
"name": "Test"
15+
}]
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"$class": "org.accordproject.templatemark.VariableDefinition",
3+
"decorators": [{
4+
"$class": "concerto.metamodel.Decorator",
5+
"arguments": [{
6+
"$class": "concerto.metamodel.DecoratorString",
7+
"value": "type"
8+
},
9+
{
10+
"$class": "concerto.metamodel.DecoratorString",
11+
"value": "value"
12+
}
13+
],
14+
"name": "Test"
15+
}]
16+
}

packages/markdown-pdf/src/ToPdfMakeVisitor.js

+11-11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
'use strict';
1616

17+
const { Decorators } = require('@accordproject/markdown-cicero');
18+
1719
/**
1820
* Unquote strings
1921
* @param {string} value - the string
@@ -153,20 +155,18 @@ class ToPdfMakeVisitor {
153155
visit(thing, parameters) {
154156

155157
// the style defaults to the name of the type
156-
let style = thing.getType();
158+
let decoratorStyle = null;
157159

158-
// if the type has an explicit PdfStyle decorator, then we use it
159-
if( thing.decorators ) {
160-
const pdfStyle = thing.decorators.filter( d => d.name === 'PdfStyle');
161-
if( pdfStyle.length > 0 ) {
162-
if(pdfStyle[0].arguments && pdfStyle[0].arguments.length === 1) {
163-
style = pdfStyle[0].arguments[0].value;
164-
}
165-
}
160+
// if the type has an explicit Pdf style decorator, then we use it
161+
try {
162+
const decorators = new Decorators(thing);
163+
decoratorStyle = decorators.getDecoratorValue( 'Pdf', 'style');
164+
}
165+
catch(error) {
166+
console.log(error);
166167
}
167-
168168
let result = {
169-
style
169+
style : decoratorStyle ? decoratorStyle : thing.getType()
170170
};
171171

172172
switch(thing.getType()) {

0 commit comments

Comments
 (0)