1/** js sequence diagrams 2.0.1 2 * https://bramp.github.io/js-sequence-diagrams/ 3 * (c) 2012-2017 Andrew Brampton (bramp.net) 4 * @license Simplified BSD license. 5 */ 6(function() { 7'use strict'; 8/*global Diagram */ 9 10// The following are included by preprocessor */ 11/** js sequence diagrams 12 * https://bramp.github.io/js-sequence-diagrams/ 13 * (c) 2012-2017 Andrew Brampton (bramp.net) 14 * Simplified BSD license. 15 */ 16/*global grammar _ */ 17 18function Diagram() { 19 this.title = undefined; 20 this.actors = []; 21 this.signals = []; 22} 23/* 24 * Return an existing actor with this alias, or creates a new one with alias and name. 25 */ 26Diagram.prototype.getActor = function(alias, name) { 27 alias = alias.trim(); 28 29 var i; 30 var actors = this.actors; 31 for (i in actors) { 32 if (actors[i].alias == alias) { 33 return actors[i]; 34 } 35 } 36 i = actors.push(new Diagram.Actor(alias, (name || alias), actors.length)); 37 return actors[ i - 1 ]; 38}; 39 40/* 41 * Parses the input as either a alias, or a "name as alias", and returns the corresponding actor. 42 */ 43Diagram.prototype.getActorWithAlias = function(input) { 44 input = input.trim(); 45 46 // We are lazy and do some of the parsing in javascript :(. TODO move into the .jison file. 47 var s = /([\s\S]+) as (\S+)$/im.exec(input); 48 var alias; 49 var name; 50 if (s) { 51 name = s[1].trim(); 52 alias = s[2].trim(); 53 } else { 54 name = alias = input; 55 } 56 return this.getActor(alias, name); 57}; 58 59Diagram.prototype.setTitle = function(title) { 60 this.title = title; 61}; 62 63Diagram.prototype.addSignal = function(signal) { 64 this.signals.push(signal); 65}; 66 67Diagram.Actor = function(alias, name, index) { 68 this.alias = alias; 69 this.name = name; 70 this.index = index; 71}; 72 73Diagram.Signal = function(actorA, signaltype, actorB, message) { 74 this.type = 'Signal'; 75 this.actorA = actorA; 76 this.actorB = actorB; 77 this.linetype = signaltype & 3; 78 this.arrowtype = (signaltype >> 2) & 3; 79 this.message = message; 80}; 81 82Diagram.Signal.prototype.isSelf = function() { 83 return this.actorA.index == this.actorB.index; 84}; 85 86Diagram.Note = function(actor, placement, message) { 87 this.type = 'Note'; 88 this.actor = actor; 89 this.placement = placement; 90 this.message = message; 91 92 if (this.hasManyActors() && actor[0] == actor[1]) { 93 throw new Error('Note should be over two different actors'); 94 } 95}; 96 97Diagram.Note.prototype.hasManyActors = function() { 98 return _.isArray(this.actor); 99}; 100 101Diagram.unescape = function(s) { 102 // Turn "\\n" into "\n" 103 return s.trim().replace(/^"(.*)"$/m, '$1').replace(/\\n/gm, '\n'); 104}; 105 106Diagram.LINETYPE = { 107 SOLID: 0, 108 DOTTED: 1 109}; 110 111Diagram.ARROWTYPE = { 112 FILLED: 0, 113 OPEN: 1 114}; 115 116Diagram.PLACEMENT = { 117 LEFTOF: 0, 118 RIGHTOF: 1, 119 OVER: 2 120}; 121 122// Some older browsers don't have getPrototypeOf, thus we polyfill it 123// https://github.com/bramp/js-sequence-diagrams/issues/57 124// https://github.com/zaach/jison/issues/194 125// Taken from http://ejohn.org/blog/objectgetprototypeof/ 126if (typeof Object.getPrototypeOf !== 'function') { 127 /* jshint -W103 */ 128 if (typeof 'test'.__proto__ === 'object') { 129 Object.getPrototypeOf = function(object) { 130 return object.__proto__; 131 }; 132 } else { 133 Object.getPrototypeOf = function(object) { 134 // May break if the constructor has been tampered with 135 return object.constructor.prototype; 136 }; 137 } 138 /* jshint +W103 */ 139} 140 141/** The following is included by preprocessor */ 142/* parser generated by jison 0.4.15 */ 143/* 144 Returns a Parser object of the following structure: 145 146 Parser: { 147 yy: {} 148 } 149 150 Parser.prototype: { 151 yy: {}, 152 trace: function(), 153 symbols_: {associative list: name ==> number}, 154 terminals_: {associative list: number ==> name}, 155 productions_: [...], 156 performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), 157 table: [...], 158 defaultActions: {...}, 159 parseError: function(str, hash), 160 parse: function(input), 161 162 lexer: { 163 EOF: 1, 164 parseError: function(str, hash), 165 setInput: function(input), 166 input: function(), 167 unput: function(str), 168 more: function(), 169 less: function(n), 170 pastInput: function(), 171 upcomingInput: function(), 172 showPosition: function(), 173 test_match: function(regex_match_array, rule_index), 174 next: function(), 175 lex: function(), 176 begin: function(condition), 177 popState: function(), 178 _currentRules: function(), 179 topState: function(), 180 pushState: function(condition), 181 182 options: { 183 ranges: boolean (optional: true ==> token location info will include a .range[] member) 184 flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) 185 backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) 186 }, 187 188 performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), 189 rules: [...], 190 conditions: {associative list: name ==> set}, 191 } 192 } 193 194 195 token location info (@$, _$, etc.): { 196 first_line: n, 197 last_line: n, 198 first_column: n, 199 last_column: n, 200 range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) 201 } 202 203 204 the parseError function receives a 'hash' object with these members for lexer and parser errors: { 205 text: (matched text) 206 token: (the produced terminal token, if any) 207 line: (yylineno) 208 } 209 while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { 210 loc: (yylloc) 211 expected: (string describing the set of expected tokens) 212 recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) 213 } 214*/ 215var parser = function() { 216 function Parser() { 217 this.yy = {}; 218 } 219 var o = function(k, v, o, l) { 220 for (o = o || {}, l = k.length; l--; o[k[l]] = v) ; 221 return o; 222 }, $V0 = [ 5, 8, 9, 13, 15, 24 ], $V1 = [ 1, 13 ], $V2 = [ 1, 17 ], $V3 = [ 24, 29, 30 ], parser = { 223 trace: function() {}, 224 yy: {}, 225 symbols_: { 226 error: 2, 227 start: 3, 228 document: 4, 229 EOF: 5, 230 line: 6, 231 statement: 7, 232 NL: 8, 233 participant: 9, 234 actor_alias: 10, 235 signal: 11, 236 note_statement: 12, 237 title: 13, 238 message: 14, 239 note: 15, 240 placement: 16, 241 actor: 17, 242 over: 18, 243 actor_pair: 19, 244 ",": 20, 245 left_of: 21, 246 right_of: 22, 247 signaltype: 23, 248 ACTOR: 24, 249 linetype: 25, 250 arrowtype: 26, 251 LINE: 27, 252 DOTLINE: 28, 253 ARROW: 29, 254 OPENARROW: 30, 255 MESSAGE: 31, 256 $accept: 0, 257 $end: 1 258 }, 259 terminals_: { 260 2: "error", 261 5: "EOF", 262 8: "NL", 263 9: "participant", 264 13: "title", 265 15: "note", 266 18: "over", 267 20: ",", 268 21: "left_of", 269 22: "right_of", 270 24: "ACTOR", 271 27: "LINE", 272 28: "DOTLINE", 273 29: "ARROW", 274 30: "OPENARROW", 275 31: "MESSAGE" 276 }, 277 productions_: [ 0, [ 3, 2 ], [ 4, 0 ], [ 4, 2 ], [ 6, 1 ], [ 6, 1 ], [ 7, 2 ], [ 7, 1 ], [ 7, 1 ], [ 7, 2 ], [ 12, 4 ], [ 12, 4 ], [ 19, 1 ], [ 19, 3 ], [ 16, 1 ], [ 16, 1 ], [ 11, 4 ], [ 17, 1 ], [ 10, 1 ], [ 23, 2 ], [ 23, 1 ], [ 25, 1 ], [ 25, 1 ], [ 26, 1 ], [ 26, 1 ], [ 14, 1 ] ], 278 performAction: function(yytext, yyleng, yylineno, yy, yystate, $$, _$) { 279 /* this == yyval */ 280 var $0 = $$.length - 1; 281 switch (yystate) { 282 case 1: 283 return yy.parser.yy; 284 285 case 4: 286 break; 287 288 case 6: 289 $$[$0]; 290 break; 291 292 case 7: 293 case 8: 294 yy.parser.yy.addSignal($$[$0]); 295 break; 296 297 case 9: 298 yy.parser.yy.setTitle($$[$0]); 299 break; 300 301 case 10: 302 this.$ = new Diagram.Note($$[$0 - 1], $$[$0 - 2], $$[$0]); 303 break; 304 305 case 11: 306 this.$ = new Diagram.Note($$[$0 - 1], Diagram.PLACEMENT.OVER, $$[$0]); 307 break; 308 309 case 12: 310 case 20: 311 this.$ = $$[$0]; 312 break; 313 314 case 13: 315 this.$ = [ $$[$0 - 2], $$[$0] ]; 316 break; 317 318 case 14: 319 this.$ = Diagram.PLACEMENT.LEFTOF; 320 break; 321 322 case 15: 323 this.$ = Diagram.PLACEMENT.RIGHTOF; 324 break; 325 326 case 16: 327 this.$ = new Diagram.Signal($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0]); 328 break; 329 330 case 17: 331 this.$ = yy.parser.yy.getActor(Diagram.unescape($$[$0])); 332 break; 333 334 case 18: 335 this.$ = yy.parser.yy.getActorWithAlias(Diagram.unescape($$[$0])); 336 break; 337 338 case 19: 339 this.$ = $$[$0 - 1] | $$[$0] << 2; 340 break; 341 342 case 21: 343 this.$ = Diagram.LINETYPE.SOLID; 344 break; 345 346 case 22: 347 this.$ = Diagram.LINETYPE.DOTTED; 348 break; 349 350 case 23: 351 this.$ = Diagram.ARROWTYPE.FILLED; 352 break; 353 354 case 24: 355 this.$ = Diagram.ARROWTYPE.OPEN; 356 break; 357 358 case 25: 359 this.$ = Diagram.unescape($$[$0].substring(1)); 360 } 361 }, 362 table: [ o($V0, [ 2, 2 ], { 363 3: 1, 364 4: 2 365 }), { 366 1: [ 3 ] 367 }, { 368 5: [ 1, 3 ], 369 6: 4, 370 7: 5, 371 8: [ 1, 6 ], 372 9: [ 1, 7 ], 373 11: 8, 374 12: 9, 375 13: [ 1, 10 ], 376 15: [ 1, 12 ], 377 17: 11, 378 24: $V1 379 }, { 380 1: [ 2, 1 ] 381 }, o($V0, [ 2, 3 ]), o($V0, [ 2, 4 ]), o($V0, [ 2, 5 ]), { 382 10: 14, 383 24: [ 1, 15 ] 384 }, o($V0, [ 2, 7 ]), o($V0, [ 2, 8 ]), { 385 14: 16, 386 31: $V2 387 }, { 388 23: 18, 389 25: 19, 390 27: [ 1, 20 ], 391 28: [ 1, 21 ] 392 }, { 393 16: 22, 394 18: [ 1, 23 ], 395 21: [ 1, 24 ], 396 22: [ 1, 25 ] 397 }, o([ 20, 27, 28, 31 ], [ 2, 17 ]), o($V0, [ 2, 6 ]), o($V0, [ 2, 18 ]), o($V0, [ 2, 9 ]), o($V0, [ 2, 25 ]), { 398 17: 26, 399 24: $V1 400 }, { 401 24: [ 2, 20 ], 402 26: 27, 403 29: [ 1, 28 ], 404 30: [ 1, 29 ] 405 }, o($V3, [ 2, 21 ]), o($V3, [ 2, 22 ]), { 406 17: 30, 407 24: $V1 408 }, { 409 17: 32, 410 19: 31, 411 24: $V1 412 }, { 413 24: [ 2, 14 ] 414 }, { 415 24: [ 2, 15 ] 416 }, { 417 14: 33, 418 31: $V2 419 }, { 420 24: [ 2, 19 ] 421 }, { 422 24: [ 2, 23 ] 423 }, { 424 24: [ 2, 24 ] 425 }, { 426 14: 34, 427 31: $V2 428 }, { 429 14: 35, 430 31: $V2 431 }, { 432 20: [ 1, 36 ], 433 31: [ 2, 12 ] 434 }, o($V0, [ 2, 16 ]), o($V0, [ 2, 10 ]), o($V0, [ 2, 11 ]), { 435 17: 37, 436 24: $V1 437 }, { 438 31: [ 2, 13 ] 439 } ], 440 defaultActions: { 441 3: [ 2, 1 ], 442 24: [ 2, 14 ], 443 25: [ 2, 15 ], 444 27: [ 2, 19 ], 445 28: [ 2, 23 ], 446 29: [ 2, 24 ], 447 37: [ 2, 13 ] 448 }, 449 parseError: function(str, hash) { 450 if (!hash.recoverable) throw new Error(str); 451 this.trace(str); 452 }, 453 parse: function(input) { 454 function lex() { 455 var token; 456 return token = lexer.lex() || EOF, "number" != typeof token && (token = self.symbols_[token] || token), 457 token; 458 } 459 var self = this, stack = [ 0 ], vstack = [ null ], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1, args = lstack.slice.call(arguments, 1), lexer = Object.create(this.lexer), sharedState = { 460 yy: {} 461 }; 462 for (var k in this.yy) Object.prototype.hasOwnProperty.call(this.yy, k) && (sharedState.yy[k] = this.yy[k]); 463 lexer.setInput(input, sharedState.yy), sharedState.yy.lexer = lexer, sharedState.yy.parser = this, 464 "undefined" == typeof lexer.yylloc && (lexer.yylloc = {}); 465 var yyloc = lexer.yylloc; 466 lstack.push(yyloc); 467 var ranges = lexer.options && lexer.options.ranges; 468 "function" == typeof sharedState.yy.parseError ? this.parseError = sharedState.yy.parseError : this.parseError = Object.getPrototypeOf(this).parseError; 469 for (var symbol, preErrorSymbol, state, action, r, p, len, newState, expected, yyval = {}; ;) { 470 if (state = stack[stack.length - 1], this.defaultActions[state] ? action = this.defaultActions[state] : (null !== symbol && "undefined" != typeof symbol || (symbol = lex()), 471 action = table[state] && table[state][symbol]), "undefined" == typeof action || !action.length || !action[0]) { 472 var errStr = ""; 473 expected = []; 474 for (p in table[state]) this.terminals_[p] && p > TERROR && expected.push("'" + this.terminals_[p] + "'"); 475 errStr = lexer.showPosition ? "Parse error on line " + (yylineno + 1) + ":\n" + lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'" : "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == EOF ? "end of input" : "'" + (this.terminals_[symbol] || symbol) + "'"), 476 this.parseError(errStr, { 477 text: lexer.match, 478 token: this.terminals_[symbol] || symbol, 479 line: lexer.yylineno, 480 loc: yyloc, 481 expected: expected 482 }); 483 } 484 if (action[0] instanceof Array && action.length > 1) throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); 485 switch (action[0]) { 486 case 1: 487 stack.push(symbol), vstack.push(lexer.yytext), lstack.push(lexer.yylloc), stack.push(action[1]), 488 symbol = null, preErrorSymbol ? (symbol = preErrorSymbol, preErrorSymbol = null) : (yyleng = lexer.yyleng, 489 yytext = lexer.yytext, yylineno = lexer.yylineno, yyloc = lexer.yylloc, recovering > 0 && recovering--); 490 break; 491 492 case 2: 493 if (len = this.productions_[action[1]][1], yyval.$ = vstack[vstack.length - len], 494 yyval._$ = { 495 first_line: lstack[lstack.length - (len || 1)].first_line, 496 last_line: lstack[lstack.length - 1].last_line, 497 first_column: lstack[lstack.length - (len || 1)].first_column, 498 last_column: lstack[lstack.length - 1].last_column 499 }, ranges && (yyval._$.range = [ lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1] ]), 500 r = this.performAction.apply(yyval, [ yytext, yyleng, yylineno, sharedState.yy, action[1], vstack, lstack ].concat(args)), 501 "undefined" != typeof r) return r; 502 len && (stack = stack.slice(0, -1 * len * 2), vstack = vstack.slice(0, -1 * len), 503 lstack = lstack.slice(0, -1 * len)), stack.push(this.productions_[action[1]][0]), 504 vstack.push(yyval.$), lstack.push(yyval._$), newState = table[stack[stack.length - 2]][stack[stack.length - 1]], 505 stack.push(newState); 506 break; 507 508 case 3: 509 return !0; 510 } 511 } 512 return !0; 513 } 514 }, lexer = function() { 515 var lexer = { 516 EOF: 1, 517 parseError: function(str, hash) { 518 if (!this.yy.parser) throw new Error(str); 519 this.yy.parser.parseError(str, hash); 520 }, 521 // resets the lexer, sets new input 522 setInput: function(input, yy) { 523 return this.yy = yy || this.yy || {}, this._input = input, this._more = this._backtrack = this.done = !1, 524 this.yylineno = this.yyleng = 0, this.yytext = this.matched = this.match = "", this.conditionStack = [ "INITIAL" ], 525 this.yylloc = { 526 first_line: 1, 527 first_column: 0, 528 last_line: 1, 529 last_column: 0 530 }, this.options.ranges && (this.yylloc.range = [ 0, 0 ]), this.offset = 0, this; 531 }, 532 // consumes and returns one char from the input 533 input: function() { 534 var ch = this._input[0]; 535 this.yytext += ch, this.yyleng++, this.offset++, this.match += ch, this.matched += ch; 536 var lines = ch.match(/(?:\r\n?|\n).*/g); 537 return lines ? (this.yylineno++, this.yylloc.last_line++) : this.yylloc.last_column++, 538 this.options.ranges && this.yylloc.range[1]++, this._input = this._input.slice(1), 539 ch; 540 }, 541 // unshifts one char (or a string) into the input 542 unput: function(ch) { 543 var len = ch.length, lines = ch.split(/(?:\r\n?|\n)/g); 544 this._input = ch + this._input, this.yytext = this.yytext.substr(0, this.yytext.length - len), 545 //this.yyleng -= len; 546 this.offset -= len; 547 var oldLines = this.match.split(/(?:\r\n?|\n)/g); 548 this.match = this.match.substr(0, this.match.length - 1), this.matched = this.matched.substr(0, this.matched.length - 1), 549 lines.length - 1 && (this.yylineno -= lines.length - 1); 550 var r = this.yylloc.range; 551 return this.yylloc = { 552 first_line: this.yylloc.first_line, 553 last_line: this.yylineno + 1, 554 first_column: this.yylloc.first_column, 555 last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len 556 }, this.options.ranges && (this.yylloc.range = [ r[0], r[0] + this.yyleng - len ]), 557 this.yyleng = this.yytext.length, this; 558 }, 559 // When called from action, caches matched text and appends it on next action 560 more: function() { 561 return this._more = !0, this; 562 }, 563 // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. 564 reject: function() { 565 return this.options.backtrack_lexer ? (this._backtrack = !0, this) : this.parseError("Lexical error on line " + (this.yylineno + 1) + ". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n" + this.showPosition(), { 566 text: "", 567 token: null, 568 line: this.yylineno 569 }); 570 }, 571 // retain first n characters of the match 572 less: function(n) { 573 this.unput(this.match.slice(n)); 574 }, 575 // displays already matched input, i.e. for error messages 576 pastInput: function() { 577 var past = this.matched.substr(0, this.matched.length - this.match.length); 578 return (past.length > 20 ? "..." : "") + past.substr(-20).replace(/\n/g, ""); 579 }, 580 // displays upcoming input, i.e. for error messages 581 upcomingInput: function() { 582 var next = this.match; 583 return next.length < 20 && (next += this._input.substr(0, 20 - next.length)), (next.substr(0, 20) + (next.length > 20 ? "..." : "")).replace(/\n/g, ""); 584 }, 585 // displays the character position where the lexing error occurred, i.e. for error messages 586 showPosition: function() { 587 var pre = this.pastInput(), c = new Array(pre.length + 1).join("-"); 588 return pre + this.upcomingInput() + "\n" + c + "^"; 589 }, 590 // test the lexed token: return FALSE when not a match, otherwise return token 591 test_match: function(match, indexed_rule) { 592 var token, lines, backup; 593 if (this.options.backtrack_lexer && (// save context 594 backup = { 595 yylineno: this.yylineno, 596 yylloc: { 597 first_line: this.yylloc.first_line, 598 last_line: this.last_line, 599 first_column: this.yylloc.first_column, 600 last_column: this.yylloc.last_column 601 }, 602 yytext: this.yytext, 603 match: this.match, 604 matches: this.matches, 605 matched: this.matched, 606 yyleng: this.yyleng, 607 offset: this.offset, 608 _more: this._more, 609 _input: this._input, 610 yy: this.yy, 611 conditionStack: this.conditionStack.slice(0), 612 done: this.done 613 }, this.options.ranges && (backup.yylloc.range = this.yylloc.range.slice(0))), lines = match[0].match(/(?:\r\n?|\n).*/g), 614 lines && (this.yylineno += lines.length), this.yylloc = { 615 first_line: this.yylloc.last_line, 616 last_line: this.yylineno + 1, 617 first_column: this.yylloc.last_column, 618 last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length 619 }, this.yytext += match[0], this.match += match[0], this.matches = match, this.yyleng = this.yytext.length, 620 this.options.ranges && (this.yylloc.range = [ this.offset, this.offset += this.yyleng ]), 621 this._more = !1, this._backtrack = !1, this._input = this._input.slice(match[0].length), 622 this.matched += match[0], token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]), 623 this.done && this._input && (this.done = !1), token) return token; 624 if (this._backtrack) { 625 // recover context 626 for (var k in backup) this[k] = backup[k]; 627 return !1; 628 } 629 return !1; 630 }, 631 // return next match in input 632 next: function() { 633 if (this.done) return this.EOF; 634 this._input || (this.done = !0); 635 var token, match, tempMatch, index; 636 this._more || (this.yytext = "", this.match = ""); 637 for (var rules = this._currentRules(), i = 0; i < rules.length; i++) if (tempMatch = this._input.match(this.rules[rules[i]]), 638 tempMatch && (!match || tempMatch[0].length > match[0].length)) { 639 if (match = tempMatch, index = i, this.options.backtrack_lexer) { 640 if (token = this.test_match(tempMatch, rules[i]), token !== !1) return token; 641 if (this._backtrack) { 642 match = !1; 643 continue; 644 } 645 // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) 646 return !1; 647 } 648 if (!this.options.flex) break; 649 } 650 return match ? (token = this.test_match(match, rules[index]), token !== !1 && token) : "" === this._input ? this.EOF : this.parseError("Lexical error on line " + (this.yylineno + 1) + ". Unrecognized text.\n" + this.showPosition(), { 651 text: "", 652 token: null, 653 line: this.yylineno 654 }); 655 }, 656 // return next match that has a token 657 lex: function() { 658 var r = this.next(); 659 return r ? r : this.lex(); 660 }, 661 // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) 662 begin: function(condition) { 663 this.conditionStack.push(condition); 664 }, 665 // pop the previously active lexer condition state off the condition stack 666 popState: function() { 667 var n = this.conditionStack.length - 1; 668 return n > 0 ? this.conditionStack.pop() : this.conditionStack[0]; 669 }, 670 // produce the lexer rule set which is active for the currently active lexer condition state 671 _currentRules: function() { 672 return this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1] ? this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules : this.conditions.INITIAL.rules; 673 }, 674 // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available 675 topState: function(n) { 676 return n = this.conditionStack.length - 1 - Math.abs(n || 0), n >= 0 ? this.conditionStack[n] : "INITIAL"; 677 }, 678 // alias for begin(condition) 679 pushState: function(condition) { 680 this.begin(condition); 681 }, 682 // return the number of states currently on the stack 683 stateStackSize: function() { 684 return this.conditionStack.length; 685 }, 686 options: { 687 "case-insensitive": !0 688 }, 689 performAction: function(yy, yy_, $avoiding_name_collisions, YY_START) { 690 switch ($avoiding_name_collisions) { 691 case 0: 692 return 8; 693 694 case 1: 695 /* skip whitespace */ 696 break; 697 698 case 2: 699 /* skip comments */ 700 break; 701 702 case 3: 703 return 9; 704 705 case 4: 706 return 21; 707 708 case 5: 709 return 22; 710 711 case 6: 712 return 18; 713 714 case 7: 715 return 15; 716 717 case 8: 718 return 13; 719 720 case 9: 721 return 20; 722 723 case 10: 724 return 24; 725 726 case 11: 727 return 24; 728 729 case 12: 730 return 28; 731 732 case 13: 733 return 27; 734 735 case 14: 736 return 30; 737 738 case 15: 739 return 29; 740 741 case 16: 742 return 31; 743 744 case 17: 745 return 5; 746 747 case 18: 748 return "INVALID"; 749 } 750 }, 751 rules: [ /^(?:[\r\n]+)/i, /^(?:\s+)/i, /^(?:#[^\r\n]*)/i, /^(?:participant\b)/i, /^(?:left of\b)/i, /^(?:right of\b)/i, /^(?:over\b)/i, /^(?:note\b)/i, /^(?:title\b)/i, /^(?:,)/i, /^(?:[^\->:,\r\n"]+)/i, /^(?:"[^"]+")/i, /^(?:--)/i, /^(?:-)/i, /^(?:>>)/i, /^(?:>)/i, /^(?:[^\r\n]+)/i, /^(?:$)/i, /^(?:.)/i ], 752 conditions: { 753 INITIAL: { 754 rules: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ], 755 inclusive: !0 756 } 757 } 758 }; 759 return lexer; 760 }(); 761 return parser.lexer = lexer, Parser.prototype = parser, parser.Parser = Parser, 762 new Parser(); 763}(); 764 765"undefined" != typeof require && "undefined" != typeof exports && (exports.parser = parser, 766exports.Parser = parser.Parser, exports.parse = function() { 767 return parser.parse.apply(parser, arguments); 768}, exports.main = function(args) { 769 args[1] || (console.log("Usage: " + args[0] + " FILE"), process.exit(1)); 770 var source = require("fs").readFileSync(require("path").normalize(args[1]), "utf8"); 771 return exports.parser.parse(source); 772}, "undefined" != typeof module && require.main === module && exports.main(process.argv.slice(1))); 773/** 774 * jison doesn't have a good exception, so we make one. 775 * This is brittle as it depends on jison internals 776 */ 777function ParseError(message, hash) { 778 _.extend(this, hash); 779 780 this.name = 'ParseError'; 781 this.message = (message || ''); 782} 783ParseError.prototype = new Error(); 784Diagram.ParseError = ParseError; 785 786Diagram.parse = function(input) { 787 // TODO jison v0.4.17 changed their API slightly, so parser is no longer defined: 788 789 // Create the object to track state and deal with errors 790 parser.yy = new Diagram(); 791 parser.yy.parseError = function(message, hash) { 792 throw new ParseError(message, hash); 793 }; 794 795 // Parse 796 var diagram = parser.parse(input); 797 798 // Then clean up the parseError key that a user won't care about 799 delete diagram.parseError; 800 return diagram; 801}; 802 803 804/** js sequence diagrams 805 * https://bramp.github.io/js-sequence-diagrams/ 806 * (c) 2012-2017 Andrew Brampton (bramp.net) 807 * Simplified BSD license. 808 */ 809/*global Diagram, _ */ 810 811// Following the CSS convention 812// Margin is the gap outside the box 813// Padding is the gap inside the box 814// Each object has x/y/width/height properties 815// The x/y should be top left corner 816// width/height is with both margin and padding 817 818// TODO 819// Image width is wrong, when there is a note in the right hand col 820// Title box could look better 821// Note box could look better 822 823var DIAGRAM_MARGIN = 10; 824 825var ACTOR_MARGIN = 10; // Margin around a actor 826var ACTOR_PADDING = 10; // Padding inside a actor 827 828var SIGNAL_MARGIN = 5; // Margin around a signal 829var SIGNAL_PADDING = 5; // Padding inside a signal 830 831var NOTE_MARGIN = 10; // Margin around a note 832var NOTE_PADDING = 5; // Padding inside a note 833var NOTE_OVERLAP = 15; // Overlap when using a "note over A,B" 834 835var TITLE_MARGIN = 0; 836var TITLE_PADDING = 5; 837 838var SELF_SIGNAL_WIDTH = 20; // How far out a self signal goes 839 840var PLACEMENT = Diagram.PLACEMENT; 841var LINETYPE = Diagram.LINETYPE; 842var ARROWTYPE = Diagram.ARROWTYPE; 843 844var ALIGN_LEFT = 0; 845var ALIGN_CENTER = 1; 846 847function AssertException(message) { this.message = message; } 848AssertException.prototype.toString = function() { 849 return 'AssertException: ' + this.message; 850}; 851 852function assert(exp, message) { 853 if (!exp) { 854 throw new AssertException(message); 855 } 856} 857 858if (!String.prototype.trim) { 859 String.prototype.trim = function() { 860 return this.replace(/^\s+|\s+$/g, ''); 861 }; 862} 863 864Diagram.themes = {}; 865function registerTheme(name, theme) { 866 Diagram.themes[name] = theme; 867} 868 869/****************** 870 * Drawing extras 871 ******************/ 872 873function getCenterX(box) { 874 return box.x + box.width / 2; 875} 876 877function getCenterY(box) { 878 return box.y + box.height / 2; 879} 880 881/****************** 882 * SVG Path extras 883 ******************/ 884 885function clamp(x, min, max) { 886 if (x < min) { 887 return min; 888 } 889 if (x > max) { 890 return max; 891 } 892 return x; 893} 894 895function wobble(x1, y1, x2, y2) { 896 assert(_.all([x1,x2,y1,y2], _.isFinite), 'x1,x2,y1,y2 must be numeric'); 897 898 // Wobble no more than 1/25 of the line length 899 var factor = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / 25; 900 901 // Distance along line where the control points are 902 // Clamp between 20% and 80% so any arrow heads aren't angled too much 903 var r1 = clamp(Math.random(), 0.2, 0.8); 904 var r2 = clamp(Math.random(), 0.2, 0.8); 905 906 var xfactor = Math.random() > 0.5 ? factor : -factor; 907 var yfactor = Math.random() > 0.5 ? factor : -factor; 908 909 var p1 = { 910 x: (x2 - x1) * r1 + x1 + xfactor, 911 y: (y2 - y1) * r1 + y1 + yfactor 912 }; 913 914 var p2 = { 915 x: (x2 - x1) * r2 + x1 - xfactor, 916 y: (y2 - y1) * r2 + y1 - yfactor 917 }; 918 919 return 'C' + p1.x.toFixed(1) + ',' + p1.y.toFixed(1) + // start control point 920 ' ' + p2.x.toFixed(1) + ',' + p2.y.toFixed(1) + // end control point 921 ' ' + x2.toFixed(1) + ',' + y2.toFixed(1); // end point 922} 923 924/** 925 * Draws a wobbly (hand drawn) rect 926 */ 927function handRect(x, y, w, h) { 928 assert(_.all([x, y, w, h], _.isFinite), 'x, y, w, h must be numeric'); 929 return 'M' + x + ',' + y + 930 wobble(x, y, x + w, y) + 931 wobble(x + w, y, x + w, y + h) + 932 wobble(x + w, y + h, x, y + h) + 933 wobble(x, y + h, x, y); 934} 935 936/** 937 * Draws a wobbly (hand drawn) line 938 */ 939function handLine(x1, y1, x2, y2) { 940 assert(_.all([x1,x2,y1,y2], _.isFinite), 'x1,x2,y1,y2 must be numeric'); 941 return 'M' + x1.toFixed(1) + ',' + y1.toFixed(1) + wobble(x1, y1, x2, y2); 942} 943 944/****************** 945 * BaseTheme 946 ******************/ 947 948var BaseTheme = function(diagram, options) { 949 this.init(diagram, options); 950}; 951 952_.extend(BaseTheme.prototype, { 953 954 // Init called while creating the Theme 955 init: function(diagram, options) { 956 this.diagram = diagram; 957 958 this.actorsHeight_ = 0; 959 this.signalsHeight_ = 0; 960 this.title_ = undefined; // hack - This should be somewhere better 961 }, 962 963 setupPaper: function(container) {}, 964 965 draw: function(container) { 966 this.setupPaper(container); 967 968 this.layout(); 969 970 var titleHeight = this.title_ ? this.title_.height : 0; 971 var y = DIAGRAM_MARGIN + titleHeight; 972 973 this.drawTitle(); 974 this.drawActors(y); 975 this.drawSignals(y + this.actorsHeight_); 976 }, 977 978 layout: function() { 979 // Local copies 980 var diagram = this.diagram; 981 var font = this.font_; 982 var actors = diagram.actors; 983 var signals = diagram.signals; 984 985 diagram.width = 0; // min width 986 diagram.height = 0; // min height 987 988 // Setup some layout stuff 989 if (diagram.title) { 990 var title = this.title_ = {}; 991 var bb = this.textBBox(diagram.title, font); 992 title.textBB = bb; 993 title.message = diagram.title; 994 995 title.width = bb.width + (TITLE_PADDING + TITLE_MARGIN) * 2; 996 title.height = bb.height + (TITLE_PADDING + TITLE_MARGIN) * 2; 997 title.x = DIAGRAM_MARGIN; 998 title.y = DIAGRAM_MARGIN; 999 1000 diagram.width += title.width; 1001 diagram.height += title.height; 1002 } 1003 1004 _.each(actors, function(a) { 1005 var bb = this.textBBox(a.name, font); 1006 a.textBB = bb; 1007 1008 a.x = 0; a.y = 0; 1009 a.width = bb.width + (ACTOR_PADDING + ACTOR_MARGIN) * 2; 1010 a.height = bb.height + (ACTOR_PADDING + ACTOR_MARGIN) * 2; 1011 1012 a.distances = []; 1013 a.paddingRight = 0; 1014 this.actorsHeight_ = Math.max(a.height, this.actorsHeight_); 1015 }, this); 1016 1017 function actorEnsureDistance(a, b, d) { 1018 assert(a < b, 'a must be less than or equal to b'); 1019 1020 if (a < 0) { 1021 // Ensure b has left margin 1022 b = actors[b]; 1023 b.x = Math.max(d - b.width / 2, b.x); 1024 } else if (b >= actors.length) { 1025 // Ensure a has right margin 1026 a = actors[a]; 1027 a.paddingRight = Math.max(d, a.paddingRight); 1028 } else { 1029 a = actors[a]; 1030 a.distances[b] = Math.max(d, a.distances[b] ? a.distances[b] : 0); 1031 } 1032 } 1033 1034 _.each(signals, function(s) { 1035 // Indexes of the left and right actors involved 1036 var a; 1037 var b; 1038 1039 var bb = this.textBBox(s.message, font); 1040 1041 //var bb = t.attr("text", s.message).getBBox(); 1042 s.textBB = bb; 1043 s.width = bb.width; 1044 s.height = bb.height; 1045 1046 var extraWidth = 0; 1047 1048 if (s.type == 'Signal') { 1049 1050 s.width += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2; 1051 s.height += (SIGNAL_MARGIN + SIGNAL_PADDING) * 2; 1052 1053 if (s.isSelf()) { 1054 // TODO Self signals need a min height 1055 a = s.actorA.index; 1056 b = a + 1; 1057 s.width += SELF_SIGNAL_WIDTH; 1058 } else { 1059 a = Math.min(s.actorA.index, s.actorB.index); 1060 b = Math.max(s.actorA.index, s.actorB.index); 1061 } 1062 1063 } else if (s.type == 'Note') { 1064 s.width += (NOTE_MARGIN + NOTE_PADDING) * 2; 1065 s.height += (NOTE_MARGIN + NOTE_PADDING) * 2; 1066 1067 // HACK lets include the actor's padding 1068 extraWidth = 2 * ACTOR_MARGIN; 1069 1070 if (s.placement == PLACEMENT.LEFTOF) { 1071 b = s.actor.index; 1072 a = b - 1; 1073 } else if (s.placement == PLACEMENT.RIGHTOF) { 1074 a = s.actor.index; 1075 b = a + 1; 1076 } else if (s.placement == PLACEMENT.OVER && s.hasManyActors()) { 1077 // Over multiple actors 1078 a = Math.min(s.actor[0].index, s.actor[1].index); 1079 b = Math.max(s.actor[0].index, s.actor[1].index); 1080 1081 // We don't need our padding, and we want to overlap 1082 extraWidth = -(NOTE_PADDING * 2 + NOTE_OVERLAP * 2); 1083 1084 } else if (s.placement == PLACEMENT.OVER) { 1085 // Over single actor 1086 a = s.actor.index; 1087 actorEnsureDistance(a - 1, a, s.width / 2); 1088 actorEnsureDistance(a, a + 1, s.width / 2); 1089 this.signalsHeight_ += s.height; 1090 1091 return; // Bail out early 1092 } 1093 } else { 1094 throw new Error('Unhandled signal type:' + s.type); 1095 } 1096 1097 actorEnsureDistance(a, b, s.width + extraWidth); 1098 this.signalsHeight_ += s.height; 1099 }, this); 1100 1101 // Re-jig the positions 1102 var actorsX = 0; 1103 _.each(actors, function(a) { 1104 a.x = Math.max(actorsX, a.x); 1105 1106 // TODO This only works if we loop in sequence, 0, 1, 2, etc 1107 _.each(a.distances, function(distance, b) { 1108 // lodash (and possibly others) do not like sparse arrays 1109 // so sometimes they return undefined 1110 if (typeof distance == 'undefined') { 1111 return; 1112 } 1113 1114 b = actors[b]; 1115 distance = Math.max(distance, a.width / 2, b.width / 2); 1116 b.x = Math.max(b.x, a.x + a.width / 2 + distance - b.width / 2); 1117 }); 1118 1119 actorsX = a.x + a.width + a.paddingRight; 1120 }, this); 1121 1122 diagram.width = Math.max(actorsX, diagram.width); 1123 1124 // TODO Refactor a little 1125 diagram.width += 2 * DIAGRAM_MARGIN; 1126 diagram.height += 2 * DIAGRAM_MARGIN + 2 * this.actorsHeight_ + this.signalsHeight_; 1127 1128 return this; 1129 }, 1130 1131 // TODO Instead of one textBBox function, create a function for each element type, e.g 1132 // layout_title, layout_actor, etc that returns it's bounding box 1133 textBBox: function(text, font) {}, 1134 1135 drawTitle: function() { 1136 var title = this.title_; 1137 if (title) { 1138 this.drawTextBox(title, title.message, TITLE_MARGIN, TITLE_PADDING, this.font_, ALIGN_LEFT); 1139 } 1140 }, 1141 1142 drawActors: function(offsetY) { 1143 var y = offsetY; 1144 _.each(this.diagram.actors, function(a) { 1145 // Top box 1146 this.drawActor(a, y, this.actorsHeight_); 1147 1148 // Bottom box 1149 this.drawActor(a, y + this.actorsHeight_ + this.signalsHeight_, this.actorsHeight_); 1150 1151 // Veritical line 1152 var aX = getCenterX(a); 1153 this.drawLine( 1154 aX, y + this.actorsHeight_ - ACTOR_MARGIN, 1155 aX, y + this.actorsHeight_ + ACTOR_MARGIN + this.signalsHeight_); 1156 }, this); 1157 }, 1158 1159 drawActor: function(actor, offsetY, height) { 1160 actor.y = offsetY; 1161 actor.height = height; 1162 this.drawTextBox(actor, actor.name, ACTOR_MARGIN, ACTOR_PADDING, this.font_, ALIGN_CENTER); 1163 }, 1164 1165 drawSignals: function(offsetY) { 1166 var y = offsetY; 1167 _.each(this.diagram.signals, function(s) { 1168 // TODO Add debug mode, that draws padding/margin box 1169 if (s.type == 'Signal') { 1170 if (s.isSelf()) { 1171 this.drawSelfSignal(s, y); 1172 } else { 1173 this.drawSignal(s, y); 1174 } 1175 1176 } else if (s.type == 'Note') { 1177 this.drawNote(s, y); 1178 } 1179 1180 y += s.height; 1181 }, this); 1182 }, 1183 1184 drawSelfSignal: function(signal, offsetY) { 1185 assert(signal.isSelf(), 'signal must be a self signal'); 1186 1187 var textBB = signal.textBB; 1188 var aX = getCenterX(signal.actorA); 1189 1190 var x = aX + SELF_SIGNAL_WIDTH + SIGNAL_PADDING; 1191 var y = offsetY + SIGNAL_PADDING + signal.height / 2 + textBB.y; 1192 1193 this.drawText(x, y, signal.message, this.font_, ALIGN_LEFT); 1194 1195 var y1 = offsetY + SIGNAL_MARGIN + SIGNAL_PADDING; 1196 var y2 = y1 + signal.height - 2 * SIGNAL_MARGIN - SIGNAL_PADDING; 1197 1198 // Draw three lines, the last one with a arrow 1199 this.drawLine(aX, y1, aX + SELF_SIGNAL_WIDTH, y1, signal.linetype); 1200 this.drawLine(aX + SELF_SIGNAL_WIDTH, y1, aX + SELF_SIGNAL_WIDTH, y2, signal.linetype); 1201 this.drawLine(aX + SELF_SIGNAL_WIDTH, y2, aX, y2, signal.linetype, signal.arrowtype); 1202 }, 1203 1204 drawSignal: function(signal, offsetY) { 1205 var aX = getCenterX(signal.actorA); 1206 var bX = getCenterX(signal.actorB); 1207 1208 // Mid point between actors 1209 var x = (bX - aX) / 2 + aX; 1210 var y = offsetY + SIGNAL_MARGIN + 2 * SIGNAL_PADDING; 1211 1212 // Draw the text in the middle of the signal 1213 this.drawText(x, y, signal.message, this.font_, ALIGN_CENTER); 1214 1215 // Draw the line along the bottom of the signal 1216 y = offsetY + signal.height - SIGNAL_MARGIN - SIGNAL_PADDING; 1217 this.drawLine(aX, y, bX, y, signal.linetype, signal.arrowtype); 1218 }, 1219 1220 drawNote: function(note, offsetY) { 1221 note.y = offsetY; 1222 var actorA = note.hasManyActors() ? note.actor[0] : note.actor; 1223 var aX = getCenterX(actorA); 1224 switch (note.placement) { 1225 case PLACEMENT.RIGHTOF: 1226 note.x = aX + ACTOR_MARGIN; 1227 break; 1228 case PLACEMENT.LEFTOF: 1229 note.x = aX - ACTOR_MARGIN - note.width; 1230 break; 1231 case PLACEMENT.OVER: 1232 if (note.hasManyActors()) { 1233 var bX = getCenterX(note.actor[1]); 1234 var overlap = NOTE_OVERLAP + NOTE_PADDING; 1235 note.x = Math.min(aX, bX) - overlap; 1236 note.width = (Math.max(aX, bX) + overlap) - note.x; 1237 } else { 1238 note.x = aX - note.width / 2; 1239 } 1240 break; 1241 default: 1242 throw new Error('Unhandled note placement: ' + note.placement); 1243 } 1244 return this.drawTextBox(note, note.message, NOTE_MARGIN, NOTE_PADDING, this.font_, ALIGN_LEFT); 1245 }, 1246 1247 /** 1248 * Draw text surrounded by a box 1249 */ 1250 drawTextBox: function(box, text, margin, padding, font, align) { 1251 var x = box.x + margin; 1252 var y = box.y + margin; 1253 var w = box.width - 2 * margin; 1254 var h = box.height - 2 * margin; 1255 1256 // Draw inner box 1257 this.drawRect(x, y, w, h); 1258 1259 // Draw text (in the center) 1260 if (align == ALIGN_CENTER) { 1261 x = getCenterX(box); 1262 y = getCenterY(box); 1263 } else { 1264 x += padding; 1265 y += padding; 1266 } 1267 1268 return this.drawText(x, y, text, font, align); 1269 } 1270}); 1271 1272/** js sequence diagrams 1273 * https://bramp.github.io/js-sequence-diagrams/ 1274 * (c) 2012-2017 Andrew Brampton (bramp.net) 1275 * Simplified BSD license. 1276 */ 1277/*global Diagram, Snap, WebFont _ */ 1278// TODO Move defintion of font onto the <svg>, so it can easily be override at each level 1279if (typeof Snap != 'undefined') { 1280 1281 var xmlns = 'http://www.w3.org/2000/svg'; 1282 1283 var LINE = { 1284 'stroke': '#000000', 1285 'stroke-width': 2, // BUG TODO This gets set as a style, not as a attribute. Look at eve.on("snap.util.attr"... 1286 'fill': 'none' 1287 }; 1288 1289 var RECT = { 1290 'stroke': '#000000', 1291 'stroke-width': 2, 1292 'fill': '#fff' 1293 }; 1294 1295 var LOADED_FONTS = {}; 1296 1297 /****************** 1298 * SnapTheme 1299 ******************/ 1300 1301 var SnapTheme = function(diagram, options, resume) { 1302 _.defaults(options, { 1303 'css-class': 'simple', 1304 'font-size': 16, 1305 'font-family': 'Andale Mono, monospace' 1306 }); 1307 1308 this.init(diagram, options, resume); 1309 }; 1310 1311 _.extend(SnapTheme.prototype, BaseTheme.prototype, { 1312 1313 init: function(diagram, options, resume) { 1314 BaseTheme.prototype.init.call(this, diagram); 1315 1316 this.paper_ = undefined; 1317 this.cssClass_ = options['css-class'] || undefined; 1318 this.font_ = { 1319 'font-size': options['font-size'], 1320 'font-family': options['font-family'] 1321 }; 1322 1323 var a = this.arrowTypes_ = {}; 1324 a[ARROWTYPE.FILLED] = 'Block'; 1325 a[ARROWTYPE.OPEN] = 'Open'; 1326 1327 var l = this.lineTypes_ = {}; 1328 l[LINETYPE.SOLID] = ''; 1329 l[LINETYPE.DOTTED] = '6,2'; 1330 1331 var that = this; 1332 this.waitForFont(function() { 1333 resume(that); 1334 }); 1335 }, 1336 1337 // Wait for loading of the font 1338 waitForFont: function(callback) { 1339 var fontFamily = this.font_['font-family']; 1340 1341 if (typeof WebFont == 'undefined') { 1342 throw new Error('WebFont is required (https://github.com/typekit/webfontloader).'); 1343 } 1344 1345 if (LOADED_FONTS[fontFamily]) { 1346 // If already loaded, just return instantly. 1347 callback(); 1348 return; 1349 } 1350 1351 WebFont.load({ 1352 custom: { 1353 families: [fontFamily] // TODO replace this with something that reads the css 1354 }, 1355 classes: false, // No need to place classes on the DOM, just use JS Events 1356 active: function() { 1357 LOADED_FONTS[fontFamily] = true; 1358 callback(); 1359 }, 1360 inactive: function() { 1361 // If we fail to fetch the font, still continue. 1362 LOADED_FONTS[fontFamily] = true; 1363 callback(); 1364 } 1365 }); 1366 }, 1367 1368 addDescription: function(svg, description) { 1369 var desc = document.createElementNS(xmlns, 'desc'); 1370 desc.appendChild(document.createTextNode(description)); 1371 svg.appendChild(desc); 1372 }, 1373 1374 setupPaper: function(container) { 1375 // Container must be a SVG element. We assume it's a div, so lets create a SVG and insert 1376 var svg = document.createElementNS(xmlns, 'svg'); 1377 container.appendChild(svg); 1378 1379 this.addDescription(svg, this.diagram.title || ''); 1380 1381 this.paper_ = Snap(svg); 1382 this.paper_.addClass('sequence'); 1383 1384 if (this.cssClass_) { 1385 this.paper_.addClass(this.cssClass_); 1386 } 1387 1388 this.beginGroup(); 1389 1390 // TODO Perhaps only include the markers if we actually use them. 1391 var a = this.arrowMarkers_ = {}; 1392 var arrow = this.paper_.path('M 0 0 L 5 2.5 L 0 5 z'); 1393 a[ARROWTYPE.FILLED] = arrow.marker(0, 0, 5, 5, 5, 2.5) 1394 .attr({id: 'markerArrowBlock'}); 1395 1396 arrow = this.paper_.path('M 9.6,8 1.92,16 0,13.7 5.76,8 0,2.286 1.92,0 9.6,8 z'); 1397 a[ARROWTYPE.OPEN] = arrow.marker(0, 0, 9.6, 16, 9.6, 8) 1398 .attr({markerWidth: '4', id: 'markerArrowOpen'}); 1399 }, 1400 1401 layout: function() { 1402 BaseTheme.prototype.layout.call(this); 1403 this.paper_.attr({ 1404 width: this.diagram.width + 'px', 1405 height: this.diagram.height + 'px' 1406 }); 1407 }, 1408 1409 textBBox: function(text, font) { 1410 // TODO getBBox will return the bounds with any whitespace/kerning. This makes some of our aligments screwed up 1411 var t = this.createText(text, font); 1412 var bb = t.getBBox(); 1413 t.remove(); 1414 return bb; 1415 }, 1416 1417 // For each drawn element, push onto the stack, so it can be wrapped in a single outer element 1418 pushToStack: function(element) { 1419 this._stack.push(element); 1420 return element; 1421 }, 1422 1423 // Begin a group of elements 1424 beginGroup: function() { 1425 this._stack = []; 1426 }, 1427 1428 // Finishes the group, and returns the <group> element 1429 finishGroup: function() { 1430 var g = this.paper_.group.apply(this.paper_, this._stack); 1431 this.beginGroup(); // Reset the group 1432 return g; 1433 }, 1434 1435 createText: function(text, font) { 1436 text = _.invoke(text.split('\n'), 'trim'); 1437 var t = this.paper_.text(0, 0, text); 1438 t.attr(font || {}); 1439 if (text.length > 1) { 1440 // Every row after the first, set tspan to be 1.2em below the previous line 1441 t.selectAll('tspan:nth-child(n+2)').attr({ 1442 dy: '1.2em', 1443 x: 0 1444 }); 1445 } 1446 1447 return t; 1448 }, 1449 1450 drawLine: function(x1, y1, x2, y2, linetype, arrowhead) { 1451 var line = this.paper_.line(x1, y1, x2, y2).attr(LINE); 1452 if (linetype !== undefined) { 1453 line.attr('strokeDasharray', this.lineTypes_[linetype]); 1454 } 1455 if (arrowhead !== undefined) { 1456 line.attr('markerEnd', this.arrowMarkers_[arrowhead]); 1457 } 1458 return this.pushToStack(line); 1459 }, 1460 1461 drawRect: function(x, y, w, h) { 1462 var rect = this.paper_.rect(x, y, w, h).attr(RECT); 1463 return this.pushToStack(rect); 1464 }, 1465 1466 /** 1467 * Draws text with a optional white background 1468 * x,y (int) x,y top left point of the text, or the center of the text (depending on align param) 1469 * text (string) text to print 1470 * font (Object) 1471 * align (string) ALIGN_LEFT or ALIGN_CENTER 1472 */ 1473 drawText: function(x, y, text, font, align) { 1474 var t = this.createText(text, font); 1475 var bb = t.getBBox(); 1476 1477 if (align == ALIGN_CENTER) { 1478 x = x - bb.width / 2; 1479 y = y - bb.height / 2; 1480 } 1481 1482 // Now move the text into place 1483 // `y - bb.y` because text(..) is positioned from the baseline, so this moves it down. 1484 t.attr({x: x - bb.x, y: y - bb.y}); 1485 t.selectAll('tspan').attr({x: x}); 1486 1487 this.pushToStack(t); 1488 return t; 1489 }, 1490 1491 drawTitle: function() { 1492 this.beginGroup(); 1493 BaseTheme.prototype.drawTitle.call(this); 1494 return this.finishGroup().addClass('title'); 1495 }, 1496 1497 drawActor: function(actor, offsetY, height) { 1498 this.beginGroup(); 1499 BaseTheme.prototype.drawActor.call(this, actor, offsetY, height); 1500 return this.finishGroup().addClass('actor'); 1501 }, 1502 1503 drawSignal: function(signal, offsetY) { 1504 this.beginGroup(); 1505 BaseTheme.prototype.drawSignal.call(this, signal, offsetY); 1506 return this.finishGroup().addClass('signal'); 1507 }, 1508 1509 drawSelfSignal: function(signal, offsetY) { 1510 this.beginGroup(); 1511 BaseTheme.prototype.drawSelfSignal.call(this, signal, offsetY); 1512 return this.finishGroup().addClass('signal'); 1513 }, 1514 1515 drawNote: function(note, offsetY) { 1516 this.beginGroup(); 1517 BaseTheme.prototype.drawNote.call(this, note, offsetY); 1518 return this.finishGroup().addClass('note'); 1519 }, 1520 }); 1521 1522 /****************** 1523 * SnapHandTheme 1524 ******************/ 1525 1526 var SnapHandTheme = function(diagram, options, resume) { 1527 _.defaults(options, { 1528 'css-class': 'hand', 1529 'font-size': 16, 1530 'font-family': 'danielbd' 1531 }); 1532 1533 this.init(diagram, options, resume); 1534 }; 1535 1536 // Take the standard SnapTheme and make all the lines wobbly 1537 _.extend(SnapHandTheme.prototype, SnapTheme.prototype, { 1538 drawLine: function(x1, y1, x2, y2, linetype, arrowhead) { 1539 var line = this.paper_.path(handLine(x1, y1, x2, y2)).attr(LINE); 1540 if (linetype !== undefined) { 1541 line.attr('strokeDasharray', this.lineTypes_[linetype]); 1542 } 1543 if (arrowhead !== undefined) { 1544 line.attr('markerEnd', this.arrowMarkers_[arrowhead]); 1545 } 1546 return this.pushToStack(line); 1547 }, 1548 1549 drawRect: function(x, y, w, h) { 1550 var rect = this.paper_.path(handRect(x, y, w, h)).attr(RECT); 1551 return this.pushToStack(rect); 1552 } 1553 }); 1554 1555 registerTheme('snapSimple', SnapTheme); 1556 registerTheme('snapHand', SnapHandTheme); 1557} 1558 1559 1560/** js sequence diagrams 1561 * https://bramp.github.io/js-sequence-diagrams/ 1562 * (c) 2012-2017 Andrew Brampton (bramp.net) 1563 * Simplified BSD license. 1564 */ 1565/*global Diagram, _ */ 1566 1567if (typeof Raphael == 'undefined' && typeof Snap == 'undefined') { 1568 throw new Error('Raphael or Snap.svg is required to be included.'); 1569} 1570 1571if (_.isEmpty(Diagram.themes)) { 1572 // If you are using stock js-sequence-diagrams you should never see this. This only 1573 // happens if you have removed the built in themes. 1574 throw new Error('No themes were registered. Please call registerTheme(...).'); 1575} 1576 1577// Set the default hand/simple based on which theme is available. 1578Diagram.themes.hand = Diagram.themes.snapHand || Diagram.themes.raphaelHand; 1579Diagram.themes.simple = Diagram.themes.snapSimple || Diagram.themes.raphaelSimple; 1580 1581/* Draws the diagram. Creates a SVG inside the container 1582* container (HTMLElement|string) DOM element or its ID to draw on 1583* options (Object) 1584*/ 1585Diagram.prototype.drawSVG = function(container, options) { 1586 var defaultOptions = { 1587 theme: 'hand' 1588 }; 1589 1590 options = _.defaults(options || {}, defaultOptions); 1591 1592 if (!(options.theme in Diagram.themes)) { 1593 throw new Error('Unsupported theme: ' + options.theme); 1594 } 1595 1596 // TODO Write tests for this check 1597 var div = _.isString(container) ? document.getElementById(container) : container; 1598 if (div === null || !div.tagName) { 1599 throw new Error('Invalid container: ' + container); 1600 } 1601 1602 var Theme = Diagram.themes[options.theme]; 1603 new Theme(this, options, function(drawing) { 1604 drawing.draw(div); 1605 }); 1606}; // end of drawSVG 1607/** js sequence diagrams 1608 * https://bramp.github.io/js-sequence-diagrams/ 1609 * (c) 2012-2017 Andrew Brampton (bramp.net) 1610 * Simplified BSD license. 1611 */ 1612/*global jQuery */ 1613if (typeof jQuery != 'undefined') { 1614 (function($) { 1615 $.fn.sequenceDiagram = function(options) { 1616 return this.each(function() { 1617 var $this = $(this); 1618 var diagram = Diagram.parse($this.text()); 1619 $this.html(''); 1620 diagram.drawSVG(this, options); 1621 }); 1622 }; 1623 })(jQuery); 1624} 1625 1626// Taken from underscore.js: 1627// Establish the root object, `window` (`self`) in the browser, or `global` on the server. 1628// We use `self` instead of `window` for `WebWorker` support. 1629var root = (typeof self == 'object' && self.self == self && self) || 1630 (typeof global == 'object' && global.global == global && global); 1631 1632// Export the Diagram object for **Node.js**, with 1633// backwards-compatibility for their old module API. If we're in 1634// the browser, add `Diagram` as a global object. 1635if (typeof exports !== 'undefined') { 1636 if (typeof module !== 'undefined' && module.exports) { 1637 exports = module.exports = Diagram; 1638 } 1639 exports.Diagram = Diagram; 1640} else { 1641 root.Diagram = Diagram; 1642} 1643}()); 1644 1645