Add label templates [#22], also Bowie is the *goblin* king... oops!
This commit is contained in:
parent
af2e786be8
commit
16095cf78a
33
README.md
33
README.md
|
@ -17,16 +17,16 @@ other projects.
|
|||
```
|
||||
title Labyrinth
|
||||
|
||||
Bowie -> Gremlin: You remind me of the babe
|
||||
Gremlin -> Bowie: What babe?
|
||||
Bowie -> Gremlin: The babe with the power
|
||||
Gremlin -> Bowie: What power?
|
||||
note right of Bowie, Gremlin: Most people get muddled here!
|
||||
Bowie -> Gremlin: 'The power of voodoo'
|
||||
Gremlin -> Bowie: "Who-do?"
|
||||
Bowie -> Gremlin: You do!
|
||||
Gremlin -> Bowie: Do what?
|
||||
Bowie -> Gremlin: Remind me of the babe!
|
||||
Bowie -> Goblin: You remind me of the babe
|
||||
Goblin -> Bowie: What babe?
|
||||
Bowie -> Goblin: The babe with the power
|
||||
Goblin -> Bowie: What power?
|
||||
note right of Bowie, Goblin: Most people get muddled here!
|
||||
Bowie -> Goblin: 'The power of voodoo'
|
||||
Goblin -> Bowie: "Who-do?"
|
||||
Bowie -> Goblin: You do!
|
||||
Goblin -> Bowie: Do what?
|
||||
Bowie -> Goblin: Remind me of the babe!
|
||||
|
||||
Bowie -> Audience: Sings
|
||||
|
||||
|
@ -113,6 +113,19 @@ else
|
|||
end
|
||||
```
|
||||
|
||||
### Label Templates
|
||||
|
||||
<img src="screenshots/LabelTemplates.png" alt="Label Templates preview" width="200" align="right" />
|
||||
|
||||
```
|
||||
autolabel "[<inc>] <label>"
|
||||
|
||||
begin "Underpants\nGnomes" as A
|
||||
A <- ]: Collect underpants
|
||||
A <-> ]: ???
|
||||
A <- ]: Profit!
|
||||
```
|
||||
|
||||
### Multiline Text
|
||||
|
||||
<img src="screenshots/MultilineText.png" alt="Multiline Text preview" width="200" align="right" />
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
|
@ -25,16 +25,16 @@
|
|||
const defaultCode = (
|
||||
'title Labyrinth\n' +
|
||||
'\n' +
|
||||
'Bowie -> Gremlin: You remind me of the babe\n' +
|
||||
'Gremlin -> Bowie: What babe?\n' +
|
||||
'Bowie -> Gremlin: The babe with the power\n' +
|
||||
'Gremlin -> Bowie: What power?\n' +
|
||||
'note right of Bowie, Gremlin: Most people get muddled here!\n' +
|
||||
'Bowie -> Gremlin: \'The power of voodoo\'\n' +
|
||||
'Gremlin -> Bowie: "Who-do?"\n' +
|
||||
'Bowie -> Gremlin: You do!\n' +
|
||||
'Gremlin -> Bowie: Do what?\n' +
|
||||
'Bowie -> Gremlin: Remind me of the babe!\n' +
|
||||
'Bowie -> Goblin: You remind me of the babe\n' +
|
||||
'Goblin -> Bowie: What babe?\n' +
|
||||
'Bowie -> Goblin: The babe with the power\n' +
|
||||
'Goblin -> Bowie: What power?\n' +
|
||||
'note right of Bowie, Goblin: Most people get muddled here!\n' +
|
||||
'Bowie -> Goblin: \'The power of voodoo\'\n' +
|
||||
'Goblin -> Bowie: "Who-do?"\n' +
|
||||
'Bowie -> Goblin: You do!\n' +
|
||||
'Goblin -> Bowie: Do what?\n' +
|
||||
'Bowie -> Goblin: Remind me of the babe!\n' +
|
||||
'\n' +
|
||||
'Bowie -> Audience: Sings\n' +
|
||||
'\n' +
|
||||
|
@ -86,6 +86,16 @@
|
|||
'end B'
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Numbered labels',
|
||||
code: 'autolabel "[<inc>] <label>"',
|
||||
preview: (
|
||||
'autolabel "[<inc>] <label>"\n' +
|
||||
'A -> B: Foo\n' +
|
||||
'A <- B: Bar\n' +
|
||||
'A -> B: Baz'
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Conditional blocks',
|
||||
code: (
|
||||
|
|
|
@ -207,6 +207,10 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
'left': makeSideNote('left'),
|
||||
'right': makeSideNote('right'),
|
||||
}},
|
||||
'autolabel': {type: 'keyword', suggest: true, then: {
|
||||
'off': {type: 'keyword', suggest: true, then: {}},
|
||||
'': textToEnd,
|
||||
}},
|
||||
'simultaneously': {type: 'keyword', suggest: true, then: {
|
||||
':': {type: 'operator', suggest: true, then: {}},
|
||||
'with': {type: 'keyword', suggest: true, then: {
|
||||
|
|
|
@ -179,6 +179,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.agentStates = new Map();
|
||||
this.agentAliases = new Map();
|
||||
this.agents = [];
|
||||
this.labelPattern = null;
|
||||
this.blockCount = 0;
|
||||
this.nesting = [];
|
||||
this.markers = new Set();
|
||||
|
@ -191,6 +192,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
'agent define': this.handleAgentDefine.bind(this),
|
||||
'agent begin': this.handleAgentBegin.bind(this),
|
||||
'agent end': this.handleAgentEnd.bind(this),
|
||||
'label pattern': this.handleLabelPattern.bind(this),
|
||||
'connect': this.handleConnect.bind(this),
|
||||
'note over': this.handleNote.bind(this),
|
||||
'note left': this.handleNote.bind(this),
|
||||
|
@ -373,6 +375,36 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.addStage({type: 'async', target}, false);
|
||||
}
|
||||
|
||||
handleLabelPattern({pattern}) {
|
||||
this.labelPattern = pattern.slice();
|
||||
for(let i = 0; i < this.labelPattern.length; ++ i) {
|
||||
const part = this.labelPattern[i];
|
||||
if(typeof part === 'object' && part.start !== undefined) {
|
||||
this.labelPattern[i] = Object.assign({
|
||||
current: part.start,
|
||||
}, part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyLabelPattern(label) {
|
||||
let result = '';
|
||||
const tokens = {
|
||||
'label': label,
|
||||
};
|
||||
this.labelPattern.forEach((part) => {
|
||||
if(typeof part === 'string') {
|
||||
result += part;
|
||||
} else if(part.token !== undefined) {
|
||||
result += tokens[part.token];
|
||||
} else if(part.current !== undefined) {
|
||||
result += part.current.toFixed(part.dp);
|
||||
part.current += part.inc;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
handleConnect({agents, label, options}) {
|
||||
const beginAgents = (agents
|
||||
.filter(agentHasFlag('begin'))
|
||||
|
@ -412,7 +444,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
const connectStage = {
|
||||
type: 'connect',
|
||||
agentNames,
|
||||
label,
|
||||
label: this.applyLabelPattern(label),
|
||||
options,
|
||||
};
|
||||
|
||||
|
@ -528,6 +560,7 @@ define(['core/ArrayUtilities'], (array) => {
|
|||
this.agents.length = 0;
|
||||
this.blockCount = 0;
|
||||
this.nesting.length = 0;
|
||||
this.labelPattern = [{token: 'label'}];
|
||||
const globals = this.beginNested('global', '', '', 0);
|
||||
|
||||
stages.forEach(this.handleStage);
|
||||
|
|
|
@ -26,6 +26,10 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
return {type: 'block end'};
|
||||
},
|
||||
|
||||
labelPattern: (pattern, {ln = 0} = {}) => {
|
||||
return {type: 'label pattern', pattern, ln};
|
||||
},
|
||||
|
||||
defineAgents: (agentNames, {ln = 0} = {}) => {
|
||||
return {
|
||||
type: 'agent define',
|
||||
|
@ -325,6 +329,56 @@ defineDescribe('Sequence Generator', ['./Generator'], (Generator) => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('uses label patterns for connections', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.labelPattern(['foo ', {token: 'label'}, ' bar']),
|
||||
PARSED.connect(['A', 'B'], {label: 'myLabel'}),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
GENERATED.connect(['A', 'B'], {
|
||||
label: 'foo myLabel bar',
|
||||
}),
|
||||
jasmine.anything(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('applies counters in label patterns', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.labelPattern([{start: 3, inc: 2, dp: 0}, ' suffix']),
|
||||
PARSED.connect(['A', 'B'], {label: 'foo'}),
|
||||
PARSED.connect(['A', 'B'], {label: 'bar'}),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
GENERATED.connect(['A', 'B'], {
|
||||
label: '3 suffix',
|
||||
}),
|
||||
GENERATED.connect(['A', 'B'], {
|
||||
label: '5 suffix',
|
||||
}),
|
||||
jasmine.anything(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('applies counter rounding in label patterns', () => {
|
||||
const sequence = generator.generate({stages: [
|
||||
PARSED.labelPattern([{start: 0.52, inc: 1, dp: 1}, ' suffix']),
|
||||
PARSED.connect(['A', 'B'], {label: 'foo'}),
|
||||
PARSED.connect(['A', 'B'], {label: 'bar'}),
|
||||
]});
|
||||
expect(sequence.stages).toEqual([
|
||||
jasmine.anything(),
|
||||
GENERATED.connect(['A', 'B'], {
|
||||
label: '0.5 suffix',
|
||||
}),
|
||||
GENERATED.connect(['A', 'B'], {
|
||||
label: '1.5 suffix',
|
||||
}),
|
||||
jasmine.anything(),
|
||||
]);
|
||||
});
|
||||
|
||||
it('creates implicit end stages for all remaining agents', () => {
|
||||
const sequence = generator.generate({
|
||||
meta: {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
define(() => {
|
||||
'use strict';
|
||||
|
||||
const LABEL_PATTERN = /(.*?)<([^<>]*)>/g;
|
||||
const DP_PATTERN = /\.([0-9]*)/;
|
||||
|
||||
function countDP(value) {
|
||||
const match = DP_PATTERN.exec(value);
|
||||
if(!match || !match[1]) {
|
||||
return 0;
|
||||
}
|
||||
return match[1].length;
|
||||
}
|
||||
|
||||
function parseCounter(args) {
|
||||
let start = 1;
|
||||
let inc = 1;
|
||||
let dp = 0;
|
||||
if(args[0]) {
|
||||
start = Number(args[0]);
|
||||
dp = Math.max(dp, countDP(args[0]));
|
||||
}
|
||||
if(args[1]) {
|
||||
inc = Number(args[1]);
|
||||
dp = Math.max(dp, countDP(args[1]));
|
||||
}
|
||||
return {start, inc, dp};
|
||||
}
|
||||
|
||||
function parseToken(token) {
|
||||
if(token === 'label') {
|
||||
return {token: 'label'};
|
||||
}
|
||||
|
||||
const p = token.indexOf(' ');
|
||||
let type = null;
|
||||
let args = null;
|
||||
if(p === -1) {
|
||||
type = token;
|
||||
args = [];
|
||||
} else {
|
||||
type = token.substr(0, p);
|
||||
args = token.substr(p + 1).split(',');
|
||||
}
|
||||
|
||||
if(type === 'inc') {
|
||||
return parseCounter(args);
|
||||
}
|
||||
|
||||
return '<' + token + '>';
|
||||
}
|
||||
|
||||
function parsePattern(raw) {
|
||||
const pattern = [];
|
||||
let match = null;
|
||||
let end = 0;
|
||||
LABEL_PATTERN.lastIndex = 0;
|
||||
while((match = LABEL_PATTERN.exec(raw))) {
|
||||
if(match[1]) {
|
||||
pattern.push(match[1]);
|
||||
}
|
||||
if(match[2]) {
|
||||
pattern.push(parseToken(match[2]));
|
||||
}
|
||||
end = LABEL_PATTERN.lastIndex;
|
||||
}
|
||||
const remainder = raw.substr(end);
|
||||
if(remainder) {
|
||||
pattern.push(remainder);
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
return parsePattern;
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
defineDescribe('Label Pattern Parser', ['./LabelPatternParser'], (parser) => {
|
||||
'use strict';
|
||||
|
||||
it('converts simple text', () => {
|
||||
const parsed = parser('hello everybody');
|
||||
expect(parsed).toEqual([
|
||||
'hello everybody',
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles the empty case', () => {
|
||||
const parsed = parser('');
|
||||
expect(parsed).toEqual([]);
|
||||
});
|
||||
|
||||
it('converts tokens', () => {
|
||||
const parsed = parser('foo <label> bar');
|
||||
expect(parsed).toEqual([
|
||||
'foo ',
|
||||
{token: 'label'},
|
||||
' bar',
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts multiple tokens', () => {
|
||||
const parsed = parser('foo <label> bar <label> baz');
|
||||
expect(parsed).toEqual([
|
||||
'foo ',
|
||||
{token: 'label'},
|
||||
' bar ',
|
||||
{token: 'label'},
|
||||
' baz',
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignores empty sequences', () => {
|
||||
const parsed = parser('<label><label>');
|
||||
expect(parsed).toEqual([
|
||||
{token: 'label'},
|
||||
{token: 'label'},
|
||||
]);
|
||||
});
|
||||
|
||||
it('passes unrecognised tokens through unchanged', () => {
|
||||
const parsed = parser('foo <nope>');
|
||||
expect(parsed).toEqual([
|
||||
'foo ',
|
||||
'<nope>',
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts counters', () => {
|
||||
const parsed = parser('<inc 5, 2>a<inc 2, 1>b');
|
||||
expect(parsed).toEqual([
|
||||
{start: 5, inc: 2, dp: 0},
|
||||
'a',
|
||||
{start: 2, inc: 1, dp: 0},
|
||||
'b',
|
||||
]);
|
||||
});
|
||||
|
||||
it('defaults counters to increment = 1', () => {
|
||||
const parsed = parser('<inc 5>');
|
||||
expect(parsed).toEqual([
|
||||
{start: 5, inc: 1, dp: 0},
|
||||
]);
|
||||
});
|
||||
|
||||
it('defaults counters to start = 1', () => {
|
||||
const parsed = parser('<inc>');
|
||||
expect(parsed).toEqual([
|
||||
{start: 1, inc: 1, dp: 0},
|
||||
]);
|
||||
});
|
||||
|
||||
it('assigns decimal places to counters by their written precision', () => {
|
||||
const parsed = parser('<inc 5.0, 2.00><inc 2.00, 1.0>');
|
||||
expect(parsed).toEqual([
|
||||
{start: jasmine.anything(), inc: jasmine.anything(), dp: 2},
|
||||
{start: jasmine.anything(), inc: jasmine.anything(), dp: 2},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,10 +1,12 @@
|
|||
define([
|
||||
'core/ArrayUtilities',
|
||||
'./Tokeniser',
|
||||
'./LabelPatternParser',
|
||||
'./CodeMirrorHints',
|
||||
], (
|
||||
array,
|
||||
Tokeniser,
|
||||
labelPatternParser,
|
||||
CMHints
|
||||
) => {
|
||||
'use strict';
|
||||
|
@ -247,6 +249,23 @@ define([
|
|||
return true;
|
||||
},
|
||||
|
||||
(line) => { // autolabel
|
||||
if(tokenKeyword(line[0]) !== 'autolabel') {
|
||||
return null;
|
||||
}
|
||||
|
||||
let raw = null;
|
||||
if(tokenKeyword(line[1]) === 'off') {
|
||||
raw = '<label>';
|
||||
} else {
|
||||
raw = joinLabel(line, 1);
|
||||
}
|
||||
return {
|
||||
type: 'label pattern',
|
||||
pattern: labelPatternParser(raw),
|
||||
};
|
||||
},
|
||||
|
||||
(line) => { // block
|
||||
if(tokenKeyword(line[0]) === 'end' && line.length === 1) {
|
||||
return {type: 'block end'};
|
||||
|
|
|
@ -412,6 +412,32 @@ defineDescribe('Sequence Parser', ['./Parser'], (Parser) => {
|
|||
}]);
|
||||
});
|
||||
|
||||
it('converts autolabel commands', () => {
|
||||
const parsed = parser.parse('autolabel "foo <label> bar"');
|
||||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'label pattern',
|
||||
ln: jasmine.anything(),
|
||||
pattern: [
|
||||
'foo ',
|
||||
{token: 'label'},
|
||||
' bar',
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts autolabel off commands', () => {
|
||||
const parsed = parser.parse('autolabel off');
|
||||
expect(parsed.stages).toEqual([
|
||||
{
|
||||
type: 'label pattern',
|
||||
ln: jasmine.anything(),
|
||||
pattern: [{token: 'label'}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('converts "simultaneously" flow commands', () => {
|
||||
const parsed = parser.parse('simultaneously:');
|
||||
expect(parsed.stages).toEqual([{
|
||||
|
|
|
@ -7,6 +7,7 @@ define([
|
|||
'interface/Interface_spec',
|
||||
'sequence/Tokeniser_spec',
|
||||
'sequence/Parser_spec',
|
||||
'sequence/LabelPatternParser_spec',
|
||||
'sequence/Generator_spec',
|
||||
'sequence/Renderer_spec',
|
||||
'sequence/themes/Basic_spec',
|
||||
|
|
Loading…
Reference in New Issue