Merge pull request #1582 from cmmoran/develop

Directive support for many diagrams, config bugfix
This commit is contained in:
Knut Sveidqvist
2020-07-28 13:17:35 +02:00
committed by GitHub
42 changed files with 1625 additions and 903 deletions

View File

@@ -1,8 +1,9 @@
import { select } from 'd3';
import { logger } from '../../logger';
import { getConfig } from '../../config';
import configApi, { getConfig } from '../../config';
import common from '../common/common';
import utils from '../../utils';
import mermaidAPI from '../../mermaidAPI';
const MERMAID_DOM_ID_PREFIX = 'classid-';
@@ -14,6 +15,10 @@ let classCounter = 0;
let funs = [];
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
const splitClassNameAndType = function(id) {
let genericType = '';
let className = id;
@@ -288,6 +293,8 @@ const setupToolTips = function(element) {
funs.push(setupToolTips);
export default {
parseDirective,
getConfig: () => configApi.getConfig().class,
addClass,
bindFunctions,
clear,

View File

@@ -242,69 +242,74 @@ describe('class diagram, ', function () {
it('should handle comments at the start', function () {
const str =
'%% Comment\n' +
'classDiagram\n' +
'class Class1 {\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}';
`%% Comment
classDiagram
class Class1 {
int : test
string : foo
test()
foo()
}`;
parser.parse(str);
});
it('should handle comments at the end', function () {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'\n}' +
'%% Comment\n';
`classDiagram
class Class1 {
int : test
string : foo
test()
foo()
}
%% Comment
`;
parser.parse(str);
});
it('should handle comments at the end no trailing newline', function () {
const str =
'classDiagram\n' +
'class Class1 {\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}\n' +
'%% Comment';
`classDiagram
class Class1 {
int : test
string : foo
test()
foo()
}
%% Comment`;
parser.parse(str);
});
it('should handle a comment with multiple line feeds', function () {
const str =
'classDiagram\n\n\n' +
'%% Comment\n\n' +
'class Class1 {\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}';
`classDiagram
%% Comment
class Class1 {
int : test
string : foo
test()
foo()
}`;
parser.parse(str);
});
it('should handle a comment with mermaid class diagram code in them', function () {
const str =
'classDiagram\n' +
'%% Comment Class01 <|-- Class02\n' +
'class Class1 {\n' +
'int : test\n' +
'string : foo\n' +
'test()\n' +
'foo()\n' +
'}';
`classDiagram
%% Comment Class01 <|-- Class02
class Class1 {
int : test
string : foo
test()
foo()
}`;
parser.parse(str);
});
@@ -640,7 +645,7 @@ describe('class diagram, ', function () {
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
});
it('should associate link with tooltip', function () {
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()\n' + 'link Class1 "google.com" "A tooltip"';
parser.parse(str);

View File

@@ -6,25 +6,31 @@
/* lexical grammar */
%lex
%x string generic struct
%x string generic struct open_directive type_directive arg_directive
%%
\%\%[^\n]*\n* /* do nothing */
\n+ return 'NEWLINE';
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)*[^\n]*(\r?\n)+ /* skip comments */
\%\%[^\n]*(\r?\n)* /* skip comments */
(\r?\n)+ return 'NEWLINE';
\s+ /* skip whitespace */
"classDiagram-v2" return 'CLASS_DIAGRAM';
"classDiagram" return 'CLASS_DIAGRAM';
[\{] { this.begin("struct"); /*console.log('Starting struct');*/return 'STRUCT_START';}
[{] { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';}
<struct><<EOF>> return "EOF_IN_STRUCT";
<struct>[\{] return "OPEN_IN_STRUCT";
<struct>\} { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
<struct>[\n] /* nothing */
<struct>[^\{\}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
<struct>[{] return "OPEN_IN_STRUCT";
<struct>[}] { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
<struct>[\n] /* nothing */
<struct>[^{}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
"class" return 'CLASS';
//"click" return 'CLICK';
//"click" return 'CLICK';
"callback" return 'CALLBACK';
"link" return 'LINK';
"<<" return 'ANNOTATION_START';
@@ -126,11 +132,39 @@
%left '^'
%start mermaidDoc
%start start
%% /* language grammar */
mermaidDoc: graphConfig;
start
: mermaidDoc
| directive start
;
mermaidDoc
: graphConfig
;
directive
: openDirective typeDirective closeDirective NEWLINE
| openDirective typeDirective ':' argDirective closeDirective NEWLINE
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'class'); }
;
graphConfig
: CLASS_DIAGRAM NEWLINE statements EOF
@@ -156,6 +190,7 @@ statement
| methodStatement
| annotationStatement
| clickStatement
| directive
;
classStatement

View File

@@ -35,13 +35,14 @@ export const sanitizeText = (text, config) => {
if (
config.flowchart &&
(config.flowchart.htmlLabels === false || config.flowchart.htmlLabels === 'false')
)
) {
htmlLabels = false;
}
if (htmlLabels) {
var level = config.securityLevel;
const level = config.securityLevel;
if (level == 'antiscript') {
if (level === 'antiscript') {
txt = removeScript(txt);
} else if (level !== 'loose') {
// eslint-disable-line

View File

@@ -2,6 +2,8 @@
*
*/
import { logger } from '../../logger';
import mermaidAPI from '../../mermaidAPI';
import configApi from '../../config';
let entities = {};
let relationships = [];
@@ -19,6 +21,10 @@ const Identification = {
IDENTIFYING: 'IDENTIFYING'
};
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
const addEntity = function(name) {
if (typeof entities[name] === 'undefined') {
entities[name] = name;
@@ -67,6 +73,8 @@ const clear = function() {
export default {
Cardinality,
Identification,
parseDirective,
getConfig: () => configApi.getConfig().er,
addEntity,
getEntities,
addRelationship,

View File

@@ -1,8 +1,17 @@
%lex
%options case-insensitive
%x open_directive type_directive arg_directive
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n]+ return 'NEWLINE';
\s+ /* skip whitespace */
[\s]+ return 'SPACE';
\"[^"]*\" return 'WORD';
@@ -29,21 +38,36 @@ o\{ return 'ZERO_OR_MORE';
start
: 'ER_DIAGRAM' document 'EOF' { /*console.log('finished parsing');*/ }
| directive start
;
document
: /* empty */
| document statement
;
: /* empty */ { $$ = [] }
| document line {$1.push($2);$$ = $1}
;
line
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NEWLINE { $$=[];}
| EOF { $$=[];}
;
directive
: openDirective typeDirective closeDirective 'NEWLINE'
| openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
;
statement
: entityName relSpec entityName ':' role
: directive
| entityName relSpec entityName ':' role
{
yy.addEntity($1);
yy.addEntity($3);
yy.addEntity($1);
yy.addEntity($3);
yy.addRelationship($1, $5, $3, $2);
/*console.log($1 + $2 + $3 + ':' + $5);*/
};
}
;
entityName
: 'ALPHANUM' { $$ = $1; /*console.log('Entity: ' + $1);*/ }
@@ -62,7 +86,7 @@ cardinality
| 'ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE; }
| 'ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE; }
| 'ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE; }
;
;
relType
: 'NON_IDENTIFYING' { $$ = yy.Identification.NON_IDENTIFYING; }
@@ -73,4 +97,21 @@ role
: 'WORD' { $$ = $1.replace(/"/g, ''); }
| 'ALPHANUM' { $$ = $1; }
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'er'); }
;
%%

View File

@@ -1,8 +1,8 @@
import { select } from 'd3';
import { logger } from '../../logger'; // eslint-disable-line
import utils from '../../utils';
import { getConfig } from '../../config';
import configApi, { getConfig } from '../../config';
import common from '../common/common';
import mermaidAPI from '../../mermaidAPI';
// const MERMAID_DOM_ID_PREFIX = 'mermaid-dom-id-';
const MERMAID_DOM_ID_PREFIX = '';
@@ -20,6 +20,10 @@ let direction;
// Functions to be run after graph rendering
let funs = [];
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
/**
* Function called by parser when a node definition has been found
* @param id
@@ -617,6 +621,8 @@ const destructLink = (_str, _startStr) => {
};
export default {
parseDirective,
getConfig: () => configApi.getConfig().flowchart,
addVertex,
addLink,
updateLinkInterpolate,

View File

@@ -9,8 +9,19 @@
%x string
%x dir
%x vertex
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
%%
\%\%[^\n]*\n* /* do nothing */
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
@@ -25,6 +36,7 @@
"flowchart" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';}
"subgraph" return 'subgraph';
"end"\b\s* return 'end';
<dir>(\r?\n)*\s*\n { this.popState(); return 'NODIR'; }
<dir>\s*"LR" { this.popState(); return 'DIR'; }
<dir>\s*"RL" { this.popState(); return 'DIR'; }
<dir>\s*"TB" { this.popState(); return 'DIR'; }
@@ -179,7 +191,7 @@
"{" return 'DIAMOND_START'
"}" return 'DIAMOND_STOP'
"\"" return 'QUOTE';
(\r|\n|\r\n)+ return 'NEWLINE';
(\r?\n)+ return 'NEWLINE';
\s return 'SPACE';
<<EOF>> return 'EOF';
@@ -189,11 +201,39 @@
%left '^'
%start mermaidDoc
%start start
%% /* language grammar */
mermaidDoc: graphConfig document;
start
: mermaidDoc
| directive start
;
directive
: openDirective typeDirective closeDirective separator
| openDirective typeDirective ':' argDirective closeDirective separator
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'flowchart'); }
;
mermaidDoc
: graphConfig document
;
document
: /* empty */
@@ -218,6 +258,8 @@ line
graphConfig
: SPACE graphConfig
| NEWLINE graphConfig
| GRAPH NODIR
{ yy.setDirection('TB');$$ = 'TB';}
| GRAPH DIR FirstStmtSeperator
{ yy.setDirection($2);$$ = $2;}
// | GRAPH SPACE TAGEND FirstStmtSeperator

View File

@@ -1,8 +1,9 @@
import moment from 'moment-mini';
import { sanitizeUrl } from '@braintree/sanitize-url';
import { logger } from '../../logger';
import { getConfig } from '../../config';
import configApi, { getConfig } from '../../config';
import utils from '../../utils';
import mermaidAPI from '../../mermaidAPI';
let dateFormat = '';
let axisFormat = '';
@@ -19,6 +20,10 @@ let inclusiveEndDates = false;
// The serial order of the task in the script
let lastOrder = 0;
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
export const clear = function() {
sections = [];
tasks = [];
@@ -582,6 +587,8 @@ export const bindFunctions = function(element) {
};
export default {
parseDirective,
getConfig: () => configApi.getConfig().gantt,
clear,
setDateFormat,
getDateFormat,

View File

@@ -11,7 +11,19 @@
%x href
%x callbackname
%x callbackargs
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)*[^\n]* /* skip comments */
[^\}]\%\%*[^\n]* /* skip comments */
\%\%*[^\n]*[\n]* /* do nothing */
[\n]+ return 'NL';
\s+ /* skip whitespace */
@@ -77,7 +89,8 @@ that id.
%% /* language grammar */
start
: gantt document 'EOF' { return $2; }
: directive start
| gantt document 'EOF' { return $2; }
;
document
@@ -102,8 +115,14 @@ statement
| section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| clickStatement
| taskTxt taskData {yy.addTask($1,$2);$$='task';}
| directive
;
directive
: openDirective typeDirective closeDirective 'NL'
| openDirective typeDirective ':' argDirective closeDirective 'NL'
;
/*
click allows any combination of href and call.
*/
@@ -131,4 +150,22 @@ clickStatementDebug
| click href callbackname callbackargs {$$=$1 + ' ' + $2 + ' ' + $3 + ' ' + $4;}
| click href {$$=$1 + ' ' + $2;}
;%%
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'gantt'); }
;
%%

View File

@@ -4,26 +4,34 @@
* MIT license.
*/
%lex
%x string
%options case-insensitive
%{
// Pre-lexer code can go here
%}
%x string
%x title
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
%%
\%\%[^\n]* /* do nothing */
\s+ /* skip whitespace */
"pie" return 'pie' ;
[\s\n\r]+ return 'NL' ;
[\s]+ return 'space';
"title"\s[^#\n;]+ return 'title';
["] {/*console.log('begin str');*/this.begin("string");}
<string>["] {/*console.log('pop-state');*/this.popState();}
<string>[^"]* {/*console.log('ending string')*/return "STR";}
":"[\s]*[\d]+(?:\.[\d]+)? return "VALUE";
<<EOF>> return 'EOF' ;
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */{ console.log('Crap after close'); }
[\n\r]+ return 'NEWLINE';
\%\%[^\n]* /* do nothing */
[\s]+ /* ignore */
title { this.begin("title");return 'title'; }
<title>(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; }
["] { this.begin("string"); }
<string>["] { this.popState(); }
<string>[^"]* { return "txt"; }
"pie" return 'PIE';
":"[\s]*[\d]+(?:\.[\d]+)? return "value";
<<EOF>> return 'EOF';
/lex
@@ -33,8 +41,9 @@
%% /* language grammar */
start
// %{ : info document 'EOF' { return yy; } }
: pie document 'EOF'
: eol start
| directive start
| PIE document
;
document
@@ -43,15 +52,41 @@ document
;
line
: statement { }
| 'NL'
: statement eol { $$ = $1 }
;
statement
: STR VALUE {
/*console.log('str:'+$1+' value: '+$2)*/
yy.addSection($1,yy.cleanupValue($2)); }
| title {yy.setTitle($1.substr(6));$$=$1.substr(6);}
:
| txt value { yy.addSection($1,yy.cleanupValue($2)); }
| title title_value { $$=$2.trim();yy.setTitle($$); }
| directive
;
directive
: openDirective typeDirective closeDirective
| openDirective typeDirective ':' argDirective closeDirective
;
eol
: NEWLINE
| ';'
| EOF
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); }
;
%%

View File

@@ -12,16 +12,67 @@ describe('when parsing pie', function() {
pie.parser.yy = pieDb;
pie.parser.yy.clear();
});
it('should handle very simple pie', function() {
const res = pie.parser.parse(`pie
"ash" : 100
`);
const sections = pieDb.getSections();
console.log('sections: ', sections);
const section1 = sections['ash'];
expect(section1).toBe(100);
});
it('should handle simple pie', function() {
const res = pie.parser.parse('pie \n"ash" : 60\n"bat" : 40\n');
const res = pie.parser.parse(`pie
"ash" : 60
"bat" : 40
`);
const sections = pieDb.getSections();
console.log('sections: ', sections);
const section1 = sections['ash'];
expect(section1).toBe(60);
});
it('should handle simple pie with comments', function() {
const res = pie.parser.parse(`pie
%% comments
"ash" : 60
"bat" : 40
`);
const sections = pieDb.getSections();
console.log('sections: ', sections);
const section1 = sections['ash'];
expect(section1).toBe(60);
});
it('should handle simple pie with a directive', function() {
const res = pie.parser.parse(`%%{init: {'logLevel':0}}%%
pie
"ash" : 60
"bat" : 40
`);
const sections = pieDb.getSections();
console.log('sections: ', sections);
const section1 = sections['ash'];
expect(section1).toBe(60);
});
it('should handle simple pie with a title', function() {
const res = pie.parser.parse(`pie title a 60/40 pie
"ash" : 60
"bat" : 40
`);
const sections = pieDb.getSections();
const title = pieDb.getTitle();
console.log('sections: ', sections);
const section1 = sections['ash'];
expect(section1).toBe(60);
expect(title).toBe('a 60/40 pie');
});
it('should handle simple pie with positive decimal', function() {
const res = pie.parser.parse('pie \n"ash" : 60.67\n"bat" : 40\n');
const res = pie.parser.parse(`pie
"ash" : 60.67
"bat" : 40
`);
const sections = pieDb.getSections();
console.log('sections: ', sections);
const section1 = sections['ash'];
@@ -30,7 +81,10 @@ describe('when parsing pie', function() {
it('should handle simple pie with negative decimal', function() {
expect(() => {
pie.parser.parse('pie \n"ash" : 60.67\n"bat" : 40..12\n');
pie.parser.parse(`pie
"ash" : 60.67
"bat" : 40..12
`);
}).toThrowError();
});
});

View File

@@ -2,10 +2,16 @@
*
*/
import { logger } from '../../logger';
import mermaidAPI from '../../mermaidAPI';
import configApi from '../../config';
let sections = {};
let title = '';
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
const addSection = function(id, value) {
if (typeof sections[id] === 'undefined') {
sections[id] = value;
@@ -39,6 +45,8 @@ const clear = function() {
// }
export default {
parseDirective,
getConfig: () => configApi.getConfig().pie,
addSection,
getSections,
cleanupValue,

View File

@@ -153,7 +153,7 @@ export const draw = (txt, id) => {
});
} catch (e) {
logger.error('Error while rendering info diagram');
logger.error(e.message);
logger.error(e);
}
};

View File

@@ -13,34 +13,29 @@
%options case-insensitive
// Special states for recognizing aliases
%x ID
%x ALIAS
// A special state for grabbing text up to the first comment/newline
%x ID ALIAS LINE
// Directive states
%x OPEN_DIRECTIVE
%x TYPE_DIRECTIVE
%x ARG_DIRECTIVE
// A special state for grabbing text up to the first comment/newline
%x LINE
%x open_directive type_directive arg_directive
%%
\%\%\{ { this.begin('OPEN_DIRECTIVE'); return 'open_directive'; }
<OPEN_DIRECTIVE>((?:(?!\}\%\%)[^:.])*) { this.begin('TYPE_DIRECTIVE'); return 'type_directive'; }
<TYPE_DIRECTIVE>":" { this.popState(); this.begin('ARG_DIRECTIVE'); return ':'; }
<TYPE_DIRECTIVE,ARG_DIRECTIVE>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<ARG_DIRECTIVE>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
[\n]+ return 'NL';
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
[\n]+ return 'NEWLINE';
\s+ /* skip all whitespace */
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,ALIAS,LINE,ARG_DIRECTIVE,TYPE_DIRECTIVE,OPEN_DIRECTIVE>\#[^\n]* /* skip comments */
<INITIAL,ID,ALIAS,LINE,arg_directive,type_directive,open_directive>\#[^\n]* /* skip comments */
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
"participant" { this.begin('ID'); return 'participant'; }
<ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NL'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
"loop" { this.begin('LINE'); return 'loop'; }
"rect" { this.begin('LINE'); return 'rect'; }
"opt" { this.begin('LINE'); return 'opt'; }
@@ -58,10 +53,10 @@
"deactivate" { this.begin('ID'); return 'deactivate'; }
"title" return 'title';
"sequenceDiagram" return 'SD';
"autonumber" return 'autonumber';
"autonumber" return 'autonumber';
"," return ',';
";" return 'NL';
[^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; }
";" return 'NEWLINE';
[^\+\->:\n,;]+((?!(\-x|\-\-x))[\-]*[^\+\->:\n,;]+)* { yytext = yytext.trim(); return 'ACTOR'; }
"->>" return 'SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
"->" return 'SOLID_OPEN_ARROW';
@@ -71,7 +66,7 @@
":"(?:(?:no)?wrap:)?[^#\n;]+ return 'TXT';
"+" return '+';
"-" return '-';
<<EOF>> return 'NL';
<<EOF>> return 'NEWLINE';
. return 'INVALID';
/lex
@@ -84,7 +79,7 @@
start
: SPACE start
| NL start
| NEWLINE start
| directive start
| SD document { yy.apply($2);return $2; }
;
@@ -97,23 +92,23 @@ document
line
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NL { $$=[]; }
| NEWLINE { $$=[]; }
;
directive
: openDirective typeDirective closeDirective 'NL'
| openDirective typeDirective ':' argDirective closeDirective 'NL'
: openDirective typeDirective closeDirective 'NEWLINE'
| openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
;
statement
: 'participant' actor 'AS' restOfLine 'NL' {$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NL' {$$=$2;}
| signal 'NL'
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$$=$2;}
| signal 'NEWLINE'
| autonumber {yy.enableSequenceNumbers()}
| 'activate' actor 'NL' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};}
| 'deactivate' actor 'NL' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};}
| note_statement 'NL'
| title text2 'NL' {$$=[{type:'setTitle', text:$2}]}
| 'activate' actor 'NEWLINE' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};}
| 'deactivate' actor 'NEWLINE' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};}
| note_statement 'NEWLINE'
| title text2 'NEWLINE' {$$=[{type:'setTitle', text:$2}]}
| 'loop' restOfLine document end
{
$3.unshift({type: 'loopStart', loopText:yy.parseMessage($2), signalType: yy.LINETYPE.LOOP_START});

View File

@@ -13,7 +13,7 @@ let sequenceNumbersEnabled = false;
let wrapEnabled = false;
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(statement, context, type);
mermaidAPI.parseDirective(this, statement, context, type);
};
export const addActor = function(id, name, description) {
@@ -140,12 +140,12 @@ export const parseMessage = function(str) {
text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(),
wrap:
_str.match(/^[:]?(?:no)?wrap:/) === null
? common.hasBreaks(_str) || autoWrap()
? common.hasBreaks(_str) || undefined
: _str.match(/^[:]?wrap:/) !== null
? true
: _str.match(/^[:]?nowrap:/) !== null
? false
: autoWrap()
: undefined
};
logger.debug('parseMessage:', message);
return message;

View File

@@ -93,6 +93,23 @@ Bob-->Alice: I am good thanks!`;
expect(messages[0].from).toBe('Alice');
expect(messages[1].from).toBe('Bob');
});
it('it should handle dashes in actor names', function() {
const str = `
sequenceDiagram
Alice-in-Wonderland->Bob:Hello Bob, how are - you?
Bob-->Alice-in-Wonderland:I am good thanks!`;
mermaidAPI.parse(str);
const actors = parser.yy.getActors();
expect(actors["Alice-in-Wonderland"].description).toBe('Alice-in-Wonderland');
actors.Bob.description = 'Bob';
const messages = parser.yy.getMessages();
expect(messages.length).toBe(2);
expect(messages[0].from).toBe('Alice-in-Wonderland');
expect(messages[1].from).toBe('Bob');
});
it('it should alias participants', function() {
const str = `
sequenceDiagram
@@ -932,7 +949,6 @@ describe('when rendering a sequenceDiagram', function() {
parser.yy = sequenceDb;
parser.yy.clear();
conf = parser.yy.getConfig();
renderer.bounds.init();
});
['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) {
it(`

View File

@@ -26,16 +26,27 @@
%x FLOATING_NOTE
%x FLOATING_NOTE_ID
%x struct
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
// A special state for grabbing text up to the first comment/newline
%x LINE
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */{ console.log('Crap after close'); }
[\n]+ return 'NL';
\s+ /* skip all whitespace */
<ID,STATE,struct,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,STATE,struct,LINE>\#[^\n]* /* skip comments */
[\s]+ /* skip all whitespace */
<ID,STATE,struct,LINE,open_directive,type_directive,arg_directive,close_directive>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,STATE,struct,LINE,open_directive,type_directive,arg_directive,close_directive>\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
@@ -93,6 +104,7 @@
start
: SPACE start
| NL start
| directive start
| SD document { /*console.warn('Root document', $2);*/ yy.setRootDoc($2);return $2; }
;
@@ -165,6 +177,17 @@ statement
$$={ stmt: 'state', id: $3.trim(), note:{position: $2.trim(), text: $4.trim()}};
}
| note NOTE_TEXT AS ID
| directive
;
directive
: openDirective typeDirective closeDirective
| openDirective typeDirective ':' argDirective closeDirective
;
eol
: NL
| ';'
;
idStatement
@@ -177,4 +200,20 @@ notePosition
| right_of
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'state'); }
;
%%

View File

@@ -1,9 +1,16 @@
import { logger } from '../../logger';
import { generateId } from '../../utils';
import mermaidAPI from '../../mermaidAPI';
import configApi from '../../config';
const clone = o => JSON.parse(JSON.stringify(o));
let rootDoc = [];
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
const setRootDoc = o => {
logger.info('Setting root doc', o);
// rootDoc = { id: 'root', doc: o };
@@ -235,6 +242,8 @@ export const relationType = {
const trimColon = str => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
export default {
parseDirective,
getConfig: () => configApi.getConfig().state,
addState,
clear,
getState,

View File

@@ -26,6 +26,16 @@ describe('state diagram, ', function() {
parser.parse(str);
});
it('simple with directive', function() {
const str = `%%{init: {'logLevel': 0 }}%%
stateDiagram\n
State1 : this is another string
[*] --> State1
State1 --> [*]
`;
parser.parse(str);
});
it('should handle relation definitions', function() {
const str = `stateDiagram\n
[*] --> State1
@@ -337,6 +347,9 @@ describe('state diagram, ', function() {
parser.parse(str);
});
});
describe('when parsing an ignored info graph it', function() {
xit('should handle if statements', function() {
const str = `stateDiagram\n
[*] --> "Order Submitted"

View File

@@ -211,11 +211,11 @@ export const draw = function(text, id) {
parser.yy = stateDb;
// Parse the graph definition
try {
parser.parse(text);
} catch (err) {
logger.debug('Parsing failed');
}
// try {
parser.parse(text);
// } catch (err) {
// logger.error('Parsing failed', err);
// }
// Fetch the default direction, use TD if none was found
let dir = stateDb.getDirection();

View File

@@ -1,3 +1,6 @@
import mermaidAPI from '../../mermaidAPI';
import configApi from '../../config';
let title = '';
let currentSection = '';
@@ -5,6 +8,10 @@ const sections = [];
const tasks = [];
const rawTasks = [];
export const parseDirective = function(statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
export const clear = function() {
sections.length = 0;
tasks.length = 0;
@@ -111,6 +118,8 @@ const getActors = function() {
};
export default {
parseDirective,
getConfig: () => configApi.getConfig().journey,
clear,
setTitle,
getTitle,

View File

@@ -70,7 +70,8 @@ function drawActorLegend(diagram) {
x: 40,
y: yPos + 7,
fill: '#666',
text: person
text: person,
textMargin: conf.boxTextMargin | 5
};
svgDraw.drawText(diagram, labelData);

View File

@@ -5,12 +5,23 @@
*/
%lex
%options case-insensitive
// Directive states
%x open_directive type_directive arg_directive
%%
[\n]+ return 'NL';
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n]+ return 'NEWLINE';
\s+ /* skip whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"journey" return 'journey';
"title"\s[^#\n;]+ return 'title';
@@ -31,6 +42,7 @@
start
: journey document 'EOF' { return $2; }
| directive start
;
document
@@ -41,12 +53,36 @@ document
line
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NL { $$=[];}
| NEWLINE { $$=[];}
| EOF { $$=[];}
;
directive
: openDirective typeDirective closeDirective 'NEWLINE'
| openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
;
statement
: title {yy.setTitle($1.substr(6));$$=$1.substr(6);}
| section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| taskName taskData {yy.addTask($1, $2);$$='task';}
| directive
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'journey'); }
;
%%