Skip to content

Commit

Permalink
feat(markdown) support multiple key/value attributes on a block (#549)
Browse files Browse the repository at this point in the history
* feat(markdown) support multiple key/value attributes on a block

Signed-off-by: Dan Selman <[email protected]>

* fix(tests) : update snapshots etc to templatemark v0.5.0

Signed-off-by: Dan Selman <[email protected]>

* chore(concerto) bump version

Signed-off-by: Dan Selman <[email protected]>

* chore(concerto) bump version

Signed-off-by: Dan Selman <[email protected]>

---------

Signed-off-by: Dan Selman <[email protected]>
  • Loading branch information
dselman authored Jul 5, 2023
1 parent d29dd1c commit bc98994
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 19 deletions.
46 changes: 33 additions & 13 deletions packages/markdown-it-template/lib/template_re.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ const names = require('./names.json');
const string = '"([^"]*)"';
const identifier = '([a-zA-Z_][a-zA-Z0-9_]+)';
const name = '(?:\\s+([A-Za-z0-9_-]+))';
const attribute = '(?:\\s+' + identifier + '(?:\\s*=\\s*' + string + ')?)';
const attributes = '(.*?)';

const format = '(:?\\s+as\\s*'+ string + '\\s*)?';
const variable = '{{\\s*' + identifier + format + '\\s*}}';

const open_block = '{{#\\s*' + identifier + name + attribute + '*\\s*}}';
const open_block = '{{#\\s*' + identifier + name + attributes + '\\s*}}';
const close_block = '{{/\\s*' + identifier + '\\s*}}';
const formula = '{{%([^%]*)%}}';

Expand All @@ -35,19 +35,39 @@ const OPEN_BLOCK_RE = new RegExp('^(?:' + open_block + ')');
const CLOSE_BLOCK_RE = new RegExp('^(?:' + close_block + ')');
const FORMULA_RE = new RegExp('^(?:' + formula + ')');

/**
* Parses an argument string into an object
* @param {string} input the argument string to parse
* @returns {[string]} an array of strings, key/value
*/
function parseArguments(input) {
const regex = /(\w+)\s*=\s*"([^"]+)"/g;
let match;
const result = [];
while ((match = regex.exec(input))) {
const argName = match[1];
const argValue = match[2];
result.push([argName, argValue]);
}
return result;
}

/**
* Extract attributes from opening blocks
* @param {string[]} match
* @param {string[]} match the block data
* @return {*[]} attributes
*/
function getBlockAttributes(match) {
const result = [];
let result = [];
// name is always present in the block
result.push([ 'name', match[2] ]);
// those are block attributes
for(let i = 3; i < match.length; i = i+2) {
if (match[i]) {
result.push([ match[i], match[i+1] ]);

// the fourth match is all the arguments
// e.g. style="long" locale="en"
if(match[3]) {
const args = parseArguments(match[3]);
if(args && args.length > 0) {
result = result.concat(args);
}
}
return result;
Expand All @@ -60,9 +80,9 @@ function getBlockAttributes(match) {
* @return {*} open tag
*/
function matchOpenBlock(text,stack) {
var match = text.match(OPEN_BLOCK_RE);
const match = text.match(OPEN_BLOCK_RE);
if (!match) { return null; }
var block_open = match[1];
const block_open = match[1];
if (!names.blocks.includes(block_open)) { return null; }
stack.unshift(block_open);
return { tag: block_open, attrs: getBlockAttributes(match), matched: match };
Expand All @@ -75,14 +95,14 @@ function matchOpenBlock(text,stack) {
* @return {*} close tag
*/
function matchCloseBlock(text,block_open,stack) {
var match = text.match(CLOSE_BLOCK_RE);
const match = text.match(CLOSE_BLOCK_RE);
if (!match) {
return null;
}
var block_close = match[1];
const block_close = match[1];
// Handle proper nesting
if (stack[0] === block_close) {
stack.shift()
stack.shift();
}
// Handle stack depleted
if (stack.length > 0) {
Expand Down
6 changes: 3 additions & 3 deletions packages/markdown-template/lib/templaterules.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var formulaRule = {
node.name = formulaName(code);
node.code = {
$class: "".concat(TemplateMarkModel.NAMESPACE, ".Code"),
type: 'ES_2020',
type: 'TYPESCRIPT',
contents: code
};
node.dependencies = [];
Expand All @@ -84,7 +84,7 @@ var ifOpenRule = {
if (condition) {
node.condition = {
$class: "".concat(TemplateMarkModel.NAMESPACE, ".Code"),
type: 'ES_2020',
type: 'TYPESCRIPT',
contents: condition
};
}
Expand Down Expand Up @@ -206,7 +206,7 @@ var clauseOpenRule = {
if (condition) {
node.condition = {
$class: "".concat(TemplateMarkModel.NAMESPACE, ".Code"),
type: 'ES_2020',
type: 'TYPESCRIPT',
contents: condition
};
}
Expand Down
6 changes: 3 additions & 3 deletions packages/markdown-template/src/templaterules.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const formulaRule = {
node.name = formulaName(code);
node.code = {
$class: `${TemplateMarkModel.NAMESPACE}.Code`,
type: 'ES_2020',
type: 'TYPESCRIPT',
contents: code
};
node.dependencies = [];
Expand All @@ -79,7 +79,7 @@ const ifOpenRule = {
if(condition) {
node.condition = {
$class: `${TemplateMarkModel.NAMESPACE}.Code`,
type: 'ES_2020',
type: 'TYPESCRIPT',
contents: condition
};
}
Expand Down Expand Up @@ -195,7 +195,7 @@ const clauseOpenRule = {
if(condition) {
node.condition = {
$class: `${TemplateMarkModel.NAMESPACE}.Code`,
type: 'ES_2020',
type: 'TYPESCRIPT',
contents: condition
};
}
Expand Down
95 changes: 95 additions & 0 deletions packages/markdown-template/test/TemplateMarkTransformer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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 chai = require('chai');
chai.use(require('chai-string'));

chai.should();
chai.use(require('chai-things'));
chai.use(require('chai-as-promised'));

const { ModelManager } = require('@accordproject/concerto-core');
const { TemplateMarkModel } = require('@accordproject/markdown-common');

const TemplateMarkTransformer = require('../lib/TemplateMarkTransformer');

const MODEL = `
namespace [email protected]
@template
concept Thing {
o String[] items
}`;

describe('#TemplateMarkTransformer', () => {
describe('#tokensToMarkdownTemplate', () => {
it('should handle join with type, style and locale', async () => {
const transformer = new TemplateMarkTransformer();
const modelManager = new ModelManager();
modelManager.addCTOModel(MODEL);
const tokens = transformer.toTokens({content: '{{#join items type="conjunction" style="long" locale="en"}}{{/join}}'});
const result = transformer.tokensToMarkdownTemplate(tokens, modelManager, 'clause');
const joinNode = result.nodes[0].nodes[0].nodes[0];
joinNode.$class.should.equal(`${TemplateMarkModel.NAMESPACE}.JoinDefinition`);
joinNode.locale.should.equal('en');
joinNode.type.should.equal('conjunction');
joinNode.style.should.equal('long');
});

it('should handle join with type, style', async () => {
const transformer = new TemplateMarkTransformer();
const modelManager = new ModelManager();
modelManager.addCTOModel(MODEL);
const tokens = transformer.toTokens({content: '{{#join items type="conjunction" style="long"}}{{/join}}'});
const result = transformer.tokensToMarkdownTemplate(tokens, modelManager, 'clause');
const joinNode = result.nodes[0].nodes[0].nodes[0];
joinNode.$class.should.equal(`${TemplateMarkModel.NAMESPACE}.JoinDefinition`);
joinNode.type.should.equal('conjunction');
joinNode.style.should.equal('long');
});

it('should handle join with type', async () => {
const transformer = new TemplateMarkTransformer();
const modelManager = new ModelManager();
modelManager.addCTOModel(MODEL);
const tokens = transformer.toTokens({content: '{{#join items type="conjunction"}}{{/join}}'});
const result = transformer.tokensToMarkdownTemplate(tokens, modelManager, 'clause');
const joinNode = result.nodes[0].nodes[0].nodes[0];
joinNode.$class.should.equal(`${TemplateMarkModel.NAMESPACE}.JoinDefinition`);
joinNode.type.should.equal('conjunction');
});

it('should handle join', async () => {
const transformer = new TemplateMarkTransformer();
const modelManager = new ModelManager();
modelManager.addCTOModel(MODEL);
const tokens = transformer.toTokens({content: '{{#join items}}{{/join}}'});
const result = transformer.tokensToMarkdownTemplate(tokens, modelManager, 'clause');
const joinNode = result.nodes[0].nodes[0].nodes[0];
joinNode.$class.should.equal(`${TemplateMarkModel.NAMESPACE}.JoinDefinition`);
});

it('should ignore unknown attributes on join', async () => {
const transformer = new TemplateMarkTransformer();
const modelManager = new ModelManager();
modelManager.addCTOModel(MODEL);
const tokens = transformer.toTokens({content: '{{#join items foo="bar"}}{{/join}}'});
const result = transformer.tokensToMarkdownTemplate(tokens, modelManager, 'clause');
const joinNode = result.nodes[0].nodes[0].nodes[0];
joinNode.$class.should.equal(`${TemplateMarkModel.NAMESPACE}.JoinDefinition`);
(joinNode.foo === undefined).should.be.true;
});
});
});

0 comments on commit bc98994

Please sign in to comment.