├── assets ├── readme.md ├── themes │ ├── git.scss │ ├── mermaid.scss │ ├── flowchart.scss │ ├── class.scss │ ├── sequence.scss │ ├── default │ │ └── index.scss │ ├── forest │ │ └── index.scss │ ├── neutral │ │ └── index.scss │ ├── dark │ │ └── index.scss │ └── gantt.scss ├── logger.js ├── utils.spec.js ├── diagrams │ ├── gantt │ │ ├── gantt.spec.js │ │ ├── parser │ │ │ └── gantt.jison │ │ ├── ganttDb.spec.js │ │ ├── ganttDb.js │ │ └── ganttRenderer.js │ ├── class │ │ ├── classDb.js │ │ ├── classDiagram.spec.js │ │ ├── parser │ │ │ └── classDiagram.jison │ │ └── classRenderer.js │ ├── git │ │ ├── parser │ │ │ └── gitGraph.jison │ │ ├── gitGraphAst.js │ │ ├── gitGraphParser.spec.js │ │ └── gitGraphRenderer.js │ ├── sequence │ │ ├── sequenceDb.js │ │ ├── parser │ │ │ └── sequenceDiagram.jison │ │ └── svgDraw.js │ └── flowchart │ │ ├── flowDb.js │ │ ├── flowRenderer.js │ │ └── parser │ │ └── flow.jison ├── utils.js ├── mermaidAPI.spec.js ├── mermaid.js ├── mermaid.spec.js └── mermaidAPI.js ├── screenshots ├── README.md ├── SCREENSHOTS.MD ├── mongodb-url.png ├── ngrok-token.png ├── create-db-user.png └── connect-to-repo.png ├── .gitignore ├── favicon.ico ├── Gemfile ├── _config.yml ├── index.md ├── _data └── navigation.yml ├── _plugins └── tags │ └── napolean.rb ├── about.md ├── README.md ├── achievibit-local.md └── kibibit-bit.md /assets/readme.md: -------------------------------------------------------------------------------- 1 | asd 2 | -------------------------------------------------------------------------------- /screenshots/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /screenshots/SCREENSHOTS.MD: -------------------------------------------------------------------------------- 1 | save your documentation screenshots here 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/doc-explorer/master/favicon.ico -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'github-pages', group: :jekyll_plugins 3 | -------------------------------------------------------------------------------- /screenshots/mongodb-url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/mongodb-url.png -------------------------------------------------------------------------------- /screenshots/ngrok-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/ngrok-token.png -------------------------------------------------------------------------------- /screenshots/create-db-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/create-db-user.png -------------------------------------------------------------------------------- /screenshots/connect-to-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kibibit/doc-explorer/master/screenshots/connect-to-repo.png -------------------------------------------------------------------------------- /assets/themes/git.scss: -------------------------------------------------------------------------------- 1 | .commit-id, 2 | .commit-msg, 3 | .branch-label { 4 | fill: lightgrey; 5 | color: lightgrey; 6 | } 7 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: Kibibit/bulma-theme 2 | name: kibibit doc explorer 3 | plugins: 4 | - jekyll 5 | - jekyll-spark 6 | -------------------------------------------------------------------------------- /assets/themes/mermaid.scss: -------------------------------------------------------------------------------- 1 | @import 'flowchart'; 2 | @import 'sequence'; 3 | @import 'gantt'; 4 | @import 'class'; 5 | @import 'git'; 6 | -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: kibibit's Document Explorer 3 | layout: homepage 4 | --- 5 | 6 | # kibibit's Document Explorer - Jekyll theme 7 | 8 | some general documentation for kibibt projects 9 | -------------------------------------------------------------------------------- /_data/navigation.yml: -------------------------------------------------------------------------------- 1 | - title: "@kibibit/bit documentation" 2 | url: /kibibit-bit 3 | icon: fa-terminal 4 | - title: Run Achievibit Locally 5 | url: /achievibit-local 6 | icon: fa-flask 7 | - title: About Us 8 | url: /about 9 | icon: fa-info-circle 10 | -------------------------------------------------------------------------------- /_plugins/tags/napolean.rb: -------------------------------------------------------------------------------- 1 | require "jekyll-spark" 2 | 3 | module Jekyll 4 | # Create your component class 5 | class NapoleanComponent < ComponentTag 6 | def template(context) 7 | render = %Q[ 8 |
9 | 10 |
11 | ] 12 | end 13 | end 14 | end 15 | 16 | # Register your component with Liquid 17 | Liquid::Template.register_tag( 18 | "Napolean", # Namespace your component 19 | Jekyll::NapoleanComponent, # Pass your newly created component class 20 | ) 21 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About Us 3 | authors: 4 | - Neil Kalman 5 | layout: default 6 | id: about-us 7 | permalink: /about 8 | --- 9 | 10 | 11 | # About Us 12 | 13 | is an open source team of software developers, trying to make the world easier piece by piece. 14 | 15 | you’re more than welcome to contribute! 16 | 17 | ## About this project 18 | 19 | This is a Jekyll theme for GitHub Pages. It's the more "formal" theme we have for important documents (happy theme: kibibit/colorful-theme) 20 | 21 | 22 | ### Example Variables: 23 | 24 | this is the **default** layout 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doc-explorer 2 | General documentation for kibibit thingies 3 | 4 | We can keep getting started documents here, design documents, and even thoughts and ideas 5 | 6 | To add a document, simply create a new `markdown` file and include the following header: 7 | 8 | ```markdown 9 | --- 10 | title: 11 | authors: 12 | - <author1> 13 | layout: <default | homepage | postmortem> 14 | id: <title-with-dashes> 15 | permalink: /<sub-path-to-article> 16 | --- 17 | ``` 18 | 19 | When the document is ready in your opinion, you can publish it by adding it to the navigation object located in `_data/navigation.yml`: 20 | 21 | ```yml 22 | - title: <title> 23 | url: /<sub-path-to-article> 24 | icon: <icon> 25 | ``` 26 | 27 | if you want to add it inside a category, use `sublinks`: 28 | 29 | ```yml 30 | - title: Postmortems 31 | url: /postmortem 32 | icon: fa-flask 33 | sublinks: 34 | - title: Shakespeare Sonnet++ Postmortem 35 | url: /postmortem/001 36 | incident: 1 37 | ``` 38 | -------------------------------------------------------------------------------- /assets/themes/flowchart.scss: -------------------------------------------------------------------------------- 1 | .label { 2 | font-family: 'trebuchet ms', verdana, arial; 3 | color: #333; 4 | } 5 | 6 | .node rect, 7 | .node circle, 8 | .node ellipse, 9 | .node polygon { 10 | fill: $mainBkg; 11 | stroke: $nodeBorder; 12 | stroke-width: 1px; 13 | } 14 | 15 | .node.clickable { 16 | cursor: pointer; 17 | } 18 | 19 | .arrowheadPath { 20 | fill: $arrowheadColor; 21 | } 22 | 23 | .edgePath .path { 24 | stroke: $lineColor; 25 | stroke-width: 1.5px; 26 | } 27 | 28 | .edgeLabel { 29 | background-color: $edgeLabelBackground; 30 | } 31 | 32 | .cluster rect { 33 | fill: $secondBkg !important; 34 | stroke: $clusterBorder !important; 35 | stroke-width: 1px !important; 36 | } 37 | 38 | .cluster text { 39 | fill: $titleColor; 40 | } 41 | 42 | div.mermaidTooltip { 43 | position: absolute; 44 | text-align: center; 45 | max-width: 200px; 46 | padding: 2px; 47 | font-family: 'trebuchet ms', verdana, arial; 48 | font-size: 12px; 49 | background: $secondBkg; 50 | border: 1px solid $border2; 51 | border-radius: 2px; 52 | pointer-events: none; 53 | z-index: 100; 54 | } 55 | -------------------------------------------------------------------------------- /assets/logger.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export const LEVELS = { 4 | debug: 1, 5 | info: 2, 6 | warn: 3, 7 | error: 4, 8 | fatal: 5 9 | } 10 | 11 | export const logger = { 12 | debug: () => {}, 13 | info: () => {}, 14 | warn: () => {}, 15 | error: () => {}, 16 | fatal: () => {} 17 | } 18 | 19 | export const setLogLevel = function (level) { 20 | logger.debug = () => {} 21 | logger.info = () => {} 22 | logger.warn = () => {} 23 | logger.error = () => {} 24 | logger.fatal = () => {} 25 | if (level <= LEVELS.fatal) { 26 | logger.fatal = console.log.bind(console, '\x1b[35m', format('FATAL')) 27 | } 28 | if (level <= LEVELS.error) { 29 | logger.error = console.log.bind(console, '\x1b[31m', format('ERROR')) 30 | } 31 | if (level <= LEVELS.warn) { 32 | logger.warn = console.log.bind(console, `\x1b[33m`, format('WARN')) 33 | } 34 | if (level <= LEVELS.info) { 35 | logger.info = console.log.bind(console, '\x1b[34m', format('INFO')) 36 | } 37 | if (level <= LEVELS.debug) { 38 | logger.debug = console.log.bind(console, '\x1b[32m', format('DEBUG')) 39 | } 40 | } 41 | 42 | const format = (level) => { 43 | const time = moment().format('HH:mm:ss.SSS') 44 | return `${time} : ${level} : ` 45 | } 46 | -------------------------------------------------------------------------------- /assets/themes/class.scss: -------------------------------------------------------------------------------- 1 | g.classGroup text { 2 | fill: $nodeBorder; 3 | stroke: none; 4 | font-family: 'trebuchet ms', verdana, arial; 5 | font-size: 10px; 6 | } 7 | 8 | g.classGroup rect { 9 | fill: $nodeBkg; 10 | stroke: $nodeBorder; 11 | } 12 | 13 | g.classGroup line { 14 | stroke: $nodeBorder; 15 | stroke-width: 1; 16 | } 17 | 18 | .classLabel .box { 19 | stroke: none; 20 | stroke-width: 0; 21 | fill: $nodeBkg; 22 | opacity: 0.5; 23 | } 24 | 25 | .classLabel .label { 26 | fill: $nodeBorder; 27 | font-size: 10px; 28 | } 29 | 30 | .relation { 31 | stroke: $nodeBorder; 32 | stroke-width: 1; 33 | fill: none; 34 | } 35 | 36 | @mixin composition { 37 | fill: $nodeBorder; 38 | stroke: $nodeBorder; 39 | stroke-width: 1; 40 | } 41 | 42 | #compositionStart { 43 | @include composition; 44 | } 45 | 46 | #compositionEnd { 47 | @include composition; 48 | } 49 | 50 | @mixin aggregation { 51 | fill: $nodeBkg; 52 | stroke: $nodeBorder; 53 | stroke-width: 1; 54 | } 55 | 56 | #aggregationStart { 57 | @include aggregation; 58 | } 59 | 60 | #aggregationEnd { 61 | @include aggregation; 62 | } 63 | 64 | #dependencyStart { 65 | @include composition; 66 | } 67 | 68 | #dependencyEnd { 69 | @include composition; 70 | } 71 | 72 | #extensionStart { 73 | @include composition; 74 | } 75 | 76 | #extensionEnd { 77 | @include composition; 78 | } 79 | -------------------------------------------------------------------------------- /assets/themes/sequence.scss: -------------------------------------------------------------------------------- 1 | .actor { 2 | stroke: $actorBorder; 3 | fill: $actorBkg; 4 | } 5 | 6 | text.actor { 7 | fill: $actorTextColor; 8 | stroke: none; 9 | } 10 | 11 | .actor-line { 12 | stroke: $actorLineColor; 13 | } 14 | 15 | .messageLine0 { 16 | stroke-width: 1.5; 17 | stroke-dasharray: '2 2'; 18 | marker-end: 'url(#arrowhead)'; 19 | stroke: $signalColor; 20 | } 21 | 22 | .messageLine1 { 23 | stroke-width: 1.5; 24 | stroke-dasharray: '2 2'; 25 | stroke: $signalColor; 26 | } 27 | 28 | #arrowhead { 29 | fill: $signalColor; 30 | } 31 | 32 | #crosshead path { 33 | fill: $signalColor !important; 34 | stroke: $signalColor !important; 35 | } 36 | 37 | .messageText { 38 | fill: $signalTextColor; 39 | stroke: none; 40 | } 41 | 42 | .labelBox { 43 | stroke: $labelBoxBorderColor; 44 | fill: $labelBoxBkgColor; 45 | } 46 | 47 | .labelText { 48 | fill: $labelTextColor; 49 | stroke: none; 50 | } 51 | 52 | .loopText { 53 | fill: $labelTextColor; 54 | stroke: none; 55 | } 56 | 57 | .loopLine { 58 | stroke-width: 2; 59 | stroke-dasharray: '2 2'; 60 | marker-end: 'url(#arrowhead)'; 61 | stroke: $labelBoxBorderColor; 62 | } 63 | 64 | .note { 65 | //stroke: #decc93; 66 | stroke: $noteBorderColor; 67 | fill: $noteBkgColor; 68 | } 69 | 70 | .noteText { 71 | fill: black; 72 | stroke: none; 73 | font-family: 'trebuchet ms', verdana, arial; 74 | font-size: 14px; 75 | } 76 | -------------------------------------------------------------------------------- /assets/themes/default/index.scss: -------------------------------------------------------------------------------- 1 | $mainBkg: #ECECFF; 2 | $secondBkg: #ffffde; 3 | $lineColor: #333333; 4 | $border1: #CCCCFF; 5 | $border2: #aaaa33; 6 | $arrowheadColor: #333333; 7 | 8 | /* Flowchart variables */ 9 | 10 | $nodeBkg: $mainBkg; 11 | $nodeBorder: #9370DB; 12 | $clusterBkg: $secondBkg; 13 | $clusterBorder: $border2; 14 | $defaultLinkColor: $lineColor; 15 | $titleColor: #333; 16 | $edgeLabelBackground: #e8e8e8; 17 | 18 | /* Sequence Diagram variables */ 19 | 20 | $actorBorder: $border1; 21 | $actorBkg: $mainBkg; 22 | $actorTextColor: black; 23 | $actorLineColor: grey; 24 | $signalColor: #333; 25 | $signalTextColor: #333; 26 | $labelBoxBkgColor: $actorBkg; 27 | $labelBoxBorderColor: $actorBorder; 28 | $labelTextColor: $actorTextColor; 29 | $noteBorderColor: $border2; 30 | $noteBkgColor: #fff5ad; 31 | 32 | /* Gantt chart variables */ 33 | 34 | $sectionBkgColor: rgba(102, 102, 255, 0.49); 35 | $altSectionBkgColor: white; 36 | $sectionBkgColor2: #fff400; 37 | $taskBorderColor: #534fbc; 38 | $taskBkgColor: #8a90dd; 39 | $taskTextLightColor: white; 40 | $taskTextColor: $taskTextLightColor; 41 | $taskTextDarkColor: black; 42 | $taskTextOutsideColor: $taskTextDarkColor; 43 | $activeTaskBorderColor: #534fbc; 44 | $activeTaskBkgColor: #bfc7ff; 45 | $gridColor: lightgrey; 46 | $doneTaskBkgColor: lightgrey; 47 | $doneTaskBorderColor: grey; 48 | $critBorderColor: #ff8888; 49 | $critBkgColor: red; 50 | $todayLineColor: red; 51 | 52 | @import '../mermaid'; 53 | -------------------------------------------------------------------------------- /assets/themes/forest/index.scss: -------------------------------------------------------------------------------- 1 | $mainBkg: #cde498; 2 | $secondBkg: #cdffb2; 3 | $lineColor: #1a3318; 4 | $lineColor: green; 5 | $border1: #13540c; 6 | $border2: #6eaa49; 7 | $arrowheadColor: green; 8 | 9 | /* Flowchart variables */ 10 | 11 | $nodeBkg: $mainBkg; 12 | $nodeBorder: $border1; 13 | $clusterBkg: $secondBkg; 14 | $clusterBorder: $border2; 15 | $defaultLinkColor: $lineColor; 16 | $titleColor: #333; 17 | $edgeLabelBackground: #e8e8e8; 18 | 19 | /* Sequence Diagram variables */ 20 | 21 | $actorBorder: $border1; 22 | $actorBkg: $mainBkg; 23 | $actorTextColor: black; 24 | $actorLineColor: grey; 25 | $signalColor: #333; 26 | $signalTextColor: #333; 27 | $labelBoxBkgColor: $actorBkg; 28 | $labelBoxBorderColor: #326932; 29 | $labelTextColor: $actorTextColor; 30 | $noteBorderColor: $border2; 31 | $noteBkgColor: #fff5ad; 32 | 33 | /* Gantt chart variables */ 34 | 35 | $sectionBkgColor: #6eaa49; 36 | $altSectionBkgColor: white; 37 | $sectionBkgColor2: #6eaa49; 38 | $taskBorderColor: $border1; 39 | $taskBkgColor: #487e3a; 40 | $taskTextLightColor: white; 41 | $taskTextColor: $taskTextLightColor; 42 | $taskTextDarkColor: black; 43 | $taskTextOutsideColor: $taskTextDarkColor; 44 | $activeTaskBorderColor: $taskBorderColor; 45 | $activeTaskBkgColor: $mainBkg; 46 | $gridColor: lightgrey; 47 | $doneTaskBkgColor: lightgrey; 48 | $doneTaskBorderColor: grey; 49 | $critBorderColor: #ff8888; 50 | $critBkgColor: red; 51 | $todayLineColor: red; 52 | 53 | @import '../mermaid'; 54 | -------------------------------------------------------------------------------- /assets/utils.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import utils from './utils' 3 | 4 | describe('when detecting chart type ', function () { 5 | it('should handle a graph defintion', function () { 6 | const str = 'graph TB\nbfs1:queue' 7 | const type = utils.detectType(str) 8 | expect(type).toBe('flowchart') 9 | }) 10 | it('should handle a graph defintion with leading spaces', function () { 11 | const str = ' graph TB\nbfs1:queue' 12 | const type = utils.detectType(str) 13 | expect(type).toBe('flowchart') 14 | }) 15 | 16 | it('should handle a graph defintion with leading spaces and newline', function () { 17 | const str = ' \n graph TB\nbfs1:queue' 18 | const type = utils.detectType(str) 19 | expect(type).toBe('flowchart') 20 | }) 21 | it('should handle a graph defintion for gitGraph', function () { 22 | const str = ' \n gitGraph TB:\nbfs1:queue' 23 | const type = utils.detectType(str) 24 | expect(type).toBe('git') 25 | }) 26 | }) 27 | 28 | describe('when finding substring in array ', function () { 29 | it('should return the array index that contains the substring', function () { 30 | const arr = ['stroke:val1', 'fill:val2'] 31 | const result = utils.isSubstringInArray('fill', arr) 32 | expect(result).toEqual(1) 33 | }) 34 | it('should return -1 if the substring is not found in the array', function () { 35 | const arr = ['stroke:val1', 'stroke-width:val2'] 36 | const result = utils.isSubstringInArray('fill', arr) 37 | expect(result).toEqual(-1) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /assets/diagrams/gantt/gantt.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import { parser } from './parser/gantt' 3 | import ganttDb from './ganttDb' 4 | 5 | describe('when parsing a gantt diagram it', function () { 6 | beforeEach(function () { 7 | parser.yy = ganttDb 8 | }) 9 | 10 | it('should handle a dateFormat definition', function () { 11 | const str = 'gantt\ndateFormat yyyy-mm-dd' 12 | 13 | parser.parse(str) 14 | }) 15 | it('should handle a title definition', function () { 16 | const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid' 17 | 18 | parser.parse(str) 19 | }) 20 | it('should handle a section definition', function () { 21 | const str = 'gantt\n' + 22 | 'dateFormat yyyy-mm-dd\n' + 23 | 'title Adding gantt diagram functionality to mermaid\n' + 24 | 'section Documentation' 25 | 26 | parser.parse(str) 27 | }) 28 | /** 29 | * Beslutsflöde inligt nedan. Obs bla bla bla 30 | * ``` 31 | * graph TD 32 | * A[Hard pledge] -- text on link -->B(Round edge) 33 | * B --> C{to do or not to do} 34 | * C -->|Too| D[Result one] 35 | * C -->|Doo| E[Result two] 36 | ``` 37 | * params bapa - a unique bapap 38 | */ 39 | it('should handle a task definition', function () { 40 | const str = 'gantt\n' + 41 | 'dateFormat yyyy-mm-dd\n' + 42 | 'title Adding gantt diagram functionality to mermaid\n' + 43 | 'section Documentation\n' + 44 | 'Design jison grammar:des1, 2014-01-01, 2014-01-04' 45 | 46 | parser.parse(str) 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /assets/themes/neutral/index.scss: -------------------------------------------------------------------------------- 1 | $mainBkg: #eee; 2 | $contrast: #26a; 3 | $secondBkg: lighten($contrast, 55%); 4 | $lineColor: #666; 5 | $border1: #999; 6 | $border2: $contrast; 7 | $note: #ffa; 8 | $text: #333; 9 | $critical: #d42; 10 | $done: #bbb; 11 | $arrowheadColor: #333333; 12 | 13 | /* Flowchart variables */ 14 | 15 | $nodeBkg: $mainBkg; 16 | $nodeBorder: $border1; 17 | $clusterBkg: $secondBkg; 18 | $clusterBorder: $border2; 19 | $defaultLinkColor: $lineColor; 20 | $titleColor: $text; 21 | $edgeLabelBackground: white; 22 | 23 | /* Sequence Diagram variables */ 24 | 25 | $actorBorder: $border1; 26 | $actorBkg: $mainBkg; 27 | $actorTextColor: $text; 28 | $actorLineColor: $lineColor; 29 | $signalColor: $text; 30 | $signalTextColor: $text; 31 | $labelBoxBkgColor: $actorBkg; 32 | $labelBoxBorderColor: $actorBorder; 33 | $labelTextColor: white; 34 | $noteBorderColor: darken($note, 60%); 35 | $noteBkgColor: $note; 36 | 37 | /* Gantt chart variables */ 38 | 39 | $sectionBkgColor: lighten($contrast, 30%); 40 | $altSectionBkgColor: white; 41 | $sectionBkgColor2: lighten($contrast, 30%); 42 | $taskBorderColor: darken($contrast, 10%); 43 | $taskBkgColor: $contrast; 44 | $taskTextLightColor: white; 45 | $taskTextColor: $taskTextLightColor; 46 | $taskTextDarkColor: $text; 47 | $taskTextOutsideColor: $taskTextDarkColor; 48 | $activeTaskBorderColor: $taskBorderColor; 49 | $activeTaskBkgColor: $mainBkg; 50 | $gridColor: lighten($border1, 30%); 51 | $doneTaskBkgColor: $done; 52 | $doneTaskBorderColor: $lineColor; 53 | $critBkgColor: $critical; 54 | $critBorderColor: darken($critBkgColor, 10%); 55 | $todayLineColor: $critBkgColor; 56 | 57 | @import '../mermaid'; 58 | -------------------------------------------------------------------------------- /assets/themes/dark/index.scss: -------------------------------------------------------------------------------- 1 | $mainBkg: #BDD5EA; 2 | $secondBkg: #6D6D65; 3 | $mainContrastColor: lightgrey; 4 | $darkTextColor: #323D47; 5 | $lineColor: $mainContrastColor; 6 | $border1: #81B1DB; 7 | $border2: rgba(255, 255, 255, 0.25); 8 | $arrowheadColor: $mainContrastColor; 9 | 10 | /* Flowchart variables */ 11 | 12 | $nodeBkg: $mainBkg; 13 | $nodeBorder: purple; 14 | $clusterBkg: $secondBkg; 15 | $clusterBorder: $border2; 16 | $defaultLinkColor: $lineColor; 17 | $titleColor: #F9FFFE; 18 | $edgeLabelBackground: #e8e8e8; 19 | 20 | /* Sequence Diagram variables */ 21 | 22 | $actorBorder: $border1; 23 | $actorBkg: $mainBkg; 24 | $actorTextColor: black; 25 | $actorLineColor: $mainContrastColor; 26 | $signalColor: $mainContrastColor; 27 | $signalTextColor: $mainContrastColor; 28 | $labelBoxBkgColor: $actorBkg; 29 | $labelBoxBorderColor: $actorBorder; 30 | $labelTextColor: $mainContrastColor; 31 | $noteBorderColor: $border2; 32 | $noteBkgColor: #fff5ad; 33 | 34 | /* Gantt chart variables */ 35 | 36 | $sectionBkgColor: rgba(255, 255, 255, 0.3); 37 | $altSectionBkgColor: white; 38 | $sectionBkgColor2: #EAE8B9; 39 | $taskBorderColor: rgba(255, 255, 255, 0.5); 40 | $taskBkgColor: $mainBkg; 41 | $taskTextColor: $darkTextColor; 42 | $taskTextLightColor: $mainContrastColor; 43 | $taskTextOutsideColor: $taskTextLightColor; 44 | $activeTaskBorderColor: rgba(255, 255, 255, 0.5); 45 | $activeTaskBkgColor: #81B1DB; 46 | $gridColor: $mainContrastColor; 47 | $doneTaskBkgColor: $mainContrastColor; 48 | $doneTaskBorderColor: grey; 49 | $critBorderColor: #E83737; 50 | $critBkgColor: #E83737; 51 | $taskTextDarkColor: $darkTextColor; 52 | $todayLineColor: #DB5757; 53 | 54 | @import '../mermaid'; 55 | -------------------------------------------------------------------------------- /assets/utils.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3' 2 | 3 | /** 4 | * @function detectType 5 | * Detects the type of the graph text. 6 | * ```mermaid 7 | * graph LR 8 | * a-->b 9 | * b-->c 10 | * c-->d 11 | * d-->e 12 | * e-->f 13 | * f-->g 14 | * g-->h 15 | * ``` 16 | * 17 | * @param {string} text The text defining the graph 18 | * @returns {string} A graph definition key 19 | */ 20 | export const detectType = function (text) { 21 | text = text.replace(/^\s*%%.*\n/g, '\n') 22 | if (text.match(/^\s*sequenceDiagram/)) { 23 | return 'sequence' 24 | } 25 | 26 | if (text.match(/^\s*gantt/)) { 27 | return 'gantt' 28 | } 29 | 30 | if (text.match(/^\s*classDiagram/)) { 31 | return 'class' 32 | } 33 | 34 | if (text.match(/^\s*gitGraph/)) { 35 | return 'git' 36 | } 37 | return 'flowchart' 38 | } 39 | 40 | /** 41 | * @function isSubstringInArray 42 | * Detects whether a substring in present in a given array 43 | * @param {string} str The substring to detect 44 | * @param {array} arr The array to search 45 | * @returns {number} the array index containing the substring or -1 if not present 46 | **/ 47 | export const isSubstringInArray = function (str, arr) { 48 | for (let i = 0; i < arr.length; i++) { 49 | if (arr[i].match(str)) return i 50 | } 51 | return -1 52 | } 53 | 54 | export const interpolateToCurve = (interpolate, defaultCurve) => { 55 | if (!interpolate) { 56 | return defaultCurve 57 | } 58 | const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}` 59 | return d3[curveName] || defaultCurve 60 | } 61 | 62 | export default { 63 | detectType, 64 | isSubstringInArray, 65 | interpolateToCurve 66 | } 67 | -------------------------------------------------------------------------------- /assets/mermaidAPI.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import mermaidAPI from './mermaidAPI' 3 | 4 | describe('when using mermaidAPI and ', function () { 5 | describe('doing initialize ', function () { 6 | beforeEach(function () { 7 | document.body.innerHTML = '' 8 | }) 9 | 10 | it('should copy a literal into the configuration', function () { 11 | const orgConfig = mermaidAPI.getConfig() 12 | expect(orgConfig.testLiteral).toBe(undefined) 13 | 14 | mermaidAPI.initialize({ 'testLiteral': true }) 15 | const config = mermaidAPI.getConfig() 16 | 17 | expect(config.testLiteral).toBe(true) 18 | }) 19 | it('should copy a an object into the configuration', function () { 20 | const orgConfig = mermaidAPI.getConfig() 21 | expect(orgConfig.testObject).toBe(undefined) 22 | 23 | const object = { 24 | test1: 1, 25 | test2: false 26 | } 27 | 28 | mermaidAPI.initialize({ 'testObject': object }) 29 | mermaidAPI.initialize({ 'testObject': { 'test3': true } }) 30 | const config = mermaidAPI.getConfig() 31 | 32 | expect(config.testObject.test1).toBe(1) 33 | expect(config.testObject.test2).toBe(false) 34 | expect(config.testObject.test3).toBe(true) 35 | }) 36 | }) 37 | describe('checking validity of input ', function () { 38 | it('it should throw for an invalid definiton', function () { 39 | expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow() 40 | }) 41 | it('it should not throw for a valid definiton', function () { 42 | expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow() 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /assets/diagrams/gantt/parser/gantt.jison: -------------------------------------------------------------------------------- 1 | /** mermaid 2 | * https://mermaidjs.github.io/ 3 | * (c) 2015 Knut Sveidqvist 4 | * MIT license. 5 | */ 6 | %lex 7 | 8 | %options case-insensitive 9 | 10 | %{ 11 | // Pre-lexer code can go here 12 | %} 13 | 14 | %% 15 | 16 | [\n]+ return 'NL'; 17 | \s+ /* skip whitespace */ 18 | \#[^\n]* /* skip comments */ 19 | \%%[^\n]* /* skip comments */ 20 | "gantt" return 'gantt'; 21 | "dateFormat"\s[^#\n;]+ return 'dateFormat'; 22 | "axisFormat"\s[^#\n;]+ return 'axisFormat'; 23 | \d\d\d\d"-"\d\d"-"\d\d return 'date'; 24 | "title"\s[^#\n;]+ return 'title'; 25 | "section"\s[^#:\n;]+ return 'section'; 26 | [^#:\n;]+ return 'taskTxt'; 27 | ":"[^#\n;]+ return 'taskData'; 28 | ":" return ':'; 29 | <<EOF>> return 'EOF'; 30 | . return 'INVALID'; 31 | 32 | /lex 33 | 34 | %left '^' 35 | 36 | %start start 37 | 38 | %% /* language grammar */ 39 | 40 | start 41 | : gantt document 'EOF' { return $2; } 42 | ; 43 | 44 | document 45 | : /* empty */ { $$ = [] } 46 | | document line {$1.push($2);$$ = $1} 47 | ; 48 | 49 | line 50 | : SPACE statement { $$ = $2 } 51 | | statement { $$ = $1 } 52 | | NL { $$=[];} 53 | | EOF { $$=[];} 54 | ; 55 | 56 | statement 57 | : 'dateFormat' {yy.setDateFormat($1.substr(11));$$=$1.substr(11);} 58 | | 'axisFormat' {yy.setAxisFormat($1.substr(11));$$=$1.substr(11);} 59 | | title {yy.setTitle($1.substr(6));$$=$1.substr(6);} 60 | | section {yy.addSection($1.substr(8));$$=$1.substr(8);} 61 | | taskTxt taskData {yy.addTask($1,$2);$$='task';} 62 | ; 63 | 64 | %% 65 | -------------------------------------------------------------------------------- /assets/diagrams/class/classDb.js: -------------------------------------------------------------------------------- 1 | 2 | import { logger } from '../../logger' 3 | 4 | let relations = [] 5 | let classes = {} 6 | 7 | /** 8 | * Function called by parser when a node definition has been found. 9 | * @param id 10 | * @param text 11 | * @param type 12 | * @param style 13 | */ 14 | export const addClass = function (id) { 15 | if (typeof classes[id] === 'undefined') { 16 | classes[id] = { 17 | id: id, 18 | methods: [], 19 | members: [] 20 | } 21 | } 22 | } 23 | 24 | export const clear = function () { 25 | relations = [] 26 | classes = {} 27 | } 28 | 29 | export const getClass = function (id) { 30 | return classes[id] 31 | } 32 | export const getClasses = function () { 33 | return classes 34 | } 35 | 36 | export const getRelations = function () { 37 | return relations 38 | } 39 | 40 | export const addRelation = function (relation) { 41 | logger.debug('Adding relation: ' + JSON.stringify(relation)) 42 | addClass(relation.id1) 43 | addClass(relation.id2) 44 | relations.push(relation) 45 | } 46 | 47 | export const addMembers = function (className, MembersArr) { 48 | const theClass = classes[className] 49 | if (typeof MembersArr === 'string') { 50 | if (MembersArr.substr(-1) === ')') { 51 | theClass.methods.push(MembersArr) 52 | } else { 53 | theClass.members.push(MembersArr) 54 | } 55 | } 56 | } 57 | 58 | export const cleanupLabel = function (label) { 59 | if (label.substring(0, 1) === ':') { 60 | return label.substr(2).trim() 61 | } else { 62 | return label.trim() 63 | } 64 | } 65 | 66 | export const lineType = { 67 | LINE: 0, 68 | DOTTED_LINE: 1 69 | } 70 | 71 | export const relationType = { 72 | AGGREGATION: 0, 73 | EXTENSION: 1, 74 | COMPOSITION: 2, 75 | DEPENDENCY: 3 76 | } 77 | 78 | export default { 79 | addClass, 80 | clear, 81 | getClass, 82 | getClasses, 83 | getRelations, 84 | addRelation, 85 | addMembers, 86 | cleanupLabel, 87 | lineType, 88 | relationType 89 | } 90 | -------------------------------------------------------------------------------- /assets/diagrams/git/parser/gitGraph.jison: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Parse following 4 | * gitGraph: 5 | * commit 6 | * commit 7 | * branch 8 | */ 9 | %lex 10 | 11 | %x string 12 | %x options 13 | %options case-insensitive 14 | 15 | %% 16 | 17 | (\r?\n)+ return 'NL'; 18 | \s+ /* skip all whitespace */ 19 | \#[^\n]* /* skip comments */ 20 | \%%[^\n]* /* skip comments */ 21 | "gitGraph" return 'GG'; 22 | "commit" return 'COMMIT'; 23 | "branch" return 'BRANCH'; 24 | "merge" return 'MERGE'; 25 | "reset" return 'RESET'; 26 | "checkout" return 'CHECKOUT'; 27 | "LR" return 'DIR'; 28 | "BT" return 'DIR'; 29 | ":" return ':'; 30 | "^" return 'CARET' 31 | "options"\r?\n this.begin("options"); 32 | <options>"end"\r?\n this.popState(); 33 | <options>[^\n]+\r?\n return 'OPT'; 34 | ["] this.begin("string"); 35 | <string>["] this.popState(); 36 | <string>[^"]* return 'STR'; 37 | [a-zA-Z][a-zA-Z0-9_]+ return 'ID'; 38 | <<EOF>> return 'EOF'; 39 | 40 | /lex 41 | 42 | %left '^' 43 | 44 | %start start 45 | 46 | %% /* language grammar */ 47 | 48 | start 49 | : GG ':' document EOF{ return $3; } 50 | | GG DIR ':' document EOF {yy.setDirection($2); return $4;} 51 | ; 52 | 53 | 54 | document 55 | : /*empty*/ 56 | | options body { yy.setOptions($1); $$ = $2} 57 | ; 58 | 59 | options 60 | : options OPT {$1 +=$2; $$=$1} 61 | | NL 62 | ; 63 | body 64 | : /*emmpty*/ {$$ = []} 65 | | body line {$1.push($2); $$=$1;} 66 | ; 67 | line 68 | : statement NL{$$ =$1} 69 | | NL 70 | ; 71 | 72 | statement 73 | : COMMIT commit_arg {yy.commit($2)} 74 | | BRANCH ID {yy.branch($2)} 75 | | CHECKOUT ID {yy.checkout($2)} 76 | | MERGE ID {yy.merge($2)} 77 | | RESET reset_arg {yy.reset($2)} 78 | ; 79 | 80 | commit_arg 81 | : /* empty */ {$$ = ""} 82 | | STR {$$=$1} 83 | ; 84 | 85 | reset_arg 86 | : 'HEAD' reset_parents{$$ = $1+ ":" + $2 } 87 | | ID reset_parents{$$ = $1+ ":" + yy.count; yy.count = 0} 88 | ; 89 | reset_parents 90 | : /* empty */ {yy.count = 0} 91 | | CARET reset_parents { yy.count += 1 } 92 | ; 93 | -------------------------------------------------------------------------------- /assets/themes/gantt.scss: -------------------------------------------------------------------------------- 1 | /** Section styling */ 2 | 3 | .section { 4 | stroke: none; 5 | opacity: 0.2; 6 | } 7 | 8 | .section0 { 9 | fill: $sectionBkgColor; 10 | } 11 | 12 | .section2 { 13 | fill: $sectionBkgColor2; 14 | } 15 | 16 | .section1, 17 | .section3 { 18 | fill: $altSectionBkgColor; 19 | opacity: 0.2; 20 | } 21 | 22 | .sectionTitle0 { 23 | fill: $titleColor; 24 | } 25 | 26 | .sectionTitle1 { 27 | fill: $titleColor; 28 | } 29 | 30 | .sectionTitle2 { 31 | fill: $titleColor; 32 | } 33 | 34 | .sectionTitle3 { 35 | fill: $titleColor; 36 | } 37 | 38 | .sectionTitle { 39 | text-anchor: start; 40 | font-size: 11px; 41 | text-height: 14px; 42 | } 43 | 44 | 45 | /* Grid and axis */ 46 | 47 | .grid .tick { 48 | stroke: $gridColor; 49 | opacity: 0.3; 50 | shape-rendering: crispEdges; 51 | } 52 | 53 | .grid path { 54 | stroke-width: 0; 55 | } 56 | 57 | 58 | /* Today line */ 59 | 60 | .today { 61 | fill: none; 62 | stroke: $todayLineColor; 63 | stroke-width: 2px; 64 | } 65 | 66 | 67 | /* Task styling */ 68 | 69 | 70 | /* Default task */ 71 | 72 | .task { 73 | stroke-width: 2; 74 | } 75 | 76 | .taskText { 77 | text-anchor: middle; 78 | font-size: 11px; 79 | } 80 | 81 | .taskTextOutsideRight { 82 | fill: $taskTextDarkColor; 83 | text-anchor: start; 84 | font-size: 11px; 85 | } 86 | 87 | .taskTextOutsideLeft { 88 | fill: $taskTextDarkColor; 89 | text-anchor: end; 90 | font-size: 11px; 91 | } 92 | 93 | 94 | /* Specific task settings for the sections*/ 95 | 96 | .taskText0, 97 | .taskText1, 98 | .taskText2, 99 | .taskText3 { 100 | fill: $taskTextColor; 101 | } 102 | 103 | .task0, 104 | .task1, 105 | .task2, 106 | .task3 { 107 | fill: $taskBkgColor; 108 | stroke: $taskBorderColor; 109 | } 110 | 111 | .taskTextOutside0, 112 | .taskTextOutside2, 113 | { 114 | fill: $taskTextOutsideColor; 115 | } 116 | 117 | .taskTextOutside1, 118 | .taskTextOutside3 { 119 | fill: $taskTextOutsideColor; 120 | } 121 | 122 | 123 | /* Active task */ 124 | 125 | .active0, 126 | .active1, 127 | .active2, 128 | .active3 { 129 | fill: $activeTaskBkgColor; 130 | stroke: $activeTaskBorderColor; 131 | } 132 | 133 | .activeText0, 134 | .activeText1, 135 | .activeText2, 136 | .activeText3 { 137 | fill: $taskTextDarkColor !important; 138 | } 139 | 140 | 141 | /* Completed task */ 142 | 143 | .done0, 144 | .done1, 145 | .done2, 146 | .done3 { 147 | stroke: $doneTaskBorderColor; 148 | fill: $doneTaskBkgColor; 149 | stroke-width: 2; 150 | } 151 | 152 | .doneText0, 153 | .doneText1, 154 | .doneText2, 155 | .doneText3 { 156 | fill: $taskTextDarkColor !important; 157 | } 158 | 159 | 160 | /* Tasks on the critical line */ 161 | 162 | .crit0, 163 | .crit1, 164 | .crit2, 165 | .crit3 { 166 | stroke: $critBorderColor; 167 | fill: $critBkgColor; 168 | stroke-width: 2; 169 | } 170 | 171 | .activeCrit0, 172 | .activeCrit1, 173 | .activeCrit2, 174 | .activeCrit3 { 175 | stroke: $critBorderColor; 176 | fill: $activeTaskBkgColor; 177 | stroke-width: 2; 178 | } 179 | 180 | .doneCrit0, 181 | .doneCrit1, 182 | .doneCrit2, 183 | .doneCrit3 { 184 | stroke: $critBorderColor; 185 | fill: $doneTaskBkgColor; 186 | stroke-width: 2; 187 | cursor: pointer; 188 | shape-rendering: crispEdges; 189 | } 190 | 191 | .doneCritText0, 192 | .doneCritText1, 193 | .doneCritText2, 194 | .doneCritText3 { 195 | fill: $taskTextDarkColor !important; 196 | } 197 | 198 | .activeCritText0, 199 | .activeCritText1, 200 | .activeCritText2, 201 | .activeCritText3 { 202 | fill: $taskTextDarkColor !important; 203 | } 204 | 205 | .titleText { 206 | text-anchor: middle; 207 | font-size: 18px; 208 | fill: $taskTextDarkColor; 209 | } 210 | -------------------------------------------------------------------------------- /assets/diagrams/sequence/sequenceDb.js: -------------------------------------------------------------------------------- 1 | import { logger } from '../../logger' 2 | 3 | let actors = {} 4 | let messages = [] 5 | const notes = [] 6 | let title = '' 7 | 8 | export const addActor = function (id, name, description) { 9 | // Don't allow description nulling 10 | const old = actors[id] 11 | if (old && name === old.name && description == null) return 12 | 13 | // Don't allow null descriptions, either 14 | if (description == null) description = name 15 | 16 | actors[id] = { name: name, description: description } 17 | } 18 | 19 | export const addMessage = function (idFrom, idTo, message, answer) { 20 | messages.push({ from: idFrom, to: idTo, message: message, answer: answer }) 21 | } 22 | 23 | export const addSignal = function (idFrom, idTo, message, messageType) { 24 | logger.debug('Adding message from=' + idFrom + ' to=' + idTo + ' message=' + message + ' type=' + messageType) 25 | messages.push({ from: idFrom, to: idTo, message: message, type: messageType }) 26 | } 27 | 28 | export const getMessages = function () { 29 | return messages 30 | } 31 | 32 | export const getActors = function () { 33 | return actors 34 | } 35 | export const getActor = function (id) { 36 | return actors[id] 37 | } 38 | export const getActorKeys = function () { 39 | return Object.keys(actors) 40 | } 41 | export const getTitle = function () { 42 | return title 43 | } 44 | 45 | export const clear = function () { 46 | actors = {} 47 | messages = [] 48 | } 49 | 50 | export const LINETYPE = { 51 | SOLID: 0, 52 | DOTTED: 1, 53 | NOTE: 2, 54 | SOLID_CROSS: 3, 55 | DOTTED_CROSS: 4, 56 | SOLID_OPEN: 5, 57 | DOTTED_OPEN: 6, 58 | LOOP_START: 10, 59 | LOOP_END: 11, 60 | ALT_START: 12, 61 | ALT_ELSE: 13, 62 | ALT_END: 14, 63 | OPT_START: 15, 64 | OPT_END: 16, 65 | ACTIVE_START: 17, 66 | ACTIVE_END: 18, 67 | PAR_START: 19, 68 | PAR_AND: 20, 69 | PAR_END: 21 70 | } 71 | 72 | export const ARROWTYPE = { 73 | FILLED: 0, 74 | OPEN: 1 75 | } 76 | 77 | export const PLACEMENT = { 78 | LEFTOF: 0, 79 | RIGHTOF: 1, 80 | OVER: 2 81 | } 82 | 83 | export const addNote = function (actor, placement, message) { 84 | const note = { actor: actor, placement: placement, message: message } 85 | 86 | // Coerce actor into a [to, from, ...] array 87 | const actors = [].concat(actor, actor) 88 | 89 | notes.push(note) 90 | messages.push({ from: actors[0], to: actors[1], message: message, type: LINETYPE.NOTE, placement: placement }) 91 | } 92 | 93 | export const setTitle = function (titleText) { 94 | title = titleText 95 | } 96 | 97 | export const apply = function (param) { 98 | if (param instanceof Array) { 99 | param.forEach(function (item) { 100 | apply(item) 101 | }) 102 | } else { 103 | switch (param.type) { 104 | case 'addActor': 105 | addActor(param.actor, param.actor, param.description) 106 | break 107 | case 'activeStart': 108 | addSignal(param.actor, undefined, undefined, param.signalType) 109 | break 110 | case 'activeEnd': 111 | addSignal(param.actor, undefined, undefined, param.signalType) 112 | break 113 | case 'addNote': 114 | addNote(param.actor, param.placement, param.text) 115 | break 116 | case 'addMessage': 117 | addSignal(param.from, param.to, param.msg, param.signalType) 118 | break 119 | case 'loopStart': 120 | addSignal(undefined, undefined, param.loopText, param.signalType) 121 | break 122 | case 'loopEnd': 123 | addSignal(undefined, undefined, undefined, param.signalType) 124 | break 125 | case 'optStart': 126 | addSignal(undefined, undefined, param.optText, param.signalType) 127 | break 128 | case 'optEnd': 129 | addSignal(undefined, undefined, undefined, param.signalType) 130 | break 131 | case 'altStart': 132 | addSignal(undefined, undefined, param.altText, param.signalType) 133 | break 134 | case 'else': 135 | addSignal(undefined, undefined, param.altText, param.signalType) 136 | break 137 | case 'altEnd': 138 | addSignal(undefined, undefined, undefined, param.signalType) 139 | break 140 | case 'setTitle': 141 | setTitle(param.text) 142 | break 143 | case 'parStart': 144 | addSignal(undefined, undefined, param.parText, param.signalType) 145 | break 146 | case 'and': 147 | addSignal(undefined, undefined, param.parText, param.signalType) 148 | break 149 | case 'parEnd': 150 | addSignal(undefined, undefined, undefined, param.signalType) 151 | break 152 | } 153 | } 154 | } 155 | 156 | export default { 157 | addActor, 158 | addMessage, 159 | addSignal, 160 | getMessages, 161 | getActors, 162 | getActor, 163 | getActorKeys, 164 | getTitle, 165 | clear, 166 | LINETYPE, 167 | ARROWTYPE, 168 | PLACEMENT, 169 | addNote, 170 | setTitle, 171 | apply 172 | } 173 | -------------------------------------------------------------------------------- /assets/mermaid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid functionality and to render 3 | * the diagrams to svg code. 4 | */ 5 | import he from 'he' 6 | 7 | import mermaidAPI from './mermaidAPI' 8 | import { logger } from './logger' 9 | 10 | /** 11 | * ## init 12 | * Function that goes through the document to find the chart definitions in there and render them. 13 | * 14 | * The function tags the processed attributes with the attribute data-processed and ignores found elements with the 15 | * attribute already set. This way the init function can be triggered several times. 16 | * 17 | * Optionally, `init` can accept in the second argument one of the following: 18 | * - a DOM Node 19 | * - an array of DOM nodes (as would come from a jQuery selector) 20 | * - a W3C selector, a la `.mermaid` 21 | * 22 | * ```mermaid 23 | * graph LR; 24 | * a(Find elements)-->b{Processed} 25 | * b-->|Yes|c(Leave element) 26 | * b-->|No |d(Transform) 27 | * ``` 28 | * Renders the mermaid diagrams 29 | * @param nodes a css selector or an array of nodes 30 | */ 31 | const init = function () { 32 | const conf = mermaidAPI.getConfig() 33 | logger.debug('Starting rendering diagrams') 34 | let nodes 35 | if (arguments.length >= 2) { 36 | /*! sequence config was passed as #1 */ 37 | if (typeof arguments[0] !== 'undefined') { 38 | mermaid.sequenceConfig = arguments[0] 39 | } 40 | 41 | nodes = arguments[1] 42 | } else { 43 | nodes = arguments[0] 44 | } 45 | 46 | // if last argument is a function this is the callback function 47 | let callback 48 | if (typeof arguments[arguments.length - 1] === 'function') { 49 | callback = arguments[arguments.length - 1] 50 | logger.debug('Callback function found') 51 | } else { 52 | if (typeof conf.mermaid !== 'undefined') { 53 | if (typeof conf.mermaid.callback === 'function') { 54 | callback = conf.mermaid.callback 55 | logger.debug('Callback function found') 56 | } else { 57 | logger.debug('No Callback function found') 58 | } 59 | } 60 | } 61 | nodes = nodes === undefined ? document.querySelectorAll('.mermaid') 62 | : typeof nodes === 'string' ? document.querySelectorAll(nodes) 63 | : nodes instanceof window.Node ? [nodes] 64 | : nodes // Last case - sequence config was passed pick next 65 | 66 | logger.debug('Start On Load before: ' + mermaid.startOnLoad) 67 | if (typeof mermaid.startOnLoad !== 'undefined') { 68 | logger.debug('Start On Load inner: ' + mermaid.startOnLoad) 69 | mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad }) 70 | } 71 | 72 | if (typeof mermaid.ganttConfig !== 'undefined') { 73 | mermaidAPI.initialize({ gantt: mermaid.ganttConfig }) 74 | } 75 | 76 | let txt 77 | 78 | for (let i = 0; i < nodes.length; i++) { 79 | const element = nodes[i] 80 | 81 | /*! Check if previously processed */ 82 | if (!element.getAttribute('data-processed')) { 83 | element.setAttribute('data-processed', true) 84 | } else { 85 | continue 86 | } 87 | 88 | const id = `mermaid-${Date.now()}` 89 | 90 | // Fetch the graph definition including tags 91 | txt = element.innerHTML 92 | 93 | // transforms the html to pure text 94 | txt = he.decode(txt).trim().replace(/<br>/ig, '<br/>') 95 | 96 | mermaidAPI.render(id, txt, (svgCode, bindFunctions) => { 97 | element.innerHTML = svgCode 98 | if (typeof callback !== 'undefined') { 99 | callback(id) 100 | } 101 | bindFunctions(element) 102 | }, element) 103 | } 104 | } 105 | 106 | const initialize = function (config) { 107 | logger.debug('Initializing mermaid') 108 | if (typeof config.mermaid !== 'undefined') { 109 | if (typeof config.mermaid.startOnLoad !== 'undefined') { 110 | mermaid.startOnLoad = config.mermaid.startOnLoad 111 | } 112 | if (typeof config.mermaid.htmlLabels !== 'undefined') { 113 | mermaid.htmlLabels = config.mermaid.htmlLabels 114 | } 115 | } 116 | mermaidAPI.initialize(config) 117 | } 118 | 119 | /** 120 | * ##contentLoaded 121 | * Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and 122 | * calls init for rendering the mermaid diagrams on the page. 123 | */ 124 | const contentLoaded = function () { 125 | let config 126 | 127 | if (mermaid.startOnLoad) { 128 | // No config found, do check API config 129 | config = mermaidAPI.getConfig() 130 | if (config.startOnLoad) { 131 | mermaid.init() 132 | } 133 | } else { 134 | if (typeof mermaid.startOnLoad === 'undefined') { 135 | logger.debug('In start, no config') 136 | config = mermaidAPI.getConfig() 137 | if (config.startOnLoad) { 138 | mermaid.init() 139 | } 140 | } 141 | } 142 | } 143 | 144 | if (typeof document !== 'undefined') { 145 | /*! 146 | * Wait for document loaded before starting the execution 147 | */ 148 | window.addEventListener('load', function () { 149 | contentLoaded() 150 | }, false) 151 | } 152 | 153 | const mermaid = { 154 | startOnLoad: true, 155 | htmlLabels: true, 156 | 157 | mermaidAPI, 158 | parse: mermaidAPI.parse, 159 | render: mermaidAPI.render, 160 | 161 | init, 162 | initialize, 163 | 164 | contentLoaded 165 | } 166 | 167 | export default mermaid 168 | -------------------------------------------------------------------------------- /assets/diagrams/sequence/parser/sequenceDiagram.jison: -------------------------------------------------------------------------------- 1 | /** mermaid 2 | * https://mermaidjs.github.io/ 3 | * (c) 2014-2015 Knut Sveidqvist 4 | * MIT license. 5 | * 6 | * Based on js sequence diagrams jison grammr 7 | * http://bramp.github.io/js-sequence-diagrams/ 8 | * (c) 2012-2013 Andrew Brampton (bramp.net) 9 | * Simplified BSD license. 10 | */ 11 | %lex 12 | 13 | %options case-insensitive 14 | 15 | // Special states for recognizing aliases 16 | %x ID 17 | %x ALIAS 18 | 19 | // A special state for grabbing text up to the first comment/newline 20 | %x LINE 21 | 22 | %% 23 | 24 | [\n]+ return 'NL'; 25 | \s+ /* skip all whitespace */ 26 | <ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */ 27 | <INITIAL,ID,ALIAS,LINE>\#[^\n]* /* skip comments */ 28 | \%%[^\n]* /* skip comments */ 29 | "participant" { this.begin('ID'); return 'participant'; } 30 | <ID>[^\->:\n,;]+?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { this.begin('ALIAS'); return 'ACTOR'; } 31 | <ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } 32 | <ALIAS>(?:) { this.popState(); this.popState(); return 'NL'; } 33 | "loop" { this.begin('LINE'); return 'loop'; } 34 | "opt" { this.begin('LINE'); return 'opt'; } 35 | "alt" { this.begin('LINE'); return 'alt'; } 36 | "else" { this.begin('LINE'); return 'else'; } 37 | "par" { this.begin('LINE'); return 'par'; } 38 | "and" { this.begin('LINE'); return 'and'; } 39 | <LINE>[^#\n;]* { this.popState(); return 'restOfLine'; } 40 | "end" return 'end'; 41 | "left of" return 'left_of'; 42 | "right of" return 'right_of'; 43 | "over" return 'over'; 44 | "note" return 'note'; 45 | "activate" { this.begin('ID'); return 'activate'; } 46 | "deactivate" { this.begin('ID'); return 'deactivate'; } 47 | "title" return 'title'; 48 | "sequenceDiagram" return 'SD'; 49 | "," return ','; 50 | ";" return 'NL'; 51 | [^\+\->:\n,;]+ { yytext = yytext.trim(); return 'ACTOR'; } 52 | "->>" return 'SOLID_ARROW'; 53 | "-->>" return 'DOTTED_ARROW'; 54 | "->" return 'SOLID_OPEN_ARROW'; 55 | "-->" return 'DOTTED_OPEN_ARROW'; 56 | \-[x] return 'SOLID_CROSS'; 57 | \-\-[x] return 'DOTTED_CROSS'; 58 | ":"[^#\n;]+ return 'TXT'; 59 | "+" return '+'; 60 | "-" return '-'; 61 | <<EOF>> return 'NL'; 62 | . return 'INVALID'; 63 | 64 | /lex 65 | 66 | %left '^' 67 | 68 | %start start 69 | 70 | %% /* language grammar */ 71 | 72 | start 73 | : SPACE start 74 | | NL start 75 | | SD document { yy.apply($2);return $2; } 76 | ; 77 | 78 | document 79 | : /* empty */ { $$ = [] } 80 | | document line {$1.push($2);$$ = $1} 81 | ; 82 | 83 | line 84 | : SPACE statement { $$ = $2 } 85 | | statement { $$ = $1 } 86 | | NL { $$=[];} 87 | ; 88 | 89 | statement 90 | : 'participant' actor 'AS' restOfLine 'NL' {$2.description=$4; $$=$2;} 91 | | 'participant' actor 'NL' {$$=$2;} 92 | | signal 'NL' 93 | | 'activate' actor 'NL' {$$={type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $2};} 94 | | 'deactivate' actor 'NL' {$$={type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $2};} 95 | | note_statement 'NL' 96 | | title text2 'NL' {$$=[{type:'setTitle', text:$2}]} 97 | | 'loop' restOfLine document end 98 | { 99 | $3.unshift({type: 'loopStart', loopText:$2, signalType: yy.LINETYPE.LOOP_START}); 100 | $3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END}); 101 | $$=$3;} 102 | | opt restOfLine document end 103 | { 104 | $3.unshift({type: 'optStart', optText:$2, signalType: yy.LINETYPE.OPT_START}); 105 | $3.push({type: 'optEnd', optText:$2, signalType: yy.LINETYPE.OPT_END}); 106 | $$=$3;} 107 | | alt restOfLine else_sections end 108 | { 109 | // Alt start 110 | $3.unshift({type: 'altStart', altText:$2, signalType: yy.LINETYPE.ALT_START}); 111 | // Content in alt is already in $3 112 | // End 113 | $3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END}); 114 | $$=$3;} 115 | | par restOfLine par_sections end 116 | { 117 | // Parallel start 118 | $3.unshift({type: 'parStart', parText:$2, signalType: yy.LINETYPE.PAR_START}); 119 | // Content in par is already in $3 120 | // End 121 | $3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END}); 122 | $$=$3;} 123 | ; 124 | 125 | par_sections 126 | : document 127 | | document and restOfLine par_sections 128 | { $$ = $1.concat([{type: 'and', parText:$3, signalType: yy.LINETYPE.PAR_AND}, $4]); } 129 | ; 130 | 131 | else_sections 132 | : document 133 | | document else restOfLine else_sections 134 | { $$ = $1.concat([{type: 'else', altText:$3, signalType: yy.LINETYPE.ALT_ELSE}, $4]); } 135 | ; 136 | 137 | note_statement 138 | : 'note' placement actor text2 139 | { 140 | $$ = [$3, {type:'addNote', placement:$2, actor:$3.actor, text:$4}];} 141 | | 'note' 'over' actor_pair text2 142 | { 143 | // Coerce actor_pair into a [to, from, ...] array 144 | $2 = [].concat($3, $3).slice(0, 2); 145 | $2[0] = $2[0].actor; 146 | $2[1] = $2[1].actor; 147 | $$ = [$3, {type:'addNote', placement:yy.PLACEMENT.OVER, actor:$2.slice(0, 2), text:$4}];} 148 | ; 149 | 150 | spaceList 151 | : SPACE spaceList 152 | | SPACE 153 | ; 154 | actor_pair 155 | : actor ',' actor { $$ = [$1, $3]; } 156 | | actor { $$ = $1; } 157 | ; 158 | 159 | placement 160 | : 'left_of' { $$ = yy.PLACEMENT.LEFTOF; } 161 | | 'right_of' { $$ = yy.PLACEMENT.RIGHTOF; } 162 | ; 163 | 164 | signal 165 | : actor signaltype '+' actor text2 166 | { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, 167 | {type: 'activeStart', signalType: yy.LINETYPE.ACTIVE_START, actor: $4} 168 | ]} 169 | | actor signaltype '-' actor text2 170 | { $$ = [$1,$4,{type: 'addMessage', from:$1.actor, to:$4.actor, signalType:$2, msg:$5}, 171 | {type: 'activeEnd', signalType: yy.LINETYPE.ACTIVE_END, actor: $1} 172 | ]} 173 | | actor signaltype actor text2 174 | { $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]} 175 | ; 176 | 177 | actor 178 | : ACTOR {$$={type: 'addActor', actor:$1}} 179 | ; 180 | 181 | signaltype 182 | : SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; } 183 | | DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; } 184 | | SOLID_ARROW { $$ = yy.LINETYPE.SOLID; } 185 | | DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; } 186 | | SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; } 187 | | DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; } 188 | ; 189 | 190 | text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ; 191 | 192 | %% 193 | -------------------------------------------------------------------------------- /assets/diagrams/git/gitGraphAst.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | import { logger } from '../../logger' 4 | 5 | let commits = {} 6 | let head = null 7 | let branches = { 'master': head } 8 | let curBranch = 'master' 9 | let direction = 'LR' 10 | let seq = 0 11 | 12 | function getRandomInt (min, max) { 13 | return Math.floor(Math.random() * (max - min)) + min 14 | } 15 | 16 | function getId () { 17 | const pool = '0123456789abcdef' 18 | let id = '' 19 | for (let i = 0; i < 7; i++) { 20 | id += pool[getRandomInt(0, 16)] 21 | } 22 | return id 23 | } 24 | 25 | function isfastforwardable (currentCommit, otherCommit) { 26 | logger.debug('Entering isfastforwardable:', currentCommit.id, otherCommit.id) 27 | while (currentCommit.seq <= otherCommit.seq && currentCommit !== otherCommit) { 28 | // only if other branch has more commits 29 | if (otherCommit.parent == null) break 30 | if (Array.isArray(otherCommit.parent)) { 31 | logger.debug('In merge commit:', otherCommit.parent) 32 | return isfastforwardable(currentCommit, commits[otherCommit.parent[0]]) || 33 | isfastforwardable(currentCommit, commits[otherCommit.parent[1]]) 34 | } else { 35 | otherCommit = commits[otherCommit.parent] 36 | } 37 | } 38 | logger.debug(currentCommit.id, otherCommit.id) 39 | return currentCommit.id === otherCommit.id 40 | } 41 | 42 | function isReachableFrom (currentCommit, otherCommit) { 43 | const currentSeq = currentCommit.seq 44 | const otherSeq = otherCommit.seq 45 | if (currentSeq > otherSeq) return isfastforwardable(otherCommit, currentCommit) 46 | return false 47 | } 48 | 49 | export const setDirection = function (dir) { 50 | direction = dir 51 | } 52 | let options = {} 53 | export const setOptions = function (rawOptString) { 54 | logger.debug('options str', rawOptString) 55 | rawOptString = rawOptString && rawOptString.trim() 56 | rawOptString = rawOptString || '{}' 57 | try { 58 | options = JSON.parse(rawOptString) 59 | } catch (e) { 60 | logger.error('error while parsing gitGraph options', e.message) 61 | } 62 | } 63 | 64 | export const getOptions = function () { 65 | return options 66 | } 67 | 68 | export const commit = function (msg) { 69 | const commit = { 70 | id: getId(), 71 | message: msg, 72 | seq: seq++, 73 | parent: head == null ? null : head.id 74 | } 75 | head = commit 76 | commits[commit.id] = commit 77 | branches[curBranch] = commit.id 78 | logger.debug('in pushCommit ' + commit.id) 79 | } 80 | 81 | export const branch = function (name) { 82 | branches[name] = head != null ? head.id : null 83 | logger.debug('in createBranch') 84 | } 85 | 86 | export const merge = function (otherBranch) { 87 | const currentCommit = commits[branches[curBranch]] 88 | const otherCommit = commits[branches[otherBranch]] 89 | if (isReachableFrom(currentCommit, otherCommit)) { 90 | logger.debug('Already merged') 91 | return 92 | } 93 | if (isfastforwardable(currentCommit, otherCommit)) { 94 | branches[curBranch] = branches[otherBranch] 95 | head = commits[branches[curBranch]] 96 | } else { 97 | // create merge commit 98 | const commit = { 99 | id: getId(), 100 | message: 'merged branch ' + otherBranch + ' into ' + curBranch, 101 | seq: seq++, 102 | parent: [head == null ? null : head.id, branches[otherBranch]] 103 | } 104 | head = commit 105 | commits[commit.id] = commit 106 | branches[curBranch] = commit.id 107 | } 108 | logger.debug(branches) 109 | logger.debug('in mergeBranch') 110 | } 111 | 112 | export const checkout = function (branch) { 113 | logger.debug('in checkout') 114 | curBranch = branch 115 | const id = branches[curBranch] 116 | head = commits[id] 117 | } 118 | 119 | export const reset = function (commitRef) { 120 | logger.debug('in reset', commitRef) 121 | const ref = commitRef.split(':')[0] 122 | let parentCount = parseInt(commitRef.split(':')[1]) 123 | let commit = ref === 'HEAD' ? head : commits[branches[ref]] 124 | logger.debug(commit, parentCount) 125 | while (parentCount > 0) { 126 | commit = commits[commit.parent] 127 | parentCount-- 128 | if (!commit) { 129 | const err = 'Critical error - unique parent commit not found during reset' 130 | logger.error(err) 131 | throw err 132 | } 133 | } 134 | head = commit 135 | branches[curBranch] = commit.id 136 | } 137 | 138 | function upsert (arr, key, newval) { 139 | const index = arr.indexOf(key) 140 | if (index === -1) { 141 | arr.push(newval) 142 | } else { 143 | arr.splice(index, 1, newval) 144 | } 145 | } 146 | 147 | function prettyPrintCommitHistory (commitArr) { 148 | const commit = _.maxBy(commitArr, 'seq') 149 | let line = '' 150 | commitArr.forEach(function (c) { 151 | if (c === commit) { 152 | line += '\t*' 153 | } else { 154 | line += '\t|' 155 | } 156 | }) 157 | const label = [line, commit.id, commit.seq] 158 | _.each(branches, function (value, key) { 159 | if (value === commit.id) label.push(key) 160 | }) 161 | logger.debug(label.join(' ')) 162 | if (Array.isArray(commit.parent)) { 163 | const newCommit = commits[commit.parent[0]] 164 | upsert(commitArr, commit, newCommit) 165 | commitArr.push(commits[commit.parent[1]]) 166 | } else if (commit.parent == null) { 167 | return 168 | } else { 169 | const nextCommit = commits[commit.parent] 170 | upsert(commitArr, commit, nextCommit) 171 | } 172 | commitArr = _.uniqBy(commitArr, 'id') 173 | prettyPrintCommitHistory(commitArr) 174 | } 175 | 176 | export const prettyPrint = function () { 177 | logger.debug(commits) 178 | const node = getCommitsArray()[0] 179 | prettyPrintCommitHistory([node]) 180 | } 181 | 182 | export const clear = function () { 183 | commits = {} 184 | head = null 185 | branches = { 'master': head } 186 | curBranch = 'master' 187 | seq = 0 188 | } 189 | 190 | export const getBranchesAsObjArray = function () { 191 | const branchArr = _.map(branches, function (value, key) { 192 | return { 'name': key, 'commit': commits[value] } 193 | }) 194 | return branchArr 195 | } 196 | 197 | export const getBranches = function () { return branches } 198 | export const getCommits = function () { return commits } 199 | export const getCommitsArray = function () { 200 | const commitArr = Object.keys(commits).map(function (key) { 201 | return commits[key] 202 | }) 203 | commitArr.forEach(function (o) { logger.debug(o.id) }) 204 | return _.orderBy(commitArr, ['seq'], ['desc']) 205 | } 206 | export const getCurrentBranch = function () { return curBranch } 207 | export const getDirection = function () { return direction } 208 | export const getHead = function () { return head } 209 | 210 | export default { 211 | setDirection, 212 | setOptions, 213 | getOptions, 214 | commit, 215 | branch, 216 | merge, 217 | checkout, 218 | reset, 219 | prettyPrint, 220 | clear, 221 | getBranchesAsObjArray, 222 | getBranches, 223 | getCommits, 224 | getCommitsArray, 225 | getCurrentBranch, 226 | getDirection, 227 | getHead 228 | } 229 | -------------------------------------------------------------------------------- /assets/diagrams/class/classDiagram.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import { parser } from './parser/classDiagram' 3 | import classDb from './classDb' 4 | 5 | describe('class diagram, ', function () { 6 | describe('when parsing an info graph it', function () { 7 | beforeEach(function () { 8 | parser.yy = classDb 9 | }) 10 | 11 | it('should handle relation definitions', function () { 12 | const str = 'classDiagram\n' + 13 | 'Class01 <|-- Class02\n' + 14 | 'Class03 *-- Class04\n' + 15 | 'Class05 o-- Class06\n' + 16 | 'Class07 .. Class08\n' + 17 | 'Class09 -- Class1' 18 | 19 | parser.parse(str) 20 | }) 21 | it('should handle relation definition of different types and directions', function () { 22 | const str = 'classDiagram\n' + 23 | 'Class11 <|.. Class12\n' + 24 | 'Class13 --> Class14\n' + 25 | 'Class15 ..> Class16\n' + 26 | 'Class17 ..|> Class18\n' + 27 | 'Class19 <--* Class20' 28 | 29 | parser.parse(str) 30 | }) 31 | 32 | it('should handle cardinality and labels', function () { 33 | const str = 'classDiagram\n' + 34 | 'Class01 "1" *-- "many" Class02 : contains\n' + 35 | 'Class03 o-- Class04 : aggregation\n' + 36 | 'Class05 --> "1" Class06' 37 | 38 | parser.parse(str) 39 | }) 40 | it('should handle class definitions', function () { 41 | const str = 'classDiagram\n' + 42 | 'class Car\n' + 43 | 'Driver -- Car : drives >\n' + 44 | 'Car *-- Wheel : have 4 >\n' + 45 | 'Car -- Person : < owns' 46 | 47 | parser.parse(str) 48 | }) 49 | 50 | it('should handle method statements', function () { 51 | const str = 'classDiagram\n' + 52 | 'Object <|-- ArrayList\n' + 53 | 'Object : equals()\n' + 54 | 'ArrayList : Object[] elementData\n' + 55 | 'ArrayList : size()' 56 | 57 | parser.parse(str) 58 | }) 59 | it('should handle parsing of method statements grouped by brackets', function () { 60 | const str = 'classDiagram\n' + 61 | 'class Dummy {\n' + 62 | 'String data\n' + 63 | ' void methods()\n' + 64 | '}\n' + 65 | '\n' + 66 | 'class Flight {\n' + 67 | ' flightNumber : Integer\n' + 68 | ' departureTime : Date\n' + 69 | '}' 70 | 71 | parser.parse(str) 72 | }) 73 | 74 | it('should handle parsing of separators', function () { 75 | const str = 'classDiagram\n' + 76 | 'class Foo1 {\n' + 77 | ' You can use\n' + 78 | ' several lines\n' + 79 | '..\n' + 80 | 'as you want\n' + 81 | 'and group\n' + 82 | '==\n' + 83 | 'things together.\n' + 84 | '__\n' + 85 | 'You can have as many groups\n' + 86 | 'as you want\n' + 87 | '--\n' + 88 | 'End of class\n' + 89 | '}\n' + 90 | '\n' + 91 | 'class User {\n' + 92 | '.. Simple Getter ..\n' + 93 | '+ getName()\n' + 94 | '+ getAddress()\n' + 95 | '.. Some setter ..\n' + 96 | '+ setName()\n' + 97 | '__ private data __\n' + 98 | 'int age\n' + 99 | '-- encrypted --\n' + 100 | 'String password\n' + 101 | '}' 102 | 103 | parser.parse(str) 104 | }) 105 | }) 106 | 107 | describe('when fetching data from an classDiagram graph it', function () { 108 | beforeEach(function () { 109 | parser.yy = classDb 110 | parser.yy.clear() 111 | }) 112 | it('should handle relation definitions EXTENSION', function () { 113 | const str = 'classDiagram\n' + 114 | 'Class01 <|-- Class02' 115 | 116 | parser.parse(str) 117 | 118 | const relations = parser.yy.getRelations() 119 | 120 | expect(parser.yy.getClass('Class01').id).toBe('Class01') 121 | expect(parser.yy.getClass('Class02').id).toBe('Class02') 122 | expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION) 123 | expect(relations[0].relation.type2).toBe('none') 124 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE) 125 | }) 126 | it('should handle relation definitions AGGREGATION and dotted line', function () { 127 | const str = 'classDiagram\n' + 128 | 'Class01 o.. Class02' 129 | 130 | parser.parse(str) 131 | 132 | const relations = parser.yy.getRelations() 133 | 134 | expect(parser.yy.getClass('Class01').id).toBe('Class01') 135 | expect(parser.yy.getClass('Class02').id).toBe('Class02') 136 | expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION) 137 | expect(relations[0].relation.type2).toBe('none') 138 | expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE) 139 | }) 140 | it('should handle relation definitions COMPOSITION on both sides', function () { 141 | const str = 'classDiagram\n' + 142 | 'Class01 *--* Class02' 143 | 144 | parser.parse(str) 145 | 146 | const relations = parser.yy.getRelations() 147 | 148 | expect(parser.yy.getClass('Class01').id).toBe('Class01') 149 | expect(parser.yy.getClass('Class02').id).toBe('Class02') 150 | expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION) 151 | expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION) 152 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE) 153 | }) 154 | it('should handle relation definitions no types', function () { 155 | const str = 'classDiagram\n' + 156 | 'Class01 -- Class02' 157 | 158 | parser.parse(str) 159 | 160 | const relations = parser.yy.getRelations() 161 | 162 | expect(parser.yy.getClass('Class01').id).toBe('Class01') 163 | expect(parser.yy.getClass('Class02').id).toBe('Class02') 164 | expect(relations[0].relation.type1).toBe('none') 165 | expect(relations[0].relation.type2).toBe('none') 166 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE) 167 | }) 168 | it('should handle relation definitions with type only on right side', function () { 169 | const str = 'classDiagram\n' + 170 | 'Class01 --|> Class02' 171 | 172 | parser.parse(str) 173 | 174 | const relations = parser.yy.getRelations() 175 | 176 | expect(parser.yy.getClass('Class01').id).toBe('Class01') 177 | expect(parser.yy.getClass('Class02').id).toBe('Class02') 178 | expect(relations[0].relation.type1).toBe('none') 179 | expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION) 180 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE) 181 | }) 182 | 183 | it('should handle multiple classes and relation definitions', function () { 184 | const str = 'classDiagram\n' + 185 | 'Class01 <|-- Class02\n' + 186 | 'Class03 *-- Class04\n' + 187 | 'Class05 o-- Class06\n' + 188 | 'Class07 .. Class08\n' + 189 | 'Class09 -- Class10' 190 | 191 | parser.parse(str) 192 | 193 | const relations = parser.yy.getRelations() 194 | 195 | expect(parser.yy.getClass('Class01').id).toBe('Class01') 196 | expect(parser.yy.getClass('Class10').id).toBe('Class10') 197 | 198 | expect(relations.length).toBe(5) 199 | 200 | expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION) 201 | expect(relations[0].relation.type2).toBe('none') 202 | expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE) 203 | expect(relations[3].relation.type1).toBe('none') 204 | expect(relations[3].relation.type2).toBe('none') 205 | expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE) 206 | }) 207 | }) 208 | }) 209 | -------------------------------------------------------------------------------- /achievibit-local.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Run Achievibit Locally 3 | authors: 4 | - Neil Kalman 5 | layout: default 6 | id: achievibit-local 7 | permalink: /achievibit-local 8 | --- 9 | 10 | # Welcome! 11 | 12 | We're so glad you're thinking about contributing to a `kibibit` open source project! If you're unsure or afraid of anything, just ask or submit the issue or pull request anyways. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contribution, and don't want a wall of rules to get in the way of that. 13 | 14 | Before contributing, we encourage you to read our **CONTRIBUTING** policy **(you are here)**, our **[LICENSE](LICENSE)**, our **[CODE OF CONDUCT](CODE_OF_CONDUCT.md)**, and our **[README](README.MD)**, all of which should be in this repository. 15 | 16 | ## Easy starting point 17 | Our list of [Easy Picks](https://github.com/Kibibit/achievibit/labels/Easy%20Pick) include specific issues that are a good starting point for new contributors. 18 | 19 | These are **easy to pick up tasks**, either because they are small in scope, or just don't involve much in-depth knowledge of the project. 20 | 21 | Based on [Jordi Boggiano](https://github.com/Seldaek)'s [blog post](https://seld.be/notes/encouraging-contributions-with-the-easy-pick-label) 22 | 23 | ## General explanation 24 | 25 | to run achievibit (locally or otherwise), you need three main things: 26 | 27 | 1. **a database** - achievibit expects to write and read data from a mongodb database. 28 | You can either create a real mongoDB, or use `monkey.js` to create a mock DB to run locally 29 | 2. **achievibit's server** - pretty self explanatory 30 | 3. **a static public ip address** - in order to connect achievibit to a github repository, you need to have a **url** you can paste into your webhook. On production, this is handled by our heroku hosting. To generate a static url, we use `ngrok` locally. This is done automatically when you run the server with the `--ngrok` command line param. 31 | 32 | ## Prerequisites 33 | 34 | ### Tools to install 35 | 36 | This project is run on `node.js`. install node.js using [their site](https://nodejs.org/en/). 37 | Besides that, nothing is required. 38 | 39 | ### Setting up a database 40 | 41 | There are 3 options for running achievibit with a database: 42 | - [run mongoDB locally](#run-mongodb-locally) 43 | - [mongoDB cloud service](#mongodb-cloud-service) 44 | - [mock mongoDB](#mock-mongodb) 45 | 46 | You can follow whichever you want, but the ocally run mongoDB is recommended since it's pretty easy to set-up and it tests your code E2E instead of using a mock DB 47 | 48 | #### Run MongoDB Locally 49 | 50 | The easiest way to do that, is to use **docker**. 51 | 52 | 1. [Install docker](https://docs.docker.com/install/) 53 | 2. Check that docker is installed by running `docker --version` in your command line 54 | 3. Create a folder on your machine in order to use that folder for the DB: `mkdir ~/data` 55 | 4. MongoDB conveniently provides us with an [official container](https://registry.hub.docker.com/_/mongo/): 56 | ```shell 57 | sudo docker run -d -p 27017:27017 -v ~/data:/data/db mongo 58 | ``` 59 | 5. At this point, you should have a MongoDB instance listening on port 27017. Its data is stored in ~/data directory of the docker host. 60 | 6. your mongoDB uri is `localhost/achievibit` 61 | 7. If you want a visual UI to see the data in your mongoDB, your can install mongoUI (`npm i -g mongoui`) and run it (`mongoui`) 62 | 63 | ## Getting Started 64 | 65 | 1. clone achievibit locally (we prefer using [ungit](https://github.com/FredrikNoren/ungit) or [gitkraken](https://www.gitkraken.com/)) 66 | 2. run `npm install` to install all dependencies 67 | 3. Now we need to run achievibit. achievibit looks for variables in the following order: 68 | 1. Command-line arguments 69 | 2. Environment variables 70 | 3. A file located at the root of the project called `privateConfig.json` 71 | You can run the command line with the `--savePrivate` argument, which will save the `privateConfig.json` file locally for you with the params you passed. 72 | Run the following command, replacing all the urls we got in the **prerequisites** step: 73 | ```shell 74 | node index.js --databaseUrl "<mongodb-url>" --ngrok 75 | ``` 76 | If you chose to [run mongoDB locally](#run-mongodb-locally), use the following command: 77 | ```shell 78 | node index.js --databaseUrl locahost/achievibit --ngrok 79 | ``` 80 | If you chose to run locally without a database, use the following command: 81 | ```shell 82 | node index.js --testDB --ngrok 83 | ``` 84 | > To set global variables: In Unixy environments: `export ngrokToken="<ngrok-token>"` || In Windows **powershell**: `$env:ngrokToken="<ngrok-token>"` 85 | 86 | 4. if everything worked, you should see the `achievibit` logo, and an **ngrok url** under it. 87 | 5. follow the instructions in the [`README.MD`](/README.MD) file to connect a test repository to your local achievibit (replace the url with your ngrok url). Make sure **Content type** is set to **application\json** 88 | > ![connect repo](/screenshots/connect-to-repo.png) 89 | 90 | 6. check that the achievement works on your database (your user got your achievement on your test repository). 91 | if your achievement requires interaction with another user, talk to one of the developers and we'll help test it (reviewing a test pull request, etc.). 92 | 93 | ## Seeing logs 94 | Other then seeing the **logs** in your `console`, you can access your logs from anywhere on `<ngrok_url>/logs`. 95 | See [Supported Params](#supported-params) for more info 96 | 97 | ## Supported params 98 | `achievibit` supports the following params: 99 | - `--databaseUrl` **OR** `--testDB` (required) - either set-up achievibit against a real DB or a mock DB 100 | - `--ngrokToken` (required) - needed in order to connect to GitHub for testing 101 | - `--logsUsername` - set a username to the logs 102 | - `--logsPassword` - set a password to the logs (without the username, this is ignored) 103 | - `--stealth` - if you don't want to print the logo in the terminal. Useful when running in `watch` mode and you don't want the server to print the achievibit logo each time it restarts 104 | - `--githubUser` - `GitHub` user to make requests with 105 | - `--githubPassword` - `GitHub` password to make requests with 106 | 107 | ## Running tests (WIP) 108 | 109 | Currently, only unit-tests are implemented. To run them locally, run `npm test` 110 | 111 | 112 | ## DataBase Appendix 113 | 114 | #### Run MongoDB Locally 115 | 116 | The easiest way to do that, is to use **docker**. 117 | 118 | 1. [Install docker](https://docs.docker.com/install/) 119 | 2. Check that docker is installed by running `docker --version` in your command line 120 | 3. Create a folder on your machine in order to use that folder for the DB: `mkdir ~/data` 121 | 4. MongoDB conveniently provides us with an [official container](https://registry.hub.docker.com/_/mongo/): 122 | ```shell 123 | sudo docker run -d -p 27017:27017 -v ~/data:/data/db mongo 124 | ``` 125 | 5. At this point, you should have a MongoDB instance listening on port 27017. Its data is stored in ~/data directory of the docker host. 126 | 6. your mongoDB uri is `localhost/achievibit` 127 | 7. If you want a visual UI to see the data in your mongoDB, your can install mongoUI (`npm i -g mongoui`) and run it (`mongoui`) 128 | 129 | #### MongoDB cloud service 130 | 131 | You can use https://mlab.com/ to create one in the cloud. 132 | If you're using `mlab`, you need to create a database, and then create a database user. 133 | After creating a database, click on Users **-->** Add database user 134 | 135 | ![Users-->Add database user](/screenshots/create-db-user.png) 136 | 137 | Now, go to your database homepage, and copy the database url. you need to enter your database username and password (**NOT YOUR MLAB ACCOUNT!**) 138 | 139 | ![mongodb url](/screenshots/mongodb-url.png) 140 | 141 | #### Mock mongoDB 142 | 143 | instead of running against a real DB, you can use our tests' mock DB with the cli param `--testDB`. This is less recommended since it means you don't test everything E2E. But it's the quickest method 144 | -------------------------------------------------------------------------------- /assets/diagrams/gantt/ganttDb.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import moment from 'moment' 3 | import ganttDb from './ganttDb' 4 | 5 | describe('when using the ganttDb', function () { 6 | beforeEach(function () { 7 | ganttDb.clear() 8 | }) 9 | 10 | it('should handle an fixed dates', function () { 11 | ganttDb.setDateFormat('YYYY-MM-DD') 12 | ganttDb.addSection('testa1') 13 | ganttDb.addTask('test1', 'id1,2013-01-01,2013-01-12') 14 | const tasks = ganttDb.getTasks() 15 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 16 | expect(tasks[0].endTime).toEqual(moment('2013-01-12', 'YYYY-MM-DD').toDate()) 17 | expect(tasks[0].id).toEqual('id1') 18 | expect(tasks[0].task).toEqual('test1') 19 | }) 20 | it('should handle duration (days) instead of fixed date to determine end date', function () { 21 | ganttDb.setDateFormat('YYYY-MM-DD') 22 | ganttDb.addSection('testa1') 23 | ganttDb.addTask('test1', 'id1,2013-01-01,2d') 24 | const tasks = ganttDb.getTasks() 25 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 26 | expect(tasks[0].endTime).toEqual(moment('2013-01-03', 'YYYY-MM-DD').toDate()) 27 | expect(tasks[0].id).toEqual('id1') 28 | expect(tasks[0].task).toEqual('test1') 29 | }) 30 | it('should handle duration (hours) instead of fixed date to determine end date', function () { 31 | ganttDb.setDateFormat('YYYY-MM-DD') 32 | ganttDb.addSection('testa1') 33 | ganttDb.addTask('test1', 'id1,2013-01-01,2h') 34 | const tasks = ganttDb.getTasks() 35 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 36 | expect(tasks[0].endTime).toEqual(moment('2013-01-01 2:00', 'YYYY-MM-DD hh:mm').toDate()) 37 | expect(tasks[0].id).toEqual('id1') 38 | expect(tasks[0].task).toEqual('test1') 39 | }) 40 | it('should handle duration (minutes) instead of fixed date to determine end date', function () { 41 | ganttDb.setDateFormat('YYYY-MM-DD') 42 | ganttDb.addSection('testa1') 43 | ganttDb.addTask('test1', 'id1,2013-01-01,2m') 44 | const tasks = ganttDb.getTasks() 45 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 46 | expect(tasks[0].endTime).toEqual(moment('2013-01-01 00:02', 'YYYY-MM-DD hh:mm').toDate()) 47 | expect(tasks[0].id).toEqual('id1') 48 | expect(tasks[0].task).toEqual('test1') 49 | }) 50 | it('should handle duration (seconds) instead of fixed date to determine end date', function () { 51 | ganttDb.setDateFormat('YYYY-MM-DD') 52 | ganttDb.addSection('testa1') 53 | ganttDb.addTask('test1', 'id1,2013-01-01,2s') 54 | const tasks = ganttDb.getTasks() 55 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 56 | expect(tasks[0].endTime).toEqual(moment('2013-01-01 00:00:02', 'YYYY-MM-DD hh:mm:ss').toDate()) 57 | expect(tasks[0].id).toEqual('id1') 58 | expect(tasks[0].task).toEqual('test1') 59 | }) 60 | it('should handle duration (weeks) instead of fixed date to determine end date', function () { 61 | ganttDb.setDateFormat('YYYY-MM-DD') 62 | ganttDb.addSection('testa1') 63 | ganttDb.addTask('test1', 'id1,2013-01-01,2w') 64 | const tasks = ganttDb.getTasks() 65 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 66 | expect(tasks[0].endTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate()) 67 | expect(tasks[0].id).toEqual('id1') 68 | expect(tasks[0].task).toEqual('test1') 69 | }) 70 | 71 | it('should handle relative start date based on id', function () { 72 | ganttDb.setDateFormat('YYYY-MM-DD') 73 | ganttDb.addSection('testa1') 74 | ganttDb.addTask('test1', 'id1,2013-01-01,2w') 75 | ganttDb.addTask('test2', 'id2,after id1,1d') 76 | 77 | const tasks = ganttDb.getTasks() 78 | 79 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate()) 80 | expect(tasks[1].id).toEqual('id2') 81 | expect(tasks[1].task).toEqual('test2') 82 | }) 83 | 84 | it('should handle relative start date based on id when id is invalid', function () { 85 | ganttDb.setDateFormat('YYYY-MM-DD') 86 | ganttDb.addSection('testa1') 87 | ganttDb.addTask('test1', 'id1,2013-01-01,2w') 88 | ganttDb.addTask('test2', 'id2,after id3,1d') 89 | const tasks = ganttDb.getTasks() 90 | expect(tasks[1].startTime).toEqual(new Date((new Date()).setHours(0, 0, 0, 0))) 91 | expect(tasks[1].id).toEqual('id2') 92 | expect(tasks[1].task).toEqual('test2') 93 | }) 94 | 95 | it('should handle fixed dates without id', function () { 96 | ganttDb.setDateFormat('YYYY-MM-DD') 97 | ganttDb.addSection('testa1') 98 | ganttDb.addTask('test1', '2013-01-01,2013-01-12') 99 | const tasks = ganttDb.getTasks() 100 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 101 | expect(tasks[0].endTime).toEqual(moment('2013-01-12', 'YYYY-MM-DD').toDate()) 102 | expect(tasks[0].id).toEqual('task1') 103 | expect(tasks[0].task).toEqual('test1') 104 | }) 105 | 106 | it('should handle duration instead of a fixed date to determine end date without id', function () { 107 | ganttDb.setDateFormat('YYYY-MM-DD') 108 | ganttDb.addSection('testa1') 109 | ganttDb.addTask('test1', '2013-01-01,4d') 110 | const tasks = ganttDb.getTasks() 111 | expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) 112 | expect(tasks[0].endTime).toEqual(moment('2013-01-05', 'YYYY-MM-DD').toDate()) 113 | expect(tasks[0].id).toEqual('task1') 114 | expect(tasks[0].task).toEqual('test1') 115 | }) 116 | 117 | it('should handle relative start date of a fixed date to determine end date without id', function () { 118 | ganttDb.setDateFormat('YYYY-MM-DD') 119 | ganttDb.addSection('testa1') 120 | ganttDb.addTask('test1', 'id1,2013-01-01,2w') 121 | ganttDb.addTask('test2', 'after id1,1d') 122 | 123 | const tasks = ganttDb.getTasks() 124 | 125 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate()) 126 | expect(tasks[1].id).toEqual('task1') 127 | expect(tasks[1].task).toEqual('test2') 128 | }) 129 | it('should handle a new task with only an end date as definition', function () { 130 | ganttDb.setDateFormat('YYYY-MM-DD') 131 | ganttDb.addSection('testa1') 132 | ganttDb.addTask('test1', 'id1,2013-01-01,2w') 133 | ganttDb.addTask('test2', '2013-01-26') 134 | 135 | const tasks = ganttDb.getTasks() 136 | 137 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate()) 138 | expect(tasks[1].endTime).toEqual(moment('2013-01-26', 'YYYY-MM-DD').toDate()) 139 | expect(tasks[1].id).toEqual('task1') 140 | expect(tasks[1].task).toEqual('test2') 141 | }) 142 | it('should handle a new task with only an end date as definition', function () { 143 | ganttDb.setDateFormat('YYYY-MM-DD') 144 | ganttDb.addSection('testa1') 145 | ganttDb.addTask('test1', 'id1,2013-01-01,2w') 146 | ganttDb.addTask('test2', '2d') 147 | 148 | const tasks = ganttDb.getTasks() 149 | 150 | expect(tasks[1].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate()) 151 | expect(tasks[1].endTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate()) 152 | expect(tasks[1].id).toEqual('task1') 153 | expect(tasks[1].task).toEqual('test2') 154 | }) 155 | it('should handle relative start date based on id regardless of sections', function () { 156 | ganttDb.setDateFormat('YYYY-MM-DD') 157 | ganttDb.addSection('testa1') 158 | ganttDb.addTask('test1', 'id1,2013-01-01,2w') 159 | ganttDb.addTask('test2', 'id2,after id3,1d') 160 | ganttDb.addSection('testa2') 161 | ganttDb.addTask('test3', 'id3,after id1,2d') 162 | 163 | const tasks = ganttDb.getTasks() 164 | 165 | expect(tasks[1].startTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate()) 166 | expect(tasks[1].endTime).toEqual(moment('2013-01-18', 'YYYY-MM-DD').toDate()) 167 | expect(tasks[1].id).toEqual('id2') 168 | expect(tasks[1].task).toEqual('test2') 169 | 170 | expect(tasks[2].id).toEqual('id3') 171 | expect(tasks[2].task).toEqual('test3') 172 | expect(tasks[2].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate()) 173 | expect(tasks[2].endTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate()) 174 | }) 175 | }) 176 | -------------------------------------------------------------------------------- /assets/diagrams/git/gitGraphParser.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import gitGraphAst from './gitGraphAst' 3 | import { parser } from './parser/gitGraph' 4 | 5 | describe('when parsing a gitGraph', function () { 6 | beforeEach(function () { 7 | parser.yy = gitGraphAst 8 | parser.yy.clear() 9 | }) 10 | it('should handle a gitGraph defintion', function () { 11 | const str = 'gitGraph:\n' + 12 | 'commit\n' 13 | 14 | parser.parse(str) 15 | const commits = parser.yy.getCommits() 16 | 17 | expect(Object.keys(commits).length).toBe(1) 18 | expect(parser.yy.getCurrentBranch()).toBe('master') 19 | expect(parser.yy.getDirection()).toBe('LR') 20 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1) 21 | }) 22 | 23 | it('should handle a gitGraph defintion with empty options', function () { 24 | const str = 'gitGraph:\n' + 25 | 'options\n' + 26 | 'end\n' + 27 | 'commit\n' 28 | 29 | parser.parse(str) 30 | const commits = parser.yy.getCommits() 31 | 32 | expect(parser.yy.getOptions()).toEqual({}) 33 | expect(Object.keys(commits).length).toBe(1) 34 | expect(parser.yy.getCurrentBranch()).toBe('master') 35 | expect(parser.yy.getDirection()).toBe('LR') 36 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1) 37 | }) 38 | 39 | it('should handle a gitGraph defintion with valid options', function () { 40 | const str = 'gitGraph:\n' + 41 | 'options\n' + 42 | '{"key": "value"}\n' + 43 | 'end\n' + 44 | 'commit\n' 45 | 46 | parser.parse(str) 47 | const commits = parser.yy.getCommits() 48 | expect(parser.yy.getOptions()['key']).toBe('value') 49 | expect(Object.keys(commits).length).toBe(1) 50 | expect(parser.yy.getCurrentBranch()).toBe('master') 51 | expect(parser.yy.getDirection()).toBe('LR') 52 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1) 53 | }) 54 | 55 | it('should not fail on a gitGraph with malformed json', function () { 56 | const str = 'gitGraph:\n' + 57 | 'options\n' + 58 | '{"key": "value"\n' + 59 | 'end\n' + 60 | 'commit\n' 61 | 62 | parser.parse(str) 63 | const commits = parser.yy.getCommits() 64 | expect(Object.keys(commits).length).toBe(1) 65 | expect(parser.yy.getCurrentBranch()).toBe('master') 66 | expect(parser.yy.getDirection()).toBe('LR') 67 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1) 68 | }) 69 | 70 | it('should handle set direction', function () { 71 | const str = 'gitGraph BT:\n' + 72 | 'commit\n' 73 | 74 | parser.parse(str) 75 | const commits = parser.yy.getCommits() 76 | 77 | expect(Object.keys(commits).length).toBe(1) 78 | expect(parser.yy.getCurrentBranch()).toBe('master') 79 | expect(parser.yy.getDirection()).toBe('BT') 80 | expect(Object.keys(parser.yy.getBranches()).length).toBe(1) 81 | }) 82 | 83 | it('should checkout a branch', function () { 84 | const str = 'gitGraph:\n' + 85 | 'branch new\n' + 86 | 'checkout new\n' 87 | 88 | parser.parse(str) 89 | const commits = parser.yy.getCommits() 90 | 91 | expect(Object.keys(commits).length).toBe(0) 92 | expect(parser.yy.getCurrentBranch()).toBe('new') 93 | }) 94 | 95 | it('should add commits to checked out branch', function () { 96 | const str = 'gitGraph:\n' + 97 | 'branch new\n' + 98 | 'checkout new\n' + 99 | 'commit\n' + 100 | 'commit\n' 101 | 102 | parser.parse(str) 103 | const commits = parser.yy.getCommits() 104 | 105 | expect(Object.keys(commits).length).toBe(2) 106 | expect(parser.yy.getCurrentBranch()).toBe('new') 107 | const branchCommit = parser.yy.getBranches()['new'] 108 | expect(branchCommit).not.toBeNull() 109 | expect(commits[branchCommit].parent).not.toBeNull() 110 | }) 111 | it('should handle commit with args', function () { 112 | const str = 'gitGraph:\n' + 113 | 'commit "a commit"\n' 114 | 115 | parser.parse(str) 116 | const commits = parser.yy.getCommits() 117 | 118 | expect(Object.keys(commits).length).toBe(1) 119 | const key = Object.keys(commits)[0] 120 | expect(commits[key].message).toBe('a commit') 121 | expect(parser.yy.getCurrentBranch()).toBe('master') 122 | }) 123 | 124 | it('it should reset a branch', function () { 125 | const str = 'gitGraph:\n' + 126 | 'commit\n' + 127 | 'commit\n' + 128 | 'branch newbranch\n' + 129 | 'checkout newbranch\n' + 130 | 'commit\n' + 131 | 'reset master\n' 132 | 133 | parser.parse(str) 134 | 135 | const commits = parser.yy.getCommits() 136 | expect(Object.keys(commits).length).toBe(3) 137 | expect(parser.yy.getCurrentBranch()).toBe('newbranch') 138 | expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']) 139 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']) 140 | }) 141 | 142 | it('reset can take an argument', function () { 143 | const str = 'gitGraph:\n' + 144 | 'commit\n' + 145 | 'commit\n' + 146 | 'branch newbranch\n' + 147 | 'checkout newbranch\n' + 148 | 'commit\n' + 149 | 'reset master^\n' 150 | 151 | parser.parse(str) 152 | 153 | const commits = parser.yy.getCommits() 154 | expect(Object.keys(commits).length).toBe(3) 155 | expect(parser.yy.getCurrentBranch()).toBe('newbranch') 156 | const master = commits[parser.yy.getBranches()['master']] 157 | expect(parser.yy.getHead().id).toEqual(master.parent) 158 | }) 159 | 160 | it('it should handle fast forwardable merges', function () { 161 | const str = 'gitGraph:\n' + 162 | 'commit\n' + 163 | 'branch newbranch\n' + 164 | 'checkout newbranch\n' + 165 | 'commit\n' + 166 | 'commit\n' + 167 | 'checkout master\n' + 168 | 'merge newbranch\n' 169 | 170 | parser.parse(str) 171 | 172 | const commits = parser.yy.getCommits() 173 | expect(Object.keys(commits).length).toBe(3) 174 | expect(parser.yy.getCurrentBranch()).toBe('master') 175 | expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']) 176 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']) 177 | }) 178 | 179 | it('it should handle cases when merge is a noop', function () { 180 | const str = 'gitGraph:\n' + 181 | 'commit\n' + 182 | 'branch newbranch\n' + 183 | 'checkout newbranch\n' + 184 | 'commit\n' + 185 | 'commit\n' + 186 | 'merge master\n' 187 | 188 | parser.parse(str) 189 | 190 | const commits = parser.yy.getCommits() 191 | expect(Object.keys(commits).length).toBe(3) 192 | expect(parser.yy.getCurrentBranch()).toBe('newbranch') 193 | expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']) 194 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']) 195 | }) 196 | 197 | it('it should handle merge with 2 parents', function () { 198 | const str = 'gitGraph:\n' + 199 | 'commit\n' + 200 | 'branch newbranch\n' + 201 | 'checkout newbranch\n' + 202 | 'commit\n' + 203 | 'commit\n' + 204 | 'checkout master\n' + 205 | 'commit\n' + 206 | 'merge newbranch\n' 207 | 208 | parser.parse(str) 209 | 210 | const commits = parser.yy.getCommits() 211 | expect(Object.keys(commits).length).toBe(5) 212 | expect(parser.yy.getCurrentBranch()).toBe('master') 213 | expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']) 214 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']) 215 | }) 216 | 217 | it('it should handle ff merge when history walk has two parents (merge commit)', function () { 218 | const str = 'gitGraph:\n' + 219 | 'commit\n' + 220 | 'branch newbranch\n' + 221 | 'checkout newbranch\n' + 222 | 'commit\n' + 223 | 'commit\n' + 224 | 'checkout master\n' + 225 | 'commit\n' + 226 | 'merge newbranch\n' + 227 | 'commit\n' + 228 | 'checkout newbranch\n' + 229 | 'merge master\n' 230 | 231 | parser.parse(str) 232 | 233 | const commits = parser.yy.getCommits() 234 | expect(Object.keys(commits).length).toBe(6) 235 | expect(parser.yy.getCurrentBranch()).toBe('newbranch') 236 | expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']) 237 | expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']) 238 | 239 | parser.yy.prettyPrint() 240 | }) 241 | }) 242 | -------------------------------------------------------------------------------- /assets/mermaid.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | import mermaid from './mermaid' 3 | import flowDb from './diagrams/flowchart/flowDb' 4 | import flowParser from './diagrams/flowchart/parser/flow' 5 | import flowRenderer from './diagrams/flowchart/flowRenderer' 6 | 7 | describe('when using mermaid and ', function () { 8 | describe('when detecting chart type ', function () { 9 | it('should not start rendering with mermaid.startOnLoad set to false', function () { 10 | mermaid.startOnLoad = false 11 | document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' 12 | spyOn(mermaid, 'init') 13 | mermaid.contentLoaded() 14 | expect(mermaid.init).not.toHaveBeenCalled() 15 | }) 16 | 17 | it('should start rendering with both startOnLoad set', function () { 18 | mermaid.startOnLoad = true 19 | document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' 20 | spyOn(mermaid, 'init') 21 | mermaid.contentLoaded() 22 | expect(mermaid.init).toHaveBeenCalled() 23 | }) 24 | 25 | it('should start rendering with mermaid.startOnLoad', function () { 26 | mermaid.startOnLoad = true 27 | document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' 28 | spyOn(mermaid, 'init') 29 | mermaid.contentLoaded() 30 | expect(mermaid.init).toHaveBeenCalled() 31 | }) 32 | 33 | it('should start rendering as a default with no changes performed', function () { 34 | document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>' 35 | spyOn(mermaid, 'init') 36 | mermaid.contentLoaded() 37 | expect(mermaid.init).toHaveBeenCalled() 38 | }) 39 | }) 40 | 41 | describe('when calling addEdges ', function () { 42 | beforeEach(function () { 43 | flowParser.parser.yy = flowDb 44 | flowDb.clear() 45 | }) 46 | it('it should handle edges with text', function () { 47 | flowParser.parser.parse('graph TD;A-->|text ex|B;') 48 | flowParser.parser.yy.getVertices() 49 | const edges = flowParser.parser.yy.getEdges() 50 | 51 | const mockG = { 52 | setEdge: function (start, end, options) { 53 | expect(start).toBe('A') 54 | expect(end).toBe('B') 55 | expect(options.arrowhead).toBe('normal') 56 | expect(options.label.match('text ex')).toBeTruthy() 57 | } 58 | } 59 | 60 | flowRenderer.addEdges(edges, mockG) 61 | }) 62 | 63 | it('should handle edges without text', function () { 64 | flowParser.parser.parse('graph TD;A-->B;') 65 | flowParser.parser.yy.getVertices() 66 | const edges = flowParser.parser.yy.getEdges() 67 | 68 | const mockG = { 69 | setEdge: function (start, end, options) { 70 | expect(start).toBe('A') 71 | expect(end).toBe('B') 72 | expect(options.arrowhead).toBe('normal') 73 | } 74 | } 75 | 76 | flowRenderer.addEdges(edges, mockG) 77 | }) 78 | 79 | it('should handle open-ended edges', function () { 80 | flowParser.parser.parse('graph TD;A---B;') 81 | flowParser.parser.yy.getVertices() 82 | const edges = flowParser.parser.yy.getEdges() 83 | 84 | const mockG = { 85 | setEdge: function (start, end, options) { 86 | expect(start).toBe('A') 87 | expect(end).toBe('B') 88 | expect(options.arrowhead).toBe('none') 89 | } 90 | } 91 | 92 | flowRenderer.addEdges(edges, mockG) 93 | }) 94 | 95 | it('should handle edges with styles defined', function () { 96 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;') 97 | flowParser.parser.yy.getVertices() 98 | const edges = flowParser.parser.yy.getEdges() 99 | 100 | const mockG = { 101 | setEdge: function (start, end, options) { 102 | expect(start).toBe('A') 103 | expect(end).toBe('B') 104 | expect(options.arrowhead).toBe('none') 105 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;') 106 | } 107 | } 108 | 109 | flowRenderer.addEdges(edges, mockG) 110 | }) 111 | it('should handle edges with interpolation defined', function () { 112 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 interpolate basis') 113 | flowParser.parser.yy.getVertices() 114 | const edges = flowParser.parser.yy.getEdges() 115 | 116 | const mockG = { 117 | setEdge: function (start, end, options) { 118 | expect(start).toBe('A') 119 | expect(end).toBe('B') 120 | expect(options.arrowhead).toBe('none') 121 | expect(options.curve).toBe('basis') // mocked as string 122 | } 123 | } 124 | 125 | flowRenderer.addEdges(edges, mockG) 126 | }) 127 | it('should handle edges with text and styles defined', function () { 128 | flowParser.parser.parse('graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;') 129 | flowParser.parser.yy.getVertices() 130 | const edges = flowParser.parser.yy.getEdges() 131 | 132 | const mockG = { 133 | setEdge: function (start, end, options) { 134 | expect(start).toBe('A') 135 | expect(end).toBe('B') 136 | expect(options.arrowhead).toBe('none') 137 | expect(options.label.match('the text')).toBeTruthy() 138 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;') 139 | } 140 | } 141 | 142 | flowRenderer.addEdges(edges, mockG) 143 | }) 144 | 145 | it('should set fill to "none" by default when handling edges', function () { 146 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;') 147 | flowParser.parser.yy.getVertices() 148 | const edges = flowParser.parser.yy.getEdges() 149 | 150 | const mockG = { 151 | setEdge: function (start, end, options) { 152 | expect(start).toBe('A') 153 | expect(end).toBe('B') 154 | expect(options.arrowhead).toBe('none') 155 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;') 156 | } 157 | } 158 | 159 | flowRenderer.addEdges(edges, mockG) 160 | }) 161 | 162 | it('should not set fill to none if fill is set in linkStyle', function () { 163 | flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;') 164 | flowParser.parser.yy.getVertices() 165 | const edges = flowParser.parser.yy.getEdges() 166 | const mockG = { 167 | setEdge: function (start, end, options) { 168 | expect(start).toBe('A') 169 | expect(end).toBe('B') 170 | expect(options.arrowhead).toBe('none') 171 | expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;') 172 | } 173 | } 174 | 175 | flowRenderer.addEdges(edges, mockG) 176 | }) 177 | }) 178 | 179 | describe('checking validity of input ', function () { 180 | it('it should throw for an invalid definiton', function () { 181 | expect(() => mermaid.parse('this is not a mermaid diagram definition')).toThrow() 182 | }) 183 | 184 | it('it should not throw for a valid flow definition', function () { 185 | expect(() => mermaid.parse('graph TD;A--x|text including URL space|B;')).not.toThrow() 186 | }) 187 | it('it should throw for an invalid flow definition', function () { 188 | expect(() => mermaid.parse('graph TQ;A--x|text including URL space|B;')).toThrow() 189 | }) 190 | 191 | it('it should not throw for a valid sequenceDiagram definition', function () { 192 | const text = 'sequenceDiagram\n' + 193 | 'Alice->Bob: Hello Bob, how are you?\n\n' + 194 | '%% Comment\n' + 195 | 'Note right of Bob: Bob thinks\n' + 196 | 'alt isWell\n\n' + 197 | 'Bob-->Alice: I am good thanks!\n' + 198 | 'else isSick\n' + 199 | 'Bob-->Alice: Feel sick...\n' + 200 | 'end' 201 | expect(() => mermaid.parse(text)).not.toThrow() 202 | }) 203 | 204 | it('it should throw for an invalid sequenceDiagram definition', function () { 205 | const text = 'sequenceDiagram\n' + 206 | 'Alice:->Bob: Hello Bob, how are you?\n\n' + 207 | '%% Comment\n' + 208 | 'Note right of Bob: Bob thinks\n' + 209 | 'alt isWell\n\n' + 210 | 'Bob-->Alice: I am good thanks!\n' + 211 | 'else isSick\n' + 212 | 'Bob-->Alice: Feel sick...\n' + 213 | 'end' 214 | expect(() => mermaid.parse(text)).toThrow() 215 | }) 216 | }) 217 | }) 218 | -------------------------------------------------------------------------------- /kibibit-bit.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "@kibibit/bit documentation" 3 | authors: 4 | - Neil Kalman 5 | layout: default 6 | id: kibibit-bit 7 | permalink: /kibibit-bit 8 | --- 9 | 10 | # @kibibit/bit 11 | 12 | @kibibit/bit is a **git-flow + GitHub** replacement for **git and git flow** 13 | 14 | git flow is great but it has much more missing to work against github considering 15 | it's a method for projects with "scheduled release cycle" 16 | 17 | Some missing things: 18 | - Work with pull requests on GitHub (for now. later we'll support more cloud gits) 19 | - Finishing a feature should open a PR instead of merging it locally to master 20 | - Initialization of a project (adding the necassery branches) 21 | - Creating a github project when `git init` a project 22 | - Integrate github with a set of corisponding tools for release and stuff 23 | - Support for convensional commit messages 24 | - Support for generating an automatic changlog 25 | 26 | **As a general rule**: This should make creating kibibit projects (and 27 | hopefully other people's projects) easy to create with all hooks and stuff 28 | as 29 | ```bash 30 | kibibit init 31 | # or bit init 32 | ``` 33 | 34 | ## Existing solutions and external documentation 35 | 36 | - [gitflow](https://github.com/nvie/gitflow) (original gitflow extension) 37 | - [Introducing GitFlow](https://datasift.github.io/gitflow/IntroducingGitFlow.html) 38 | - [HubFlow: Tool for gitflow and GitHub](https://datasift.github.io/gitflow/) (not maintained anymore) 39 | - [gitflow workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) 40 | - [gitflow cheatsheet](https://danielkummer.github.io/git-flow-cheatsheet/) 41 | 42 | ## Some lessons learned from `tdd1t` 43 | 44 | Basically, two different flows can be used: 45 | 46 | - rebase every pull request into master (keeping all commits intact) 47 | - merge and squash the commits into a single "PR" commit for each PR 48 | 49 | With `tdd1t`, we used the first option, which looked like it was recieved 50 | poorly becuase the team needed to maintain **conventional commit messages 51 | for all commits in a branch**. I think it's better to go with the second option, 52 | which will force keeping only pull request title's and descriptions to conventions. 53 | 54 | This might be a bit harder since it will require a github integration with 55 | pull requests' webhooks in order to keep PR title + body to conventions instead of a githook. 56 | 57 | This can be done with a [probot bot installation](https://probot.github.io/apps/semantic-pull-requests/), 58 | we just need to check that it supports configuration and if not, add it. 59 | 60 | ## Project "Gotch'a"s 61 | 62 | Some things we need to consider while writing this: 63 | 64 | - Should be easily mainainable, preferrably with tests and such 65 | - Should work as a command line tool, and as an npm library 66 | > We need to write functions that will corespond to specific cli commands 67 | > that can take the flow and work with it **without the extra command line question steps**. 68 | - Should save sensitive data in appropriate locations (like keychain) 69 | - Should be as easy to integrate with as possible 70 | > We should support customization for advanced users, but also lots of defaults or user prompts 71 | > for first time users. 72 | 73 | ## Supported Commands 74 | 75 | ### Initialization `kibibit init` 76 | 77 | kibibit\bit should have an `init` command which will: 78 | 79 | - create a git repo and support command line params used by `git init` 80 | - should make sure this repo has a GitHub remote, and if not, ask to create it 81 | - Should configure a specific login account for this repo and github. 82 | > Basically, with `git`, you can have a user per project 83 | - should ensure that the two `gitflow` branches exist (master & develop by default) 84 | - should install a `.kibibit` file in the root of the repo with specific configurations 85 | > all configuration should be split between two places: the `.git` folder, and `.kibibit`. 86 | > `.kibibit` will basically hold configuration we want to make **consistant** since 87 | > `.git` folder is not being commited. 88 | > for example: the user to use should **not be consistant** becuase it can change locally, 89 | > but which branch is "production" and which one is "develop" should be kept in `.kibibit`, 90 | > and updated inside the `.git` folder on initial use. 91 | 92 | Currently, we'll support `node` applications specifically. So, these `node` integrations 93 | can also be initialized here: 94 | 95 | - create a `package.json` with all the attributes we know (remote repo, name, etc.) 96 | - install [husky](https://github.com/typicode/husky) for git hooks 97 | - install `kibibit`'s git hooks (need to understand which one's we need :-)) 98 | 99 | ### Clone `kibibit clone` 100 | 101 | Should clone a git repository and also install all kibibit/bit tools (like hooks) 102 | and stuff. Also, update some `.git` config params based on the `.kibibit` file. 103 | 104 | A nice bonus feature might be to support forks. If a repo is a fork of another repo, 105 | Add the fork as `origin` remote, and the original repo as the `upstream` remote. 106 | 107 | Also, if you clone a repo of another user, the cli might suggest the user to fork 108 | it first. 109 | 110 | see [this as a reference](https://help.github.com/en/articles/fork-a-repo) 111 | 112 | ### Commit `kibibit commit` 113 | 114 | Should allow commits on all bracnehs besides `master` and `develop`. 115 | Will support command line params OR opening an editor to create a commit message. 116 | 117 | ### Status `kibibit status` 118 | 119 | Pretty much the same as `git status`. should show files in staging area and 120 | unstaged files, and maybe some info about which feature you're working on 121 | (connected github issue & PR status \ PR if exists) 122 | 123 | ### Feature `kibibit feature` 124 | 125 | start or continue a feature (will be prompted). If no featureName is given, returns all ongoing features 126 | 127 | ### Hotfix `kibibit hotfix` 128 | 129 | start or continue a hotfix (will be prompted). If no hotfixName is given, returns all ongoing hotfixes 130 | 131 | ### Finish `kibibit finish` 132 | 133 | use GitHub to issue a pull request to origin/develop. 134 | 135 | ### Update `kibibit update` 136 | 137 | keep up-to-date with completed features on GitHub. 138 | 139 | Should this happen automatically? what about conflicts? 140 | 141 | ### Save `kibibit push` OR `kibibit save` 142 | 143 | (the oposite of update) 144 | 145 | push your current feature branch back to GitHub as you make progress and want to save your work 146 | 147 | ### Checkout shared branches `kibibit master` AND `kibibit develop` 148 | 149 | checkout master or develop branches. should use the configured branch names 150 | from `kibibit init` which should be located in `.kibibit` and `.git`. 151 | 152 | These branches should not allow direct commits (maybe later we'll allow it for admins based on github). 153 | 154 | ### Open-Source `kibibit opensource` 155 | 156 | Check alignment with GitHub's [Community Profile](https://github.com/Kibibit/tdd1t/community) 157 | and help with generating some of those files 158 | 159 | Might be a bad name, we can change this to something else if we decide to include 160 | some more GitHub settings changes here. 161 | 162 | ## Some Technical recommendations 163 | 164 | Every function should return a promise so we can concatinate things like: 165 | 166 | ```javascript 167 | kibibit.init('project name', 'github.user@gmail.com', /* createPackageFile */ true) 168 | .then(() => kibibit.feature('my-feature')) 169 | .then(() => kibibit.add('new-file.ts')) 170 | .then(() => kibibit.commit()) 171 | ``` 172 | 173 | The only exception to this IMO is creating a new kibibit client: 174 | ```javascript 175 | const kibibit = new Kibibit('<path_to_repo>'); 176 | ``` 177 | Which will create a new kibibit object were you can use commands for the given path. 178 | 179 | `kibibit.<function_name>` should allow passing variables similar to the command line. 180 | 181 | Using promises will allow use to use the cli like so: 182 | 183 | ```javascript 184 | function CLIFunctionInit(...params) { 185 | return kibibit.init(...params) 186 | .catch((err) => { 187 | if (err.code === ALREADY_INITIALIZED) { 188 | // deal with it! 189 | } 190 | 191 | if (err.code === NO_SIGNED_IN_USER) { 192 | // deal with it! 193 | } 194 | }); 195 | } 196 | 197 | ``` 198 | OR 199 | ```javascript 200 | function CLIFunctionInit(...params) { 201 | 202 | return Promise.resolve() 203 | .then(() => ensureRepoName(params)) 204 | .then(() => kibibit.init(...params)); 205 | } 206 | 207 | function ensureRepoName(params) { 208 | if (params.repoName) { 209 | return Promise.resolve(); 210 | } 211 | 212 | return prompt('please provide a name for this repo') 213 | .then((response) => params.repoName = response); 214 | } 215 | 216 | ``` 217 | -------------------------------------------------------------------------------- /assets/diagrams/class/parser/classDiagram.jison: -------------------------------------------------------------------------------- 1 | /** mermaid 2 | * https://mermaidjs.github.io/ 3 | * (c) 2015 Knut Sveidqvist 4 | * MIT license. 5 | */ 6 | 7 | /* lexical grammar */ 8 | %lex 9 | %x string struct 10 | 11 | %% 12 | \%\%[^\n]* /* do nothing */ 13 | \n+ return 'NEWLINE'; 14 | \s+ /* skip whitespace */ 15 | "classDiagram" return 'CLASS_DIAGRAM'; 16 | [\{] { this.begin("struct"); /*console.log('Starting struct');*/return 'STRUCT_START';} 17 | <struct>\} { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}} 18 | <struct>[\n] /* nothing */ 19 | <struct>[^\{\}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";} 20 | 21 | 22 | 23 | "class" return 'CLASS'; 24 | ["] this.begin("string"); 25 | <string>["] this.popState(); 26 | <string>[^"]* return "STR"; 27 | 28 | 29 | \s*\<\| return 'EXTENSION'; 30 | \s*\|\> return 'EXTENSION'; 31 | \s*\> return 'DEPENDENCY'; 32 | \s*\< return 'DEPENDENCY'; 33 | \s*\* return 'COMPOSITION'; 34 | \s*o return 'AGGREGATION'; 35 | \-\- return 'LINE'; 36 | \.\. return 'DOTTED_LINE'; 37 | ":"[^#\n;]+ return 'LABEL'; 38 | \- return 'MINUS'; 39 | "." return 'DOT'; 40 | \+ return 'PLUS'; 41 | \% return 'PCT'; 42 | "=" return 'EQUALS'; 43 | \= return 'EQUALS'; 44 | [A-Za-z]+ return 'ALPHA'; 45 | [!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION'; 46 | [0-9]+ return 'NUM'; 47 | [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| 48 | [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]| 49 | [\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]| 50 | [\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]| 51 | [\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]| 52 | [\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]| 53 | [\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]| 54 | [\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]| 55 | [\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]| 56 | [\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]| 57 | [\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]| 58 | [\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]| 59 | [\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]| 60 | [\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]| 61 | [\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]| 62 | [\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]| 63 | [\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]| 64 | [\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]| 65 | [\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]| 66 | [\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]| 67 | [\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]| 68 | [\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]| 69 | [\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]| 70 | [\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]| 71 | [\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]| 72 | [\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]| 73 | [\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]| 74 | [\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]| 75 | [\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]| 76 | [\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]| 77 | [\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]| 78 | [\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]| 79 | [\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]| 80 | [\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]| 81 | [\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]| 82 | [\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]| 83 | [\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]| 84 | [\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]| 85 | [\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]| 86 | [\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]| 87 | [\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]| 88 | [\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]| 89 | [\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]| 90 | [\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]| 91 | [\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]| 92 | [\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]| 93 | [\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]| 94 | [\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]| 95 | [\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]| 96 | [\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]| 97 | [\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]| 98 | [\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]| 99 | [\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]| 100 | [\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]| 101 | [\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]| 102 | [\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]| 103 | [\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]| 104 | [\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]| 105 | [\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]| 106 | [\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]| 107 | [\uFFD2-\uFFD7\uFFDA-\uFFDC] 108 | return 'UNICODE_TEXT'; 109 | \s return 'SPACE'; 110 | <<EOF>> return 'EOF'; 111 | 112 | /lex 113 | 114 | /* operator associations and precedence */ 115 | 116 | %left '^' 117 | 118 | %start mermaidDoc 119 | 120 | %% /* language grammar */ 121 | 122 | mermaidDoc: graphConfig; 123 | 124 | graphConfig 125 | : CLASS_DIAGRAM NEWLINE statements EOF 126 | ; 127 | 128 | statements 129 | : statement 130 | | statement NEWLINE statements 131 | ; 132 | 133 | 134 | className 135 | : alphaNumToken className { $$=$1+$2; } 136 | | alphaNumToken { $$=$1; } 137 | ; 138 | 139 | statement 140 | : relationStatement { yy.addRelation($1); } 141 | | relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); } 142 | | classStatement 143 | | methodStatement 144 | ; 145 | 146 | classStatement 147 | : CLASS className 148 | | CLASS className STRUCT_START members STRUCT_STOP {/*console.log($2,JSON.stringify($4));*/yy.addMembers($2,$4);} 149 | ; 150 | 151 | members 152 | : MEMBER { $$ = [$1]; } 153 | | MEMBER members { $2.push($1);$$=$2;} 154 | ; 155 | 156 | methodStatement 157 | : className {/*console.log('Rel found',$1);*/} 158 | | className LABEL {yy.addMembers($1,yy.cleanupLabel($2));} 159 | | MEMBER {console.warn('Member',$1);} 160 | | SEPARATOR {/*console.log('sep found',$1);*/} 161 | ; 162 | 163 | relationStatement 164 | : className relation className { $$ = {'id1':$1,'id2':$3, relation:$2, relationTitle1:'none', relationTitle2:'none'}; } 165 | | className STR relation className { $$ = {id1:$1, id2:$4, relation:$3, relationTitle1:$2, relationTitle2:'none'}} 166 | | className relation STR className { $$ = {id1:$1, id2:$4, relation:$2, relationTitle1:'none', relationTitle2:$3}; } 167 | | className STR relation STR className { $$ = {id1:$1, id2:$5, relation:$3, relationTitle1:$2, relationTitle2:$4} } 168 | ; 169 | 170 | relation 171 | : relationType lineType relationType { $$={type1:$1,type2:$3,lineType:$2}; } 172 | | lineType relationType { $$={type1:'none',type2:$2,lineType:$1}; } 173 | | relationType lineType { $$={type1:$1,type2:'none',lineType:$2}; } 174 | | lineType { $$={type1:'none',type2:'none',lineType:$1}; } 175 | ; 176 | 177 | relationType 178 | : AGGREGATION { $$=yy.relationType.AGGREGATION;} 179 | | EXTENSION { $$=yy.relationType.EXTENSION;} 180 | | COMPOSITION { $$=yy.relationType.COMPOSITION;} 181 | | DEPENDENCY { $$=yy.relationType.DEPENDENCY;} 182 | ; 183 | 184 | lineType 185 | : LINE {$$=yy.lineType.LINE;} 186 | | DOTTED_LINE {$$=yy.lineType.DOTTED_LINE;} 187 | ; 188 | 189 | commentToken : textToken | graphCodeTokens ; 190 | 191 | textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT; 192 | 193 | textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ; 194 | 195 | alphaNumToken : UNICODE_TEXT | NUM | ALPHA; 196 | %% 197 | -------------------------------------------------------------------------------- /assets/diagrams/gantt/ganttDb.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import { logger } from '../../logger' 3 | 4 | let dateFormat = '' 5 | let axisFormat = '' 6 | let title = '' 7 | let sections = [] 8 | let tasks = [] 9 | let currentSection = '' 10 | 11 | export const clear = function () { 12 | sections = [] 13 | tasks = [] 14 | currentSection = '' 15 | title = '' 16 | taskCnt = 0 17 | lastTask = undefined 18 | lastTaskID = undefined 19 | rawTasks = [] 20 | } 21 | 22 | export const setAxisFormat = function (txt) { 23 | axisFormat = txt 24 | } 25 | 26 | export const getAxisFormat = function () { 27 | return axisFormat 28 | } 29 | 30 | export const setDateFormat = function (txt) { 31 | dateFormat = txt 32 | } 33 | 34 | export const setTitle = function (txt) { 35 | title = txt 36 | } 37 | 38 | export const getTitle = function () { 39 | return title 40 | } 41 | 42 | export const addSection = function (txt) { 43 | currentSection = txt 44 | sections.push(txt) 45 | } 46 | 47 | export const getTasks = function () { 48 | let allItemsPricessed = compileTasks() 49 | const maxDepth = 10 50 | let iterationCount = 0 51 | while (!allItemsPricessed && (iterationCount < maxDepth)) { 52 | allItemsPricessed = compileTasks() 53 | iterationCount++ 54 | } 55 | 56 | tasks = rawTasks 57 | 58 | return tasks 59 | } 60 | 61 | const getStartDate = function (prevTime, dateFormat, str) { 62 | str = str.trim() 63 | 64 | // Test for after 65 | const re = /^after\s+([\d\w-]+)/ 66 | const afterStatement = re.exec(str.trim()) 67 | 68 | if (afterStatement !== null) { 69 | const task = findTaskById(afterStatement[1]) 70 | 71 | if (typeof task === 'undefined') { 72 | const dt = new Date() 73 | dt.setHours(0, 0, 0, 0) 74 | return dt 75 | } 76 | return task.endTime 77 | } 78 | 79 | // Check for actual date set 80 | if (moment(str, dateFormat.trim(), true).isValid()) { 81 | return moment(str, dateFormat.trim(), true).toDate() 82 | } else { 83 | logger.debug('Invalid date:' + str) 84 | logger.debug('With date format:' + dateFormat.trim()) 85 | } 86 | 87 | // Default date - now 88 | return new Date() 89 | } 90 | 91 | const getEndDate = function (prevTime, dateFormat, str) { 92 | str = str.trim() 93 | 94 | // Check for actual date 95 | if (moment(str, dateFormat.trim(), true).isValid()) { 96 | return moment(str, dateFormat.trim()).toDate() 97 | } 98 | 99 | const d = moment(prevTime) 100 | // Check for length 101 | const re = /^([\d]+)([wdhms])/ 102 | const durationStatement = re.exec(str.trim()) 103 | 104 | if (durationStatement !== null) { 105 | switch (durationStatement[2]) { 106 | case 's': 107 | d.add(durationStatement[1], 'seconds') 108 | break 109 | case 'm': 110 | d.add(durationStatement[1], 'minutes') 111 | break 112 | case 'h': 113 | d.add(durationStatement[1], 'hours') 114 | break 115 | case 'd': 116 | d.add(durationStatement[1], 'days') 117 | break 118 | case 'w': 119 | d.add(durationStatement[1], 'weeks') 120 | break 121 | } 122 | return d.toDate() 123 | } 124 | // Default date - now 125 | return d.toDate() 126 | } 127 | 128 | let taskCnt = 0 129 | const parseId = function (idStr) { 130 | if (typeof idStr === 'undefined') { 131 | taskCnt = taskCnt + 1 132 | return 'task' + taskCnt 133 | } 134 | return idStr 135 | } 136 | // id, startDate, endDate 137 | // id, startDate, length 138 | // id, after x, endDate 139 | // id, after x, length 140 | // startDate, endDate 141 | // startDate, length 142 | // after x, endDate 143 | // after x, length 144 | // endDate 145 | // length 146 | 147 | const compileData = function (prevTask, dataStr) { 148 | let ds 149 | 150 | if (dataStr.substr(0, 1) === ':') { 151 | ds = dataStr.substr(1, dataStr.length) 152 | } else { 153 | ds = dataStr 154 | } 155 | 156 | const data = ds.split(',') 157 | 158 | const task = {} 159 | 160 | // Get tags like active, done cand crit 161 | let matchFound = true 162 | while (matchFound) { 163 | matchFound = false 164 | if (data[0].match(/^\s*active\s*$/)) { 165 | task.active = true 166 | data.shift(1) 167 | matchFound = true 168 | } 169 | if (data[0].match(/^\s*done\s*$/)) { 170 | task.done = true 171 | data.shift(1) 172 | matchFound = true 173 | } 174 | if (data[0].match(/^\s*crit\s*$/)) { 175 | task.crit = true 176 | data.shift(1) 177 | matchFound = true 178 | } 179 | } 180 | for (let i = 0; i < data.length; i++) { 181 | data[i] = data[i].trim() 182 | } 183 | 184 | switch (data.length) { 185 | case 1: 186 | task.id = parseId() 187 | task.startTime = prevTask.endTime 188 | task.endTime = getEndDate(task.startTime, dateFormat, data[0]) 189 | break 190 | case 2: 191 | task.id = parseId() 192 | task.startTime = getStartDate(undefined, dateFormat, data[0]) 193 | task.endTime = getEndDate(task.startTime, dateFormat, data[1]) 194 | break 195 | case 3: 196 | task.id = parseId(data[0]) 197 | task.startTime = getStartDate(undefined, dateFormat, data[1]) 198 | task.endTime = getEndDate(task.startTime, dateFormat, data[2]) 199 | break 200 | default: 201 | } 202 | 203 | return task 204 | } 205 | 206 | const parseData = function (prevTaskId, dataStr) { 207 | let ds 208 | if (dataStr.substr(0, 1) === ':') { 209 | ds = dataStr.substr(1, dataStr.length) 210 | } else { 211 | ds = dataStr 212 | } 213 | 214 | const data = ds.split(',') 215 | 216 | const task = {} 217 | 218 | // Get tags like active, done cand crit 219 | let matchFound = true 220 | while (matchFound) { 221 | matchFound = false 222 | if (data[0].match(/^\s*active\s*$/)) { 223 | task.active = true 224 | data.shift(1) 225 | matchFound = true 226 | } 227 | if (data[0].match(/^\s*done\s*$/)) { 228 | task.done = true 229 | data.shift(1) 230 | matchFound = true 231 | } 232 | if (data[0].match(/^\s*crit\s*$/)) { 233 | task.crit = true 234 | data.shift(1) 235 | matchFound = true 236 | } 237 | } 238 | for (let i = 0; i < data.length; i++) { 239 | data[i] = data[i].trim() 240 | } 241 | 242 | switch (data.length) { 243 | case 1: 244 | task.id = parseId() 245 | task.startTime = { type: 'prevTaskEnd', id: prevTaskId } 246 | task.endTime = { data: data[0] } 247 | break 248 | case 2: 249 | task.id = parseId() 250 | task.startTime = { type: 'getStartDate', startData: data[0] } 251 | task.endTime = { data: data[1] } 252 | break 253 | case 3: 254 | task.id = parseId(data[0]) 255 | task.startTime = { type: 'getStartDate', startData: data[1] } 256 | task.endTime = { data: data[2] } 257 | break 258 | default: 259 | } 260 | 261 | return task 262 | } 263 | 264 | let lastTask 265 | let lastTaskID 266 | let rawTasks = [] 267 | const taskDb = {} 268 | export const addTask = function (descr, data) { 269 | const rawTask = { 270 | section: currentSection, 271 | type: currentSection, 272 | processed: false, 273 | raw: { data: data }, 274 | task: descr 275 | } 276 | const taskInfo = parseData(lastTaskID, data) 277 | rawTask.raw.startTime = taskInfo.startTime 278 | rawTask.raw.endTime = taskInfo.endTime 279 | rawTask.id = taskInfo.id 280 | rawTask.prevTaskId = lastTaskID 281 | rawTask.active = taskInfo.active 282 | rawTask.done = taskInfo.done 283 | rawTask.crit = taskInfo.crit 284 | 285 | const pos = rawTasks.push(rawTask) 286 | 287 | lastTaskID = rawTask.id 288 | // Store cross ref 289 | taskDb[rawTask.id] = pos - 1 290 | } 291 | 292 | export const findTaskById = function (id) { 293 | const pos = taskDb[id] 294 | return rawTasks[pos] 295 | } 296 | 297 | export const addTaskOrg = function (descr, data) { 298 | const newTask = { 299 | section: currentSection, 300 | type: currentSection, 301 | description: descr, 302 | task: descr 303 | } 304 | const taskInfo = compileData(lastTask, data) 305 | newTask.startTime = taskInfo.startTime 306 | newTask.endTime = taskInfo.endTime 307 | newTask.id = taskInfo.id 308 | newTask.active = taskInfo.active 309 | newTask.done = taskInfo.done 310 | newTask.crit = taskInfo.crit 311 | lastTask = newTask 312 | tasks.push(newTask) 313 | } 314 | 315 | const compileTasks = function () { 316 | const compileTask = function (pos) { 317 | const task = rawTasks[pos] 318 | let startTime = '' 319 | switch (rawTasks[pos].raw.startTime.type) { 320 | case 'prevTaskEnd': 321 | const prevTask = findTaskById(task.prevTaskId) 322 | task.startTime = prevTask.endTime 323 | break 324 | case 'getStartDate': 325 | startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData) 326 | if (startTime) { 327 | rawTasks[pos].startTime = startTime 328 | } 329 | break 330 | } 331 | 332 | if (rawTasks[pos].startTime) { 333 | rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data) 334 | if (rawTasks[pos].endTime) { 335 | rawTasks[pos].processed = true 336 | } 337 | } 338 | 339 | return rawTasks[pos].processed 340 | } 341 | 342 | let allProcessed = true 343 | for (let i = 0; i < rawTasks.length; i++) { 344 | compileTask(i) 345 | 346 | allProcessed = allProcessed && rawTasks[i].processed 347 | } 348 | return allProcessed 349 | } 350 | 351 | export default { 352 | clear, 353 | setDateFormat, 354 | setAxisFormat, 355 | getAxisFormat, 356 | setTitle, 357 | getTitle, 358 | addSection, 359 | getTasks, 360 | addTask, 361 | findTaskById, 362 | addTaskOrg 363 | } 364 | -------------------------------------------------------------------------------- /assets/diagrams/sequence/svgDraw.js: -------------------------------------------------------------------------------- 1 | export const drawRect = function (elem, rectData) { 2 | const rectElem = elem.append('rect') 3 | rectElem.attr('x', rectData.x) 4 | rectElem.attr('y', rectData.y) 5 | rectElem.attr('fill', rectData.fill) 6 | rectElem.attr('stroke', rectData.stroke) 7 | rectElem.attr('width', rectData.width) 8 | rectElem.attr('height', rectData.height) 9 | rectElem.attr('rx', rectData.rx) 10 | rectElem.attr('ry', rectData.ry) 11 | 12 | if (typeof rectData.class !== 'undefined') { 13 | rectElem.attr('class', rectData.class) 14 | } 15 | 16 | return rectElem 17 | } 18 | 19 | export const drawText = function (elem, textData, width) { 20 | // Remove and ignore br:s 21 | const nText = textData.text.replace(/<br\/?>/ig, ' ') 22 | 23 | const textElem = elem.append('text') 24 | textElem.attr('x', textData.x) 25 | textElem.attr('y', textData.y) 26 | textElem.style('text-anchor', textData.anchor) 27 | textElem.attr('fill', textData.fill) 28 | if (typeof textData.class !== 'undefined') { 29 | textElem.attr('class', textData.class) 30 | } 31 | 32 | const span = textElem.append('tspan') 33 | span.attr('x', textData.x + textData.textMargin * 2) 34 | span.attr('fill', textData.fill) 35 | span.text(nText) 36 | 37 | return textElem 38 | } 39 | 40 | export const drawLabel = function (elem, txtObject) { 41 | function genPoints (x, y, width, height, cut) { 42 | return x + ',' + y + ' ' + 43 | (x + width) + ',' + y + ' ' + 44 | (x + width) + ',' + (y + height - cut) + ' ' + 45 | (x + width - cut * 1.2) + ',' + (y + height) + ' ' + 46 | (x) + ',' + (y + height) 47 | } 48 | const polygon = elem.append('polygon') 49 | polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7)) 50 | polygon.attr('class', 'labelBox') 51 | 52 | txtObject.y = txtObject.y + txtObject.labelMargin 53 | txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin 54 | drawText(elem, txtObject) 55 | } 56 | 57 | let actorCnt = -1 58 | /** 59 | * Draws an actor in the diagram with the attaced line 60 | * @param center - The center of the the actor 61 | * @param pos The position if the actor in the liost of actors 62 | * @param description The text in the box 63 | */ 64 | export const drawActor = function (elem, left, verticalPos, description, conf) { 65 | const center = left + (conf.width / 2) 66 | const g = elem.append('g') 67 | if (verticalPos === 0) { 68 | actorCnt++ 69 | g.append('line') 70 | .attr('id', 'actor' + actorCnt) 71 | .attr('x1', center) 72 | .attr('y1', 5) 73 | .attr('x2', center) 74 | .attr('y2', 2000) 75 | .attr('class', 'actor-line') 76 | .attr('stroke-width', '0.5px') 77 | .attr('stroke', '#999') 78 | } 79 | 80 | const rect = getNoteRect() 81 | rect.x = left 82 | rect.y = verticalPos 83 | rect.fill = '#eaeaea' 84 | rect.width = conf.width 85 | rect.height = conf.height 86 | rect.class = 'actor' 87 | rect.rx = 3 88 | rect.ry = 3 89 | drawRect(g, rect) 90 | 91 | _drawTextCandidateFunc(conf)(description, g, 92 | rect.x, rect.y, rect.width, rect.height, { 'class': 'actor' }) 93 | } 94 | 95 | export const anchorElement = function (elem) { 96 | return elem.append('g') 97 | } 98 | /** 99 | * Draws an actor in the diagram with the attaced line 100 | * @param elem - element to append activation rect 101 | * @param bounds - activation box bounds 102 | * @param verticalPos - precise y cooridnate of bottom activation box edge 103 | */ 104 | export const drawActivation = function (elem, bounds, verticalPos) { 105 | const rect = getNoteRect() 106 | const g = bounds.anchored 107 | rect.x = bounds.startx 108 | rect.y = bounds.starty 109 | rect.fill = '#f4f4f4' 110 | rect.width = bounds.stopx - bounds.startx 111 | rect.height = verticalPos - bounds.starty 112 | drawRect(g, rect) 113 | } 114 | 115 | /** 116 | * Draws an actor in the diagram with the attaced line 117 | * @param center - The center of the the actor 118 | * @param pos The position if the actor in the list of actors 119 | * @param description The text in the box 120 | */ 121 | export const drawLoop = function (elem, bounds, labelText, conf) { 122 | const g = elem.append('g') 123 | const drawLoopLine = function (startx, starty, stopx, stopy) { 124 | return g.append('line') 125 | .attr('x1', startx) 126 | .attr('y1', starty) 127 | .attr('x2', stopx) 128 | .attr('y2', stopy) 129 | .attr('class', 'loopLine') 130 | } 131 | drawLoopLine(bounds.startx, bounds.starty, bounds.stopx, bounds.starty) 132 | drawLoopLine(bounds.stopx, bounds.starty, bounds.stopx, bounds.stopy) 133 | drawLoopLine(bounds.startx, bounds.stopy, bounds.stopx, bounds.stopy) 134 | drawLoopLine(bounds.startx, bounds.starty, bounds.startx, bounds.stopy) 135 | if (typeof bounds.sections !== 'undefined') { 136 | bounds.sections.forEach(function (item) { 137 | drawLoopLine(bounds.startx, item, bounds.stopx, item).style('stroke-dasharray', '3, 3') 138 | }) 139 | } 140 | 141 | let txt = getTextObj() 142 | txt.text = labelText 143 | txt.x = bounds.startx 144 | txt.y = bounds.starty 145 | txt.labelMargin = 1.5 * 10 // This is the small box that says "loop" 146 | txt.class = 'labelText' // Its size & position are fixed. 147 | 148 | drawLabel(g, txt) 149 | 150 | txt = getTextObj() 151 | txt.text = '[ ' + bounds.title + ' ]' 152 | txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2 153 | txt.y = bounds.starty + 1.5 * conf.boxMargin 154 | txt.anchor = 'middle' 155 | txt.class = 'loopText' 156 | 157 | drawText(g, txt) 158 | 159 | if (typeof bounds.sectionTitles !== 'undefined') { 160 | bounds.sectionTitles.forEach(function (item, idx) { 161 | if (item !== '') { 162 | txt.text = '[ ' + item + ' ]' 163 | txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin 164 | drawText(g, txt) 165 | } 166 | }) 167 | } 168 | } 169 | 170 | /** 171 | * Setup arrow head and define the marker. The result is appended to the svg. 172 | */ 173 | export const insertArrowHead = function (elem) { 174 | elem.append('defs').append('marker') 175 | .attr('id', 'arrowhead') 176 | .attr('refX', 5) 177 | .attr('refY', 2) 178 | .attr('markerWidth', 6) 179 | .attr('markerHeight', 4) 180 | .attr('orient', 'auto') 181 | .append('path') 182 | .attr('d', 'M 0,0 V 4 L6,2 Z') // this is actual shape for arrowhead 183 | } 184 | /** 185 | * Setup arrow head and define the marker. The result is appended to the svg. 186 | */ 187 | export const insertArrowCrossHead = function (elem) { 188 | const defs = elem.append('defs') 189 | const marker = defs.append('marker') 190 | .attr('id', 'crosshead') 191 | .attr('markerWidth', 15) 192 | .attr('markerHeight', 8) 193 | .attr('orient', 'auto') 194 | .attr('refX', 16) 195 | .attr('refY', 4) 196 | 197 | // The arrow 198 | marker.append('path') 199 | .attr('fill', 'black') 200 | .attr('stroke', '#000000') 201 | .style('stroke-dasharray', ('0, 0')) 202 | .attr('stroke-width', '1px') 203 | .attr('d', 'M 9,2 V 6 L16,4 Z') 204 | 205 | // The cross 206 | marker.append('path') 207 | .attr('fill', 'none') 208 | .attr('stroke', '#000000') 209 | .style('stroke-dasharray', ('0, 0')) 210 | .attr('stroke-width', '1px') 211 | .attr('d', 'M 0,1 L 6,7 M 6,1 L 0,7') 212 | // this is actual shape for arrowhead 213 | } 214 | 215 | export const getTextObj = function () { 216 | const txt = { 217 | x: 0, 218 | y: 0, 219 | 'fill': 'black', 220 | 'text-anchor': 'start', 221 | style: '#666', 222 | width: 100, 223 | height: 100, 224 | textMargin: 0, 225 | rx: 0, 226 | ry: 0 227 | } 228 | return txt 229 | } 230 | 231 | export const getNoteRect = function () { 232 | const rect = { 233 | x: 0, 234 | y: 0, 235 | fill: '#EDF2AE', 236 | stroke: '#666', 237 | width: 100, 238 | anchor: 'start', 239 | height: 100, 240 | rx: 0, 241 | ry: 0 242 | } 243 | return rect 244 | } 245 | 246 | const _drawTextCandidateFunc = (function () { 247 | function byText (content, g, x, y, width, height, textAttrs) { 248 | const text = g.append('text') 249 | .attr('x', x + width / 2).attr('y', y + height / 2 + 5) 250 | .style('text-anchor', 'middle') 251 | .text(content) 252 | _setTextAttrs(text, textAttrs) 253 | } 254 | 255 | function byTspan (content, g, x, y, width, height, textAttrs) { 256 | const text = g.append('text') 257 | .attr('x', x + width / 2).attr('y', y) 258 | .style('text-anchor', 'middle') 259 | text.append('tspan') 260 | .attr('x', x + width / 2).attr('dy', '0') 261 | .text(content) 262 | 263 | text.attr('y', y + height / 2.0) 264 | .attr('dominant-baseline', 'central') 265 | .attr('alignment-baseline', 'central') 266 | 267 | _setTextAttrs(text, textAttrs) 268 | } 269 | 270 | function byFo (content, g, x, y, width, height, textAttrs) { 271 | const s = g.append('switch') 272 | const f = s.append('foreignObject') 273 | .attr('x', x).attr('y', y) 274 | .attr('width', width).attr('height', height) 275 | 276 | const text = f.append('div').style('display', 'table') 277 | .style('height', '100%').style('width', '100%') 278 | 279 | text.append('div').style('display', 'table-cell') 280 | .style('text-align', 'center').style('vertical-align', 'middle') 281 | .text(content) 282 | 283 | byTspan(content, s, x, y, width, height, textAttrs) 284 | _setTextAttrs(text, textAttrs) 285 | } 286 | 287 | function _setTextAttrs (toText, fromTextAttrsDict) { 288 | for (const key in fromTextAttrsDict) { 289 | if (fromTextAttrsDict.hasOwnProperty(key)) { 290 | toText.attr(key, fromTextAttrsDict[key]) 291 | } 292 | } 293 | } 294 | 295 | return function (conf) { 296 | return conf.textPlacement === 'fo' ? byFo : ( 297 | conf.textPlacement === 'old' ? byText : byTspan) 298 | } 299 | })() 300 | 301 | export default { 302 | drawRect, 303 | drawText, 304 | drawLabel, 305 | drawActor, 306 | anchorElement, 307 | drawActivation, 308 | drawLoop, 309 | insertArrowHead, 310 | insertArrowCrossHead, 311 | getTextObj, 312 | getNoteRect 313 | } 314 | -------------------------------------------------------------------------------- /assets/diagrams/git/gitGraphRenderer.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import * as d3 from 'd3' 3 | 4 | import db from './gitGraphAst' 5 | import gitGraphParser from './parser/gitGraph' 6 | import { logger } from '../../logger' 7 | import { interpolateToCurve } from '../../utils' 8 | 9 | let allCommitsDict = {} 10 | let branchNum 11 | let config = { 12 | nodeSpacing: 150, 13 | nodeFillColor: 'yellow', 14 | nodeStrokeWidth: 2, 15 | nodeStrokeColor: 'grey', 16 | lineStrokeWidth: 4, 17 | branchOffset: 50, 18 | lineColor: 'grey', 19 | leftMargin: 50, 20 | branchColors: ['#442f74', '#983351', '#609732', '#AA9A39'], 21 | nodeRadius: 10, 22 | nodeLabel: { 23 | width: 75, 24 | height: 100, 25 | x: -25, 26 | y: 0 27 | } 28 | } 29 | let apiConfig = {} 30 | export const setConf = function (c) { 31 | apiConfig = c 32 | } 33 | 34 | function svgCreateDefs (svg) { 35 | svg 36 | .append('defs') 37 | .append('g') 38 | .attr('id', 'def-commit') 39 | .append('circle') 40 | .attr('r', config.nodeRadius) 41 | .attr('cx', 0) 42 | .attr('cy', 0) 43 | svg.select('#def-commit') 44 | .append('foreignObject') 45 | .attr('width', config.nodeLabel.width) 46 | .attr('height', config.nodeLabel.height) 47 | .attr('x', config.nodeLabel.x) 48 | .attr('y', config.nodeLabel.y) 49 | .attr('class', 'node-label') 50 | .attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility') 51 | .append('p') 52 | .html('') 53 | } 54 | 55 | function svgDrawLine (svg, points, colorIdx, interpolate) { 56 | const curve = interpolateToCurve(interpolate, d3.curveBasis) 57 | const color = config.branchColors[colorIdx % config.branchColors.length] 58 | const lineGen = d3.line() 59 | .x(function (d) { 60 | return Math.round(d.x) 61 | }) 62 | .y(function (d) { 63 | return Math.round(d.y) 64 | }) 65 | .curve(curve) 66 | 67 | svg 68 | .append('svg:path') 69 | .attr('d', lineGen(points)) 70 | .style('stroke', color) 71 | .style('stroke-width', config.lineStrokeWidth) 72 | .style('fill', 'none') 73 | } 74 | 75 | // Pass in the element and its pre-transform coords 76 | function getElementCoords (element, coords) { 77 | coords = coords || element.node().getBBox() 78 | const ctm = element.node().getCTM() 79 | const xn = ctm.e + coords.x * ctm.a 80 | const yn = ctm.f + coords.y * ctm.d 81 | return { 82 | left: xn, 83 | top: yn, 84 | width: coords.width, 85 | height: coords.height 86 | } 87 | } 88 | 89 | function svgDrawLineForCommits (svg, fromId, toId, direction, color) { 90 | logger.debug('svgDrawLineForCommits: ', fromId, toId) 91 | const fromBbox = getElementCoords(svg.select('#node-' + fromId + ' circle')) 92 | const toBbox = getElementCoords(svg.select('#node-' + toId + ' circle')) 93 | switch (direction) { 94 | case 'LR': 95 | // (toBbox) 96 | // +-------- 97 | // + (fromBbox) 98 | if (fromBbox.left - toBbox.left > config.nodeSpacing) { 99 | const lineStart = { x: fromBbox.left - config.nodeSpacing, y: toBbox.top + toBbox.height / 2 } 100 | const lineEnd = { x: toBbox.left + toBbox.width, y: toBbox.top + toBbox.height / 2 } 101 | svgDrawLine(svg, [lineStart, lineEnd], color, 'linear') 102 | svgDrawLine(svg, [ 103 | { x: fromBbox.left, y: fromBbox.top + fromBbox.height / 2 }, 104 | { x: fromBbox.left - config.nodeSpacing / 2, y: fromBbox.top + fromBbox.height / 2 }, 105 | { x: fromBbox.left - config.nodeSpacing / 2, y: lineStart.y }, 106 | lineStart], color) 107 | } else { 108 | svgDrawLine(svg, [{ 109 | 'x': fromBbox.left, 110 | 'y': fromBbox.top + fromBbox.height / 2 111 | }, { 112 | 'x': fromBbox.left - config.nodeSpacing / 2, 113 | 'y': fromBbox.top + fromBbox.height / 2 114 | }, { 115 | 'x': fromBbox.left - config.nodeSpacing / 2, 116 | 'y': toBbox.top + toBbox.height / 2 117 | }, { 118 | 'x': toBbox.left + toBbox.width, 119 | 'y': toBbox.top + toBbox.height / 2 120 | }], color) 121 | } 122 | break 123 | case 'BT': 124 | // + (fromBbox) 125 | // | 126 | // | 127 | // + (toBbox) 128 | if (toBbox.top - fromBbox.top > config.nodeSpacing) { 129 | const lineStart = { x: toBbox.left + toBbox.width / 2, y: fromBbox.top + fromBbox.height + config.nodeSpacing } 130 | const lineEnd = { x: toBbox.left + toBbox.width / 2, y: toBbox.top } 131 | svgDrawLine(svg, [lineStart, lineEnd], color, 'linear') 132 | svgDrawLine(svg, [ 133 | { x: fromBbox.left + fromBbox.width / 2, y: fromBbox.top + fromBbox.height }, 134 | { x: fromBbox.left + fromBbox.width / 2, y: fromBbox.top + fromBbox.height + config.nodeSpacing / 2 }, 135 | { x: toBbox.left + toBbox.width / 2, y: lineStart.y - config.nodeSpacing / 2 }, 136 | lineStart], color) 137 | } else { 138 | svgDrawLine(svg, [{ 139 | 'x': fromBbox.left + fromBbox.width / 2, 140 | 'y': fromBbox.top + fromBbox.height 141 | }, { 142 | 'x': fromBbox.left + fromBbox.width / 2, 143 | 'y': fromBbox.top + config.nodeSpacing / 2 144 | }, { 145 | 'x': toBbox.left + toBbox.width / 2, 146 | 'y': toBbox.top - config.nodeSpacing / 2 147 | }, { 148 | 'x': toBbox.left + toBbox.width / 2, 149 | 'y': toBbox.top 150 | }], color) 151 | } 152 | break 153 | } 154 | } 155 | 156 | function cloneNode (svg, selector) { 157 | return svg.select(selector).node().cloneNode(true) 158 | } 159 | 160 | function renderCommitHistory (svg, commitid, branches, direction) { 161 | let commit 162 | const numCommits = Object.keys(allCommitsDict).length 163 | if (_.isString(commitid)) { 164 | do { 165 | commit = allCommitsDict[commitid] 166 | logger.debug('in renderCommitHistory', commit.id, commit.seq) 167 | if (svg.select('#node-' + commitid).size() > 0) { 168 | return 169 | } 170 | svg 171 | .append(function () { 172 | return cloneNode(svg, '#def-commit') 173 | }) 174 | .attr('class', 'commit') 175 | .attr('id', function () { 176 | return 'node-' + commit.id 177 | }) 178 | .attr('transform', function () { 179 | switch (direction) { 180 | case 'LR': 181 | return 'translate(' + (commit.seq * config.nodeSpacing + config.leftMargin) + ', ' + 182 | (branchNum * config.branchOffset) + ')' 183 | case 'BT': 184 | return 'translate(' + (branchNum * config.branchOffset + config.leftMargin) + ', ' + 185 | ((numCommits - commit.seq) * config.nodeSpacing) + ')' 186 | } 187 | }) 188 | .attr('fill', config.nodeFillColor) 189 | .attr('stroke', config.nodeStrokeColor) 190 | .attr('stroke-width', config.nodeStrokeWidth) 191 | 192 | const branch = _.find(branches, ['commit', commit]) 193 | if (branch) { 194 | logger.debug('found branch ', branch.name) 195 | svg.select('#node-' + commit.id + ' p') 196 | .append('xhtml:span') 197 | .attr('class', 'branch-label') 198 | .text(branch.name + ', ') 199 | } 200 | svg.select('#node-' + commit.id + ' p') 201 | .append('xhtml:span') 202 | .attr('class', 'commit-id') 203 | .text(commit.id) 204 | if (commit.message !== '' && direction === 'BT') { 205 | svg.select('#node-' + commit.id + ' p') 206 | .append('xhtml:span') 207 | .attr('class', 'commit-msg') 208 | .text(', ' + commit.message) 209 | } 210 | commitid = commit.parent 211 | } while (commitid && allCommitsDict[commitid]) 212 | } 213 | 214 | if (_.isArray(commitid)) { 215 | logger.debug('found merge commmit', commitid) 216 | renderCommitHistory(svg, commitid[0], branches, direction) 217 | branchNum++ 218 | renderCommitHistory(svg, commitid[1], branches, direction) 219 | branchNum-- 220 | } 221 | } 222 | 223 | function renderLines (svg, commit, direction, branchColor) { 224 | branchColor = branchColor || 0 225 | while (commit.seq > 0 && !commit.lineDrawn) { 226 | if (_.isString(commit.parent)) { 227 | svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor) 228 | commit.lineDrawn = true 229 | commit = allCommitsDict[commit.parent] 230 | } else if (_.isArray(commit.parent)) { 231 | svgDrawLineForCommits(svg, commit.id, commit.parent[0], direction, branchColor) 232 | svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1) 233 | renderLines(svg, allCommitsDict[commit.parent[1]], direction, branchColor + 1) 234 | commit.lineDrawn = true 235 | commit = allCommitsDict[commit.parent[0]] 236 | } 237 | } 238 | } 239 | 240 | export const draw = function (txt, id, ver) { 241 | try { 242 | const parser = gitGraphParser.parser 243 | parser.yy = db 244 | 245 | logger.debug('in gitgraph renderer', txt, id, ver) 246 | // Parse the graph definition 247 | parser.parse(txt + '\n') 248 | 249 | config = _.extend(config, apiConfig, db.getOptions()) 250 | logger.debug('effective options', config) 251 | const direction = db.getDirection() 252 | allCommitsDict = db.getCommits() 253 | const branches = db.getBranchesAsObjArray() 254 | if (direction === 'BT') { 255 | config.nodeLabel.x = branches.length * config.branchOffset 256 | config.nodeLabel.width = '100%' 257 | config.nodeLabel.y = -1 * 2 * config.nodeRadius 258 | } 259 | const svg = d3.select(`[id="${id}"]`) 260 | svgCreateDefs(svg) 261 | branchNum = 1 262 | _.each(branches, function (v) { 263 | renderCommitHistory(svg, v.commit.id, branches, direction) 264 | renderLines(svg, v.commit, direction) 265 | branchNum++ 266 | }) 267 | svg.attr('height', function () { 268 | if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing 269 | return (branches.length + 1) * config.branchOffset 270 | }) 271 | } catch (e) { 272 | logger.error('Error while rendering gitgraph') 273 | logger.error(e.message) 274 | } 275 | } 276 | 277 | export default { 278 | setConf, 279 | draw 280 | } 281 | -------------------------------------------------------------------------------- /assets/diagrams/gantt/ganttRenderer.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3' 2 | 3 | import { parser } from './parser/gantt' 4 | import ganttDb from './ganttDb' 5 | 6 | parser.yy = ganttDb 7 | 8 | const conf = { 9 | titleTopMargin: 25, 10 | barHeight: 20, 11 | barGap: 4, 12 | topPadding: 50, 13 | rightPadding: 75, 14 | leftPadding: 75, 15 | gridLineStartPadding: 35, 16 | fontSize: 11, 17 | fontFamily: '"Open-Sans", "sans-serif"' 18 | } 19 | export const setConf = function (cnf) { 20 | const keys = Object.keys(cnf) 21 | 22 | keys.forEach(function (key) { 23 | conf[key] = cnf[key] 24 | }) 25 | } 26 | let w 27 | export const draw = function (text, id) { 28 | parser.yy.clear() 29 | parser.parse(text) 30 | 31 | const elem = document.getElementById(id) 32 | w = elem.parentElement.offsetWidth 33 | 34 | if (typeof w === 'undefined') { 35 | w = 1200 36 | } 37 | 38 | if (typeof conf.useWidth !== 'undefined') { 39 | w = conf.useWidth 40 | } 41 | 42 | const taskArray = parser.yy.getTasks() 43 | 44 | // Set height based on number of tasks 45 | const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding 46 | 47 | elem.setAttribute('height', '100%') 48 | // Set viewBox 49 | elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h) 50 | const svg = d3.select(`[id="${id}"]`) 51 | 52 | // Set timescale 53 | const timeScale = d3.scaleTime() 54 | .domain([d3.min(taskArray, function (d) { 55 | return d.startTime 56 | }), 57 | d3.max(taskArray, function (d) { 58 | return d.endTime 59 | })]) 60 | .rangeRound([0, w - conf.leftPadding - conf.rightPadding]) 61 | 62 | let categories = [] 63 | 64 | for (let i = 0; i < taskArray.length; i++) { 65 | categories.push(taskArray[i].type) 66 | } 67 | 68 | const catsUnfiltered = categories // for vert labels 69 | 70 | categories = checkUnique(categories) 71 | 72 | makeGant(taskArray, w, h) 73 | if (typeof conf.useWidth !== 'undefined') { 74 | elem.setAttribute('width', w) 75 | } 76 | 77 | svg.append('text') 78 | .text(parser.yy.getTitle()) 79 | .attr('x', w / 2) 80 | .attr('y', conf.titleTopMargin) 81 | .attr('class', 'titleText') 82 | 83 | function makeGant (tasks, pageWidth, pageHeight) { 84 | const barHeight = conf.barHeight 85 | const gap = barHeight + conf.barGap 86 | const topPadding = conf.topPadding 87 | const leftPadding = conf.leftPadding 88 | 89 | const colorScale = d3.scaleLinear() 90 | .domain([0, categories.length]) 91 | .range(['#00B9FA', '#F95002']) 92 | .interpolate(d3.interpolateHcl) 93 | 94 | makeGrid(leftPadding, topPadding, pageWidth, pageHeight) 95 | drawRects(tasks, gap, topPadding, leftPadding, barHeight, colorScale, pageWidth, pageHeight) 96 | vertLabels(gap, topPadding, leftPadding, barHeight, colorScale) 97 | drawToday(leftPadding, topPadding, pageWidth, pageHeight) 98 | } 99 | 100 | function drawRects (theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) { 101 | svg.append('g') 102 | .selectAll('rect') 103 | .data(theArray) 104 | .enter() 105 | .append('rect') 106 | .attr('x', 0) 107 | .attr('y', function (d, i) { 108 | return i * theGap + theTopPad - 2 109 | }) 110 | .attr('width', function () { 111 | return w - conf.rightPadding / 2 112 | }) 113 | .attr('height', theGap) 114 | .attr('class', function (d) { 115 | for (let i = 0; i < categories.length; i++) { 116 | if (d.type === categories[i]) { 117 | return 'section section' + (i % conf.numberSectionStyles) 118 | } 119 | } 120 | return 'section section0' 121 | }) 122 | 123 | const rectangles = svg.append('g') 124 | .selectAll('rect') 125 | .data(theArray) 126 | .enter() 127 | 128 | rectangles.append('rect') 129 | .attr('rx', 3) 130 | .attr('ry', 3) 131 | .attr('x', function (d) { 132 | return timeScale(d.startTime) + theSidePad 133 | }) 134 | .attr('y', function (d, i) { 135 | return i * theGap + theTopPad 136 | }) 137 | .attr('width', function (d) { 138 | return (timeScale(d.endTime) - timeScale(d.startTime)) 139 | }) 140 | .attr('height', theBarHeight) 141 | .attr('class', function (d) { 142 | const res = 'task ' 143 | 144 | let secNum = 0 145 | for (let i = 0; i < categories.length; i++) { 146 | if (d.type === categories[i]) { 147 | secNum = (i % conf.numberSectionStyles) 148 | } 149 | } 150 | 151 | if (d.active) { 152 | if (d.crit) { 153 | return res + ' activeCrit' + secNum 154 | } else { 155 | return res + ' active' + secNum 156 | } 157 | } 158 | 159 | if (d.done) { 160 | if (d.crit) { 161 | return res + ' doneCrit' + secNum 162 | } else { 163 | return res + ' done' + secNum 164 | } 165 | } 166 | 167 | if (d.crit) { 168 | return res + ' crit' + secNum 169 | } 170 | 171 | return res + ' task' + secNum 172 | }) 173 | 174 | rectangles.append('text') 175 | .text(function (d) { 176 | return d.task 177 | }) 178 | .attr('font-size', conf.fontSize) 179 | .attr('x', function (d) { 180 | const startX = timeScale(d.startTime) 181 | const endX = timeScale(d.endTime) 182 | const textWidth = this.getBBox().width 183 | 184 | // Check id text width > width of rectangle 185 | if (textWidth > (endX - startX)) { 186 | if (endX + textWidth + 1.5 * conf.leftPadding > w) { 187 | return startX + theSidePad - 5 188 | } else { 189 | return endX + theSidePad + 5 190 | } 191 | } else { 192 | return (endX - startX) / 2 + startX + theSidePad 193 | } 194 | }) 195 | .attr('y', function (d, i) { 196 | return i * theGap + (conf.barHeight / 2) + (conf.fontSize / 2 - 2) + theTopPad 197 | }) 198 | .attr('text-height', theBarHeight) 199 | .attr('class', function (d) { 200 | const startX = timeScale(d.startTime) 201 | const endX = timeScale(d.endTime) 202 | const textWidth = this.getBBox().width 203 | let secNum = 0 204 | for (let i = 0; i < categories.length; i++) { 205 | if (d.type === categories[i]) { 206 | secNum = (i % conf.numberSectionStyles) 207 | } 208 | } 209 | 210 | let taskType = '' 211 | if (d.active) { 212 | if (d.crit) { 213 | taskType = 'activeCritText' + secNum 214 | } else { 215 | taskType = 'activeText' + secNum 216 | } 217 | } 218 | 219 | if (d.done) { 220 | if (d.crit) { 221 | taskType = taskType + ' doneCritText' + secNum 222 | } else { 223 | taskType = taskType + ' doneText' + secNum 224 | } 225 | } else { 226 | if (d.crit) { 227 | taskType = taskType + ' critText' + secNum 228 | } 229 | } 230 | 231 | // Check id text width > width of rectangle 232 | if (textWidth > (endX - startX)) { 233 | if (endX + textWidth + 1.5 * conf.leftPadding > w) { 234 | return 'taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType 235 | } else { 236 | return 'taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType 237 | } 238 | } else { 239 | return 'taskText taskText' + secNum + ' ' + taskType 240 | } 241 | }) 242 | } 243 | 244 | function makeGrid (theSidePad, theTopPad, w, h) { 245 | let xAxis = d3.axisBottom(timeScale) 246 | .tickSize(-h + theTopPad + conf.gridLineStartPadding) 247 | .tickFormat(d3.timeFormat(parser.yy.getAxisFormat() || conf.axisFormat || '%Y-%m-%d')) 248 | 249 | svg.append('g') 250 | .attr('class', 'grid') 251 | .attr('transform', 'translate(' + theSidePad + ', ' + (h - 50) + ')') 252 | .call(xAxis) 253 | .selectAll('text') 254 | .style('text-anchor', 'middle') 255 | .attr('fill', '#000') 256 | .attr('stroke', 'none') 257 | .attr('font-size', 10) 258 | .attr('dy', '1em') 259 | } 260 | 261 | function vertLabels (theGap, theTopPad) { 262 | const numOccurances = [] 263 | let prevGap = 0 264 | 265 | for (let i = 0; i < categories.length; i++) { 266 | numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)] 267 | } 268 | 269 | svg.append('g') // without doing this, impossible to put grid lines behind text 270 | .selectAll('text') 271 | .data(numOccurances) 272 | .enter() 273 | .append('text') 274 | .text(function (d) { 275 | return d[0] 276 | }) 277 | .attr('x', 10) 278 | .attr('y', function (d, i) { 279 | if (i > 0) { 280 | for (let j = 0; j < i; j++) { 281 | prevGap += numOccurances[i - 1][1] 282 | return d[1] * theGap / 2 + prevGap * theGap + theTopPad 283 | } 284 | } else { 285 | return d[1] * theGap / 2 + theTopPad 286 | } 287 | }) 288 | .attr('class', function (d) { 289 | for (let i = 0; i < categories.length; i++) { 290 | if (d[0] === categories[i]) { 291 | return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles) 292 | } 293 | } 294 | return 'sectionTitle' 295 | }) 296 | } 297 | 298 | function drawToday (theSidePad, theTopPad, w, h) { 299 | const todayG = svg.append('g') 300 | .attr('class', 'today') 301 | 302 | const today = new Date() 303 | 304 | todayG.append('line') 305 | .attr('x1', timeScale(today) + theSidePad) 306 | .attr('x2', timeScale(today) + theSidePad) 307 | .attr('y1', conf.titleTopMargin) 308 | .attr('y2', h - conf.titleTopMargin) 309 | .attr('class', 'today') 310 | } 311 | 312 | // from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript 313 | function checkUnique (arr) { 314 | const hash = {} 315 | const result = [] 316 | for (let i = 0, l = arr.length; i < l; ++i) { 317 | if (!hash.hasOwnProperty(arr[i])) { // it works with objects! in FF, at least 318 | hash[arr[i]] = true 319 | result.push(arr[i]) 320 | } 321 | } 322 | return result 323 | } 324 | 325 | // from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array 326 | function getCounts (arr) { 327 | let i = arr.length // const to loop over 328 | const obj = {} // obj to store results 329 | while (i) { 330 | obj[arr[--i]] = (obj[arr[i]] || 0) + 1 // count occurrences 331 | } 332 | return obj 333 | } 334 | 335 | // get specific from everything 336 | function getCount (word, arr) { 337 | return getCounts(arr)[word] || 0 338 | } 339 | } 340 | 341 | export default { 342 | setConf, 343 | draw 344 | } 345 | -------------------------------------------------------------------------------- /assets/diagrams/flowchart/flowDb.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3' 2 | 3 | import { logger } from '../../logger' 4 | import utils from '../../utils' 5 | 6 | let vertices = {} 7 | let edges = [] 8 | let classes = [] 9 | let subGraphs = [] 10 | let tooltips = {} 11 | let subCount = 0 12 | let direction 13 | // Functions to be run after graph rendering 14 | let funs = [] 15 | /** 16 | * Function called by parser when a node definition has been found 17 | * @param id 18 | * @param text 19 | * @param type 20 | * @param style 21 | */ 22 | export const addVertex = function (id, text, type, style) { 23 | let txt 24 | 25 | if (typeof id === 'undefined') { 26 | return 27 | } 28 | if (id.trim().length === 0) { 29 | return 30 | } 31 | 32 | if (typeof vertices[id] === 'undefined') { 33 | vertices[id] = { id: id, styles: [], classes: [] } 34 | } 35 | if (typeof text !== 'undefined') { 36 | txt = text.trim() 37 | 38 | // strip quotes if string starts and exnds with a quote 39 | if (txt[0] === '"' && txt[txt.length - 1] === '"') { 40 | txt = txt.substring(1, txt.length - 1) 41 | } 42 | 43 | vertices[id].text = txt 44 | } 45 | if (typeof type !== 'undefined') { 46 | vertices[id].type = type 47 | } 48 | if (typeof type !== 'undefined') { 49 | vertices[id].type = type 50 | } 51 | if (typeof style !== 'undefined') { 52 | if (style !== null) { 53 | style.forEach(function (s) { 54 | vertices[id].styles.push(s) 55 | }) 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * Function called by parser when a link/edge definition has been found 62 | * @param start 63 | * @param end 64 | * @param type 65 | * @param linktext 66 | */ 67 | export const addLink = function (start, end, type, linktext) { 68 | logger.info('Got edge...', start, end) 69 | const edge = { start: start, end: end, type: undefined, text: '' } 70 | linktext = type.text 71 | 72 | if (typeof linktext !== 'undefined') { 73 | edge.text = linktext.trim() 74 | 75 | // strip quotes if string starts and exnds with a quote 76 | if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') { 77 | edge.text = edge.text.substring(1, edge.text.length - 1) 78 | } 79 | } 80 | 81 | if (typeof type !== 'undefined') { 82 | edge.type = type.type 83 | edge.stroke = type.stroke 84 | } 85 | edges.push(edge) 86 | } 87 | 88 | /** 89 | * Updates a link's line interpolation algorithm 90 | * @param pos 91 | * @param interpolate 92 | */ 93 | export const updateLinkInterpolate = function (pos, interp) { 94 | if (pos === 'default') { 95 | edges.defaultInterpolate = interp 96 | } else { 97 | edges[pos].interpolate = interp 98 | } 99 | } 100 | 101 | /** 102 | * Updates a link with a style 103 | * @param pos 104 | * @param style 105 | */ 106 | export const updateLink = function (pos, style) { 107 | if (pos === 'default') { 108 | edges.defaultStyle = style 109 | } else { 110 | if (utils.isSubstringInArray('fill', style) === -1) { 111 | style.push('fill:none') 112 | } 113 | edges[pos].style = style 114 | } 115 | } 116 | 117 | export const addClass = function (id, style) { 118 | if (typeof classes[id] === 'undefined') { 119 | classes[id] = { id: id, styles: [] } 120 | } 121 | 122 | if (typeof style !== 'undefined') { 123 | if (style !== null) { 124 | style.forEach(function (s) { 125 | classes[id].styles.push(s) 126 | }) 127 | } 128 | } 129 | } 130 | 131 | /** 132 | * Called by parser when a graph definition is found, stores the direction of the chart. 133 | * @param dir 134 | */ 135 | export const setDirection = function (dir) { 136 | direction = dir 137 | } 138 | 139 | /** 140 | * Called by parser when a graph definition is found, stores the direction of the chart. 141 | * @param dir 142 | */ 143 | export const setClass = function (id, className) { 144 | if (id.indexOf(',') > 0) { 145 | id.split(',').forEach(function (id2) { 146 | if (typeof vertices[id2] !== 'undefined') { 147 | vertices[id2].classes.push(className) 148 | } 149 | }) 150 | } else { 151 | if (typeof vertices[id] !== 'undefined') { 152 | vertices[id].classes.push(className) 153 | } 154 | } 155 | } 156 | 157 | const setTooltip = function (id, tooltip) { 158 | if (typeof tooltip !== 'undefined') { 159 | tooltips[id] = tooltip 160 | } 161 | } 162 | 163 | const setClickFun = function (id, functionName) { 164 | if (typeof functionName === 'undefined') { 165 | return 166 | } 167 | if (typeof vertices[id] !== 'undefined') { 168 | funs.push(function (element) { 169 | const elem = d3.select(element).select(`[id="${id}"]`) 170 | if (elem !== null) { 171 | elem.on('click', function () { 172 | window[functionName](id) 173 | }) 174 | } 175 | }) 176 | } 177 | } 178 | 179 | const setLink = function (id, linkStr) { 180 | if (typeof linkStr === 'undefined') { 181 | return 182 | } 183 | if (typeof vertices[id] !== 'undefined') { 184 | funs.push(function (element) { 185 | const elem = d3.select(element).select(`[id="${id}"]`) 186 | if (elem !== null) { 187 | elem.on('click', function () { 188 | window.open(linkStr, 'newTab') 189 | }) 190 | } 191 | }) 192 | } 193 | } 194 | export const getTooltip = function (id) { 195 | return tooltips[id] 196 | } 197 | 198 | /** 199 | * Called by parser when a graph definition is found, stores the direction of the chart. 200 | * @param dir 201 | */ 202 | export const setClickEvent = function (id, functionName, link, tooltip) { 203 | if (id.indexOf(',') > 0) { 204 | id.split(',').forEach(function (id2) { 205 | setTooltip(id2, tooltip) 206 | setClickFun(id2, functionName) 207 | setLink(id2, link) 208 | setClass(id, 'clickable') 209 | }) 210 | } else { 211 | setTooltip(id, tooltip) 212 | setClickFun(id, functionName) 213 | setLink(id, link) 214 | setClass(id, 'clickable') 215 | } 216 | } 217 | 218 | export const bindFunctions = function (element) { 219 | funs.forEach(function (fun) { 220 | fun(element) 221 | }) 222 | } 223 | export const getDirection = function () { 224 | return direction 225 | } 226 | /** 227 | * Retrieval function for fetching the found nodes after parsing has completed. 228 | * @returns {{}|*|vertices} 229 | */ 230 | export const getVertices = function () { 231 | return vertices 232 | } 233 | 234 | /** 235 | * Retrieval function for fetching the found links after parsing has completed. 236 | * @returns {{}|*|edges} 237 | */ 238 | export const getEdges = function () { 239 | return edges 240 | } 241 | 242 | /** 243 | * Retrieval function for fetching the found class definitions after parsing has completed. 244 | * @returns {{}|*|classes} 245 | */ 246 | export const getClasses = function () { 247 | return classes 248 | } 249 | 250 | const setupToolTips = function (element) { 251 | let tooltipElem = d3.select('.mermaidTooltip') 252 | if ((tooltipElem._groups || tooltipElem)[0][0] === null) { 253 | tooltipElem = d3.select('body') 254 | .append('div') 255 | .attr('class', 'mermaidTooltip') 256 | .style('opacity', 0) 257 | } 258 | 259 | const svg = d3.select(element).select('svg') 260 | 261 | const nodes = svg.selectAll('g.node') 262 | nodes 263 | .on('mouseover', function () { 264 | const el = d3.select(this) 265 | const title = el.attr('title') 266 | // Dont try to draw a tooltip if no data is provided 267 | if (title === null) { 268 | return 269 | } 270 | const rect = this.getBoundingClientRect() 271 | 272 | tooltipElem.transition() 273 | .duration(200) 274 | .style('opacity', '.9') 275 | tooltipElem.html(el.attr('title')) 276 | .style('left', (rect.left + (rect.right - rect.left) / 2) + 'px') 277 | .style('top', (rect.top - 14 + document.body.scrollTop) + 'px') 278 | el.classed('hover', true) 279 | }) 280 | .on('mouseout', function () { 281 | tooltipElem.transition() 282 | .duration(500) 283 | .style('opacity', 0) 284 | const el = d3.select(this) 285 | el.classed('hover', false) 286 | }) 287 | } 288 | funs.push(setupToolTips) 289 | 290 | /** 291 | * Clears the internal graph db so that a new graph can be parsed. 292 | */ 293 | export const clear = function () { 294 | vertices = {} 295 | classes = {} 296 | edges = [] 297 | funs = [] 298 | funs.push(setupToolTips) 299 | subGraphs = [] 300 | subCount = 0 301 | tooltips = [] 302 | } 303 | /** 304 | * 305 | * @returns {string} 306 | */ 307 | export const defaultStyle = function () { 308 | return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;' 309 | } 310 | 311 | /** 312 | * Clears the internal graph db so that a new graph can be parsed. 313 | */ 314 | export const addSubGraph = function (list, title) { 315 | function uniq (a) { 316 | const prims = { 'boolean': {}, 'number': {}, 'string': {} } 317 | const objs = [] 318 | 319 | return a.filter(function (item) { 320 | const type = typeof item 321 | if (item.trim() === '') { 322 | return false 323 | } 324 | if (type in prims) { return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true) } else { return objs.indexOf(item) >= 0 ? false : objs.push(item) } 325 | }) 326 | } 327 | 328 | let nodeList = [] 329 | 330 | nodeList = uniq(nodeList.concat.apply(nodeList, list)) 331 | 332 | const subGraph = { id: 'subGraph' + subCount, nodes: nodeList, title: title.trim() } 333 | subGraphs.push(subGraph) 334 | subCount = subCount + 1 335 | return subGraph.id 336 | } 337 | 338 | const getPosForId = function (id) { 339 | for (let i = 0; i < subGraphs.length; i++) { 340 | if (subGraphs[i].id === id) { 341 | return i 342 | } 343 | } 344 | return -1 345 | } 346 | let secCount = -1 347 | const posCrossRef = [] 348 | const indexNodes2 = function (id, pos) { 349 | const nodes = subGraphs[pos].nodes 350 | secCount = secCount + 1 351 | if (secCount > 2000) { 352 | return 353 | } 354 | posCrossRef[secCount] = pos 355 | // Check if match 356 | if (subGraphs[pos].id === id) { 357 | return { 358 | result: true, 359 | count: 0 360 | } 361 | } 362 | 363 | let count = 0 364 | let posCount = 1 365 | while (count < nodes.length) { 366 | const childPos = getPosForId(nodes[count]) 367 | // Ignore regular nodes (pos will be -1) 368 | if (childPos >= 0) { 369 | const res = indexNodes2(id, childPos) 370 | if (res.result) { 371 | return { 372 | result: true, 373 | count: posCount + res.count 374 | } 375 | } else { 376 | posCount = posCount + res.count 377 | } 378 | } 379 | count = count + 1 380 | } 381 | 382 | return { 383 | result: false, 384 | count: posCount 385 | } 386 | } 387 | 388 | export const getDepthFirstPos = function (pos) { 389 | return posCrossRef[pos] 390 | } 391 | export const indexNodes = function () { 392 | secCount = -1 393 | if (subGraphs.length > 0) { 394 | indexNodes2('none', subGraphs.length - 1, 0) 395 | } 396 | } 397 | 398 | export const getSubGraphs = function () { 399 | return subGraphs 400 | } 401 | 402 | export default { 403 | addVertex, 404 | addLink, 405 | updateLinkInterpolate, 406 | updateLink, 407 | addClass, 408 | setDirection, 409 | setClass, 410 | getTooltip, 411 | setClickEvent, 412 | bindFunctions, 413 | getDirection, 414 | getVertices, 415 | getEdges, 416 | getClasses, 417 | clear, 418 | defaultStyle, 419 | addSubGraph, 420 | getDepthFirstPos, 421 | indexNodes, 422 | getSubGraphs 423 | } 424 | -------------------------------------------------------------------------------- /assets/diagrams/class/classRenderer.js: -------------------------------------------------------------------------------- 1 | import dagre from 'dagre-layout' 2 | import graphlib from 'graphlibrary' 3 | import * as d3 from 'd3' 4 | 5 | import classDb from './classDb' 6 | import { logger } from '../../logger' 7 | import { parser } from './parser/classDiagram' 8 | 9 | parser.yy = classDb 10 | 11 | const idCache = {} 12 | 13 | let classCnt = 0 14 | const conf = { 15 | dividerMargin: 10, 16 | padding: 5, 17 | textHeight: 10 18 | } 19 | 20 | // Todo optimize 21 | const getGraphId = function (label) { 22 | const keys = Object.keys(idCache) 23 | 24 | for (let i = 0; i < keys.length; i++) { 25 | if (idCache[keys[i]].label === label) { 26 | return keys[i] 27 | } 28 | } 29 | 30 | return undefined 31 | } 32 | 33 | /** 34 | * Setup arrow head and define the marker. The result is appended to the svg. 35 | */ 36 | const insertMarkers = function (elem) { 37 | elem.append('defs').append('marker') 38 | .attr('id', 'extensionStart') 39 | .attr('class', 'extension') 40 | .attr('refX', 0) 41 | .attr('refY', 7) 42 | .attr('markerWidth', 190) 43 | .attr('markerHeight', 240) 44 | .attr('orient', 'auto') 45 | .append('path') 46 | .attr('d', 'M 1,7 L18,13 V 1 Z') 47 | 48 | elem.append('defs').append('marker') 49 | .attr('id', 'extensionEnd') 50 | .attr('refX', 19) 51 | .attr('refY', 7) 52 | .attr('markerWidth', 20) 53 | .attr('markerHeight', 28) 54 | .attr('orient', 'auto') 55 | .append('path') 56 | .attr('d', 'M 1,1 V 13 L18,7 Z') // this is actual shape for arrowhead 57 | 58 | elem.append('defs').append('marker') 59 | .attr('id', 'compositionStart') 60 | .attr('class', 'extension') 61 | .attr('refX', 0) 62 | .attr('refY', 7) 63 | .attr('markerWidth', 190) 64 | .attr('markerHeight', 240) 65 | .attr('orient', 'auto') 66 | .append('path') 67 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') 68 | 69 | elem.append('defs').append('marker') 70 | .attr('id', 'compositionEnd') 71 | .attr('refX', 19) 72 | .attr('refY', 7) 73 | .attr('markerWidth', 20) 74 | .attr('markerHeight', 28) 75 | .attr('orient', 'auto') 76 | .append('path') 77 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') 78 | 79 | elem.append('defs').append('marker') 80 | .attr('id', 'aggregationStart') 81 | .attr('class', 'extension') 82 | .attr('refX', 0) 83 | .attr('refY', 7) 84 | .attr('markerWidth', 190) 85 | .attr('markerHeight', 240) 86 | .attr('orient', 'auto') 87 | .append('path') 88 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') 89 | 90 | elem.append('defs').append('marker') 91 | .attr('id', 'aggregationEnd') 92 | .attr('refX', 19) 93 | .attr('refY', 7) 94 | .attr('markerWidth', 20) 95 | .attr('markerHeight', 28) 96 | .attr('orient', 'auto') 97 | .append('path') 98 | .attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z') 99 | 100 | elem.append('defs').append('marker') 101 | .attr('id', 'dependencyStart') 102 | .attr('class', 'extension') 103 | .attr('refX', 0) 104 | .attr('refY', 7) 105 | .attr('markerWidth', 190) 106 | .attr('markerHeight', 240) 107 | .attr('orient', 'auto') 108 | .append('path') 109 | .attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z') 110 | 111 | elem.append('defs').append('marker') 112 | .attr('id', 'dependencyEnd') 113 | .attr('refX', 19) 114 | .attr('refY', 7) 115 | .attr('markerWidth', 20) 116 | .attr('markerHeight', 28) 117 | .attr('orient', 'auto') 118 | .append('path') 119 | .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z') 120 | } 121 | 122 | let edgeCount = 0 123 | const drawEdge = function (elem, path, relation) { 124 | const getRelationType = function (type) { 125 | switch (type) { 126 | case classDb.relationType.AGGREGATION: 127 | return 'aggregation' 128 | case classDb.relationType.EXTENSION: 129 | return 'extension' 130 | case classDb.relationType.COMPOSITION: 131 | return 'composition' 132 | case classDb.relationType.DEPENDENCY: 133 | return 'dependency' 134 | } 135 | } 136 | 137 | // The data for our line 138 | const lineData = path.points 139 | 140 | // This is the accessor function we talked about above 141 | const lineFunction = d3.line() 142 | .x(function (d) { 143 | return d.x 144 | }) 145 | .y(function (d) { 146 | return d.y 147 | }) 148 | .curve(d3.curveBasis) 149 | 150 | const svgPath = elem.append('path') 151 | .attr('d', lineFunction(lineData)) 152 | .attr('id', 'edge' + edgeCount) 153 | .attr('class', 'relation') 154 | let url = '' 155 | if (conf.arrowMarkerAbsolute) { 156 | url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search 157 | url = url.replace(/\(/g, '\\(') 158 | url = url.replace(/\)/g, '\\)') 159 | } 160 | 161 | if (relation.relation.type1 !== 'none') { 162 | svgPath.attr('marker-start', 'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')') 163 | } 164 | if (relation.relation.type2 !== 'none') { 165 | svgPath.attr('marker-end', 'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')') 166 | } 167 | 168 | let x, y 169 | const l = path.points.length 170 | if ((l % 2) !== 0) { 171 | const p1 = path.points[Math.floor(l / 2)] 172 | const p2 = path.points[Math.ceil(l / 2)] 173 | x = (p1.x + p2.x) / 2 174 | y = (p1.y + p2.y) / 2 175 | } else { 176 | const p = path.points[Math.floor(l / 2)] 177 | x = p.x 178 | y = p.y 179 | } 180 | 181 | if (typeof relation.title !== 'undefined') { 182 | const g = elem.append('g') 183 | .attr('class', 'classLabel') 184 | const label = g.append('text') 185 | .attr('class', 'label') 186 | .attr('x', x) 187 | .attr('y', y) 188 | .attr('fill', 'red') 189 | .attr('text-anchor', 'middle') 190 | .text(relation.title) 191 | 192 | window.label = label 193 | const bounds = label.node().getBBox() 194 | 195 | g.insert('rect', ':first-child') 196 | .attr('class', 'box') 197 | .attr('x', bounds.x - conf.padding / 2) 198 | .attr('y', bounds.y - conf.padding / 2) 199 | .attr('width', bounds.width + conf.padding) 200 | .attr('height', bounds.height + conf.padding) 201 | } 202 | 203 | edgeCount++ 204 | } 205 | 206 | const drawClass = function (elem, classDef) { 207 | logger.info('Rendering class ' + classDef) 208 | 209 | const addTspan = function (textEl, txt, isFirst) { 210 | const tSpan = textEl.append('tspan') 211 | .attr('x', conf.padding) 212 | .text(txt) 213 | if (!isFirst) { 214 | tSpan.attr('dy', conf.textHeight) 215 | } 216 | } 217 | 218 | const id = 'classId' + classCnt 219 | const classInfo = { 220 | id: id, 221 | label: classDef.id, 222 | width: 0, 223 | height: 0 224 | } 225 | 226 | const g = elem.append('g') 227 | .attr('id', id) 228 | .attr('class', 'classGroup') 229 | const title = g.append('text') 230 | .attr('x', conf.padding) 231 | .attr('y', conf.textHeight + conf.padding) 232 | .text(classDef.id) 233 | 234 | const titleHeight = title.node().getBBox().height 235 | 236 | const membersLine = g.append('line') // text label for the x axis 237 | .attr('x1', 0) 238 | .attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2) 239 | .attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2) 240 | 241 | const members = g.append('text') // text label for the x axis 242 | .attr('x', conf.padding) 243 | .attr('y', titleHeight + (conf.dividerMargin) + conf.textHeight) 244 | .attr('fill', 'white') 245 | .attr('class', 'classText') 246 | 247 | let isFirst = true 248 | classDef.members.forEach(function (member) { 249 | addTspan(members, member, isFirst) 250 | isFirst = false 251 | }) 252 | 253 | const membersBox = members.node().getBBox() 254 | 255 | const methodsLine = g.append('line') // text label for the x axis 256 | .attr('x1', 0) 257 | .attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height) 258 | .attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height) 259 | 260 | const methods = g.append('text') // text label for the x axis 261 | .attr('x', conf.padding) 262 | .attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight) 263 | .attr('fill', 'white') 264 | .attr('class', 'classText') 265 | 266 | isFirst = true 267 | 268 | classDef.methods.forEach(function (method) { 269 | addTspan(methods, method, isFirst) 270 | isFirst = false 271 | }) 272 | 273 | const classBox = g.node().getBBox() 274 | g.insert('rect', ':first-child') 275 | .attr('x', 0) 276 | .attr('y', 0) 277 | .attr('width', classBox.width + 2 * conf.padding) 278 | .attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin) 279 | 280 | membersLine.attr('x2', classBox.width + 2 * conf.padding) 281 | methodsLine.attr('x2', classBox.width + 2 * conf.padding) 282 | 283 | classInfo.width = classBox.width + 2 * conf.padding 284 | classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin 285 | 286 | idCache[id] = classInfo 287 | classCnt++ 288 | return classInfo 289 | } 290 | 291 | export const setConf = function (cnf) { 292 | const keys = Object.keys(cnf) 293 | 294 | keys.forEach(function (key) { 295 | conf[key] = cnf[key] 296 | }) 297 | } 298 | /** 299 | * Draws a flowchart in the tag with id: id based on the graph definition in text. 300 | * @param text 301 | * @param id 302 | */ 303 | export const draw = function (text, id) { 304 | parser.yy.clear() 305 | parser.parse(text) 306 | 307 | logger.info('Rendering diagram ' + text) 308 | 309 | /// / Fetch the default direction, use TD if none was found 310 | const diagram = d3.select(`[id="${id}"]`) 311 | insertMarkers(diagram) 312 | 313 | // Layout graph, Create a new directed graph 314 | const g = new graphlib.Graph({ 315 | multigraph: true 316 | }) 317 | 318 | // Set an object for the graph label 319 | g.setGraph({ 320 | isMultiGraph: true 321 | }) 322 | 323 | // Default to assigning a new object as a label for each new edge. 324 | g.setDefaultEdgeLabel(function () { 325 | return {} 326 | }) 327 | 328 | const classes = classDb.getClasses() 329 | const keys = Object.keys(classes) 330 | for (let i = 0; i < keys.length; i++) { 331 | const classDef = classes[keys[i]] 332 | const node = drawClass(diagram, classDef) 333 | // Add nodes to the graph. The first argument is the node id. The second is 334 | // metadata about the node. In this case we're going to add labels to each of 335 | // our nodes. 336 | g.setNode(node.id, node) 337 | logger.info('Org height: ' + node.height) 338 | } 339 | 340 | const relations = classDb.getRelations() 341 | relations.forEach(function (relation) { 342 | logger.info('tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation)) 343 | g.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), { relation: relation }) 344 | }) 345 | dagre.layout(g) 346 | g.nodes().forEach(function (v) { 347 | if (typeof v !== 'undefined') { 348 | logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v))) 349 | d3.select('#' + v).attr('transform', 'translate(' + (g.node(v).x - (g.node(v).width / 2)) + ',' + (g.node(v).y - (g.node(v).height / 2)) + ' )') 350 | } 351 | }) 352 | g.edges().forEach(function (e) { 353 | logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e))) 354 | drawEdge(diagram, g.edge(e), g.edge(e).relation) 355 | }) 356 | 357 | diagram.attr('height', '100%') 358 | diagram.attr('width', '100%') 359 | diagram.attr('viewBox', '0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20)) 360 | } 361 | 362 | export default { 363 | setConf, 364 | draw 365 | } 366 | -------------------------------------------------------------------------------- /assets/mermaidAPI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --- 3 | * title: mermaidAPI 4 | * order: 5 5 | * --- 6 | * # mermaidAPI 7 | * This is the api to be used when handling the integration with the web page instead of using the default integration 8 | * (mermaid.js). 9 | * 10 | * The core of this api is the **render** function that given a graph definitionas text renders the graph/diagram and 11 | * returns a svg element for the graph. It is is then up to the user of the API to make use of the svg, either insert it 12 | * somewhere in the page or something completely different. 13 | */ 14 | import * as d3 from 'd3' 15 | import scope from 'scope-css' 16 | 17 | import { logger, setLogLevel } from './logger' 18 | import utils from './utils' 19 | import flowRenderer from './diagrams/flowchart/flowRenderer' 20 | import flowParser from './diagrams/flowchart/parser/flow' 21 | import flowDb from './diagrams/flowchart/flowDb' 22 | import sequenceRenderer from './diagrams/sequence/sequenceRenderer' 23 | import sequenceParser from './diagrams/sequence/parser/sequenceDiagram' 24 | import sequenceDb from './diagrams/sequence/sequenceDb' 25 | import ganttRenderer from './diagrams/gantt/ganttRenderer' 26 | import ganttParser from './diagrams/gantt/parser/gantt' 27 | import ganttDb from './diagrams/gantt/ganttDb' 28 | import classRenderer from './diagrams/class/classRenderer' 29 | import classParser from './diagrams/class/parser/classDiagram' 30 | import classDb from './diagrams/class/classDb' 31 | import gitGraphRenderer from './diagrams/git/gitGraphRenderer' 32 | import gitGraphParser from './diagrams/git/parser/gitGraph' 33 | import gitGraphAst from './diagrams/git/gitGraphAst' 34 | 35 | const themes = {} 36 | for (const themeName of ['default', 'forest', 'dark', 'neutral']) { 37 | themes[themeName] = require(`./themes/${themeName}/index.scss`) 38 | } 39 | 40 | /** 41 | * ## Configuration 42 | * These are the default options which can be overridden with the initialization call as in the example below: 43 | * ``` 44 | * mermaid.initialize({ 45 | * flowchart:{ 46 | * htmlLabels: false 47 | * } 48 | * }); 49 | * ``` 50 | */ 51 | const config = { 52 | theme: 'default', 53 | themeCSS: undefined, 54 | 55 | /** 56 | * logLevel , decides the amount of logging to be used. 57 | * * debug: 1 58 | * * info: 2 59 | * * warn: 3 60 | * * error: 4 61 | * * fatal: 5 62 | */ 63 | logLevel: 5, 64 | 65 | /** 66 | * **startOnLoad** - This options controls whether or mermaid starts when the page loads 67 | */ 68 | startOnLoad: true, 69 | 70 | /** 71 | * **arrowMarkerAbsolute** - This options controls whether or arrow markers in html code will be absolute paths or 72 | * an anchor, #. This matters if you are using base tag settings. 73 | */ 74 | arrowMarkerAbsolute: false, 75 | 76 | /** 77 | * ### flowchart 78 | * *The object containing configurations specific for flowcharts* 79 | */ 80 | flowchart: { 81 | /** 82 | * **htmlLabels** - Flag for setting whether or not a html tag should be used for rendering labels 83 | * on the edges 84 | */ 85 | htmlLabels: true, 86 | 87 | curve: 'linear' 88 | }, 89 | 90 | /** 91 | * ### sequenceDiagram 92 | * The object containing configurations specific for sequence diagrams 93 | */ 94 | sequence: { 95 | 96 | /** 97 | * **diagramMarginX** - margin to the right and left of the sequence diagram 98 | */ 99 | diagramMarginX: 50, 100 | 101 | /** 102 | * **diagramMarginY** - margin to the over and under the sequence diagram 103 | */ 104 | diagramMarginY: 10, 105 | 106 | /** 107 | * **actorMargin** - Margin between actors 108 | */ 109 | actorMargin: 50, 110 | 111 | /** 112 | * **width** - Width of actor boxes 113 | */ 114 | width: 150, 115 | 116 | /** 117 | * **height** - Height of actor boxes 118 | */ 119 | height: 65, 120 | 121 | /** 122 | * **boxMargin** - Margin around loop boxes 123 | */ 124 | boxMargin: 10, 125 | 126 | /** 127 | * **boxTextMargin** - margin around the text in loop/alt/opt boxes 128 | */ 129 | boxTextMargin: 5, 130 | 131 | /** 132 | * **noteMargin** - margin around notes 133 | */ 134 | noteMargin: 10, 135 | 136 | /** 137 | * **messageMargin** - Space between messages 138 | */ 139 | messageMargin: 35, 140 | 141 | /** 142 | * **mirrorActors** - mirror actors under diagram 143 | */ 144 | mirrorActors: true, 145 | 146 | /** 147 | * **bottomMarginAdj** - Depending on css styling this might need adjustment. 148 | * Prolongs the edge of the diagram downwards 149 | */ 150 | bottomMarginAdj: 1, 151 | 152 | /** 153 | * **useMaxWidth** - when this flag is set the height and width is set to 100% and is then scaling with the 154 | * available space if not the absolute space required is used 155 | */ 156 | useMaxWidth: true 157 | }, 158 | 159 | /** ### gantt 160 | * The object containing configurations specific for gantt diagrams* 161 | */ 162 | gantt: { 163 | /** 164 | * **titleTopMargin** - margin top for the text over the gantt diagram 165 | */ 166 | titleTopMargin: 25, 167 | 168 | /** 169 | * **barHeight** - the height of the bars in the graph 170 | */ 171 | barHeight: 20, 172 | 173 | /** 174 | * **barGap** - the margin between the different activities in the gantt diagram 175 | */ 176 | barGap: 4, 177 | 178 | /** 179 | * **topPadding** - margin between title and gantt diagram and between axis and gantt diagram. 180 | */ 181 | topPadding: 50, 182 | 183 | /** 184 | * **leftPadding** - the space allocated for the section name to the left of the activities. 185 | */ 186 | leftPadding: 75, 187 | 188 | /** 189 | * **gridLineStartPadding** - Vertical starting position of the grid lines 190 | */ 191 | gridLineStartPadding: 35, 192 | 193 | /** 194 | * **fontSize** - font size ... 195 | */ 196 | fontSize: 11, 197 | 198 | /** 199 | * **fontFamily** - font family ... 200 | */ 201 | fontFamily: '"Open-Sans", "sans-serif"', 202 | 203 | /** 204 | * **numberSectionStyles** - the number of alternating section styles 205 | */ 206 | numberSectionStyles: 4, 207 | 208 | /** 209 | * **axisFormat** - datetime format of the axis, this might need adjustment to match your locale and preferences 210 | */ 211 | axisFormat: '%Y-%m-%d' 212 | }, 213 | class: {}, 214 | git: {} 215 | } 216 | 217 | setLogLevel(config.logLevel) 218 | 219 | function parse (text) { 220 | const graphType = utils.detectType(text) 221 | let parser 222 | 223 | switch (graphType) { 224 | case 'git': 225 | parser = gitGraphParser 226 | parser.parser.yy = gitGraphAst 227 | break 228 | case 'flowchart': 229 | parser = flowParser 230 | parser.parser.yy = flowDb 231 | break 232 | case 'sequence': 233 | parser = sequenceParser 234 | parser.parser.yy = sequenceDb 235 | break 236 | case 'gantt': 237 | parser = ganttParser 238 | parser.parser.yy = ganttDb 239 | break 240 | case 'class': 241 | parser = classParser 242 | parser.parser.yy = classDb 243 | break 244 | } 245 | 246 | parser.parser.yy.parseError = (str, hash) => { 247 | const error = { str, hash } 248 | throw error 249 | } 250 | 251 | parser.parse(text) 252 | } 253 | 254 | export const encodeEntities = function (text) { 255 | let txt = text 256 | 257 | txt = txt.replace(/style.*:\S*#.*;/g, function (s) { 258 | const innerTxt = s.substring(0, s.length - 1) 259 | return innerTxt 260 | }) 261 | txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) { 262 | const innerTxt = s.substring(0, s.length - 1) 263 | return innerTxt 264 | }) 265 | 266 | txt = txt.replace(/#\w+;/g, function (s) { 267 | const innerTxt = s.substring(1, s.length - 1) 268 | 269 | const isInt = /^\+?\d+$/.test(innerTxt) 270 | if (isInt) { 271 | return 'fl°°' + innerTxt + '¶ß' 272 | } else { 273 | return 'fl°' + innerTxt + '¶ß' 274 | } 275 | }) 276 | 277 | return txt 278 | } 279 | 280 | export const decodeEntities = function (text) { 281 | let txt = text 282 | 283 | txt = txt.replace(/fl°°/g, function () { 284 | return '&#' 285 | }) 286 | txt = txt.replace(/fl°/g, function () { 287 | return '&' 288 | }) 289 | txt = txt.replace(/¶ß/g, function () { 290 | return ';' 291 | }) 292 | 293 | return txt 294 | } 295 | /** 296 | * ##render 297 | * Function that renders an svg with a graph from a chart definition. Usage example below. 298 | * 299 | * ``` 300 | * mermaidAPI.initialize({ 301 | * startOnLoad:true 302 | * }); 303 | * $(function(){ 304 | * const graphDefinition = 'graph TB\na-->b'; 305 | * const cb = function(svgGraph){ 306 | * console.log(svgGraph); 307 | * }; 308 | * mermaidAPI.render('id1',graphDefinition,cb); 309 | * }); 310 | *``` 311 | * @param id the id of the element to be rendered 312 | * @param txt the graph definition 313 | * @param cb callback which is called after rendering is finished with the svg code as inparam. 314 | * @param container selector to element in which a div with the graph temporarily will be inserted. In one is 315 | * provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is 316 | * completed. 317 | */ 318 | const render = function (id, txt, cb, container) { 319 | if (typeof container !== 'undefined') { 320 | container.innerHTML = '' 321 | 322 | d3.select(container).append('div') 323 | .attr('id', 'd' + id) 324 | .append('svg') 325 | .attr('id', id) 326 | .attr('width', '100%') 327 | .attr('xmlns', 'http://www.w3.org/2000/svg') 328 | .append('g') 329 | } else { 330 | const element = document.querySelector('#' + 'd' + id) 331 | if (element) { 332 | element.innerHTML = '' 333 | } 334 | 335 | d3.select('body').append('div') 336 | .attr('id', 'd' + id) 337 | .append('svg') 338 | .attr('id', id) 339 | .attr('width', '100%') 340 | .attr('xmlns', 'http://www.w3.org/2000/svg') 341 | .append('g') 342 | } 343 | 344 | window.txt = txt 345 | txt = encodeEntities(txt) 346 | 347 | const element = d3.select('#d' + id).node() 348 | const graphType = utils.detectType(txt) 349 | 350 | // insert inline style into svg 351 | const svg = element.firstChild 352 | const firstChild = svg.firstChild 353 | 354 | // pre-defined theme 355 | let style = themes[config.theme] 356 | if (style === undefined) { 357 | style = '' 358 | } 359 | 360 | // user provided theme CSS 361 | if (config.themeCSS !== undefined) { 362 | style += `\n${config.themeCSS}` 363 | } 364 | 365 | // classDef 366 | if (graphType === 'flowchart') { 367 | const classes = flowRenderer.getClasses(txt) 368 | for (const className in classes) { 369 | style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }` 370 | } 371 | } 372 | 373 | const style1 = document.createElement('style') 374 | style1.innerHTML = scope(style, `#${id}`) 375 | svg.insertBefore(style1, firstChild) 376 | 377 | const style2 = document.createElement('style') 378 | const cs = window.getComputedStyle(svg) 379 | style2.innerHTML = `#${id} { 380 | color: ${cs.color}; 381 | font: ${cs.font}; 382 | }` 383 | svg.insertBefore(style2, firstChild) 384 | 385 | switch (graphType) { 386 | case 'git': 387 | config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute 388 | gitGraphRenderer.setConf(config.git) 389 | gitGraphRenderer.draw(txt, id, false) 390 | break 391 | case 'flowchart': 392 | config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute 393 | flowRenderer.setConf(config.flowchart) 394 | flowRenderer.draw(txt, id, false) 395 | break 396 | case 'sequence': 397 | config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute 398 | if (config.sequenceDiagram) { // backwards compatibility 399 | sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram)) 400 | console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.') 401 | } else { 402 | sequenceRenderer.setConf(config.sequence) 403 | } 404 | sequenceRenderer.draw(txt, id) 405 | break 406 | case 'gantt': 407 | config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute 408 | ganttRenderer.setConf(config.gantt) 409 | ganttRenderer.draw(txt, id) 410 | break 411 | case 'class': 412 | config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute 413 | classRenderer.setConf(config.class) 414 | classRenderer.draw(txt, id) 415 | break 416 | } 417 | 418 | d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml') 419 | 420 | let url = '' 421 | if (config.arrowMarkerAbsolute) { 422 | url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search 423 | url = url.replace(/\(/g, '\\(') 424 | url = url.replace(/\)/g, '\\)') 425 | } 426 | 427 | // Fix for when the base tag is used 428 | let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g') 429 | 430 | svgCode = decodeEntities(svgCode) 431 | 432 | if (typeof cb !== 'undefined') { 433 | cb(svgCode, flowDb.bindFunctions) 434 | } else { 435 | logger.warn('CB = undefined!') 436 | } 437 | 438 | const node = d3.select('#d' + id).node() 439 | if (node !== null && typeof node.remove === 'function') { 440 | d3.select('#d' + id).node().remove() 441 | } 442 | 443 | return svgCode 444 | } 445 | 446 | const setConf = function (cnf) { 447 | // Top level initially mermaid, gflow, sequenceDiagram and gantt 448 | const lvl1Keys = Object.keys(cnf) 449 | for (let i = 0; i < lvl1Keys.length; i++) { 450 | if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) { 451 | const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]) 452 | 453 | for (let j = 0; j < lvl2Keys.length; j++) { 454 | logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j]) 455 | if (typeof config[lvl1Keys[i]] === 'undefined') { 456 | config[lvl1Keys[i]] = {} 457 | } 458 | logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]]) 459 | config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]] 460 | } 461 | } else { 462 | config[lvl1Keys[i]] = cnf[lvl1Keys[i]] 463 | } 464 | } 465 | } 466 | 467 | function initialize (options) { 468 | logger.debug('Initializing mermaidAPI') 469 | // Update default config with options supplied at initialization 470 | if (typeof options === 'object') { 471 | setConf(options) 472 | } 473 | setLogLevel(config.logLevel) 474 | } 475 | 476 | function getConfig () { 477 | return config 478 | } 479 | 480 | const mermaidAPI = { 481 | render, 482 | parse, 483 | initialize, 484 | getConfig 485 | } 486 | 487 | export default mermaidAPI 488 | -------------------------------------------------------------------------------- /assets/diagrams/flowchart/flowRenderer.js: -------------------------------------------------------------------------------- 1 | import graphlib from 'graphlibrary' 2 | import * as d3 from 'd3' 3 | 4 | import flowDb from './flowDb' 5 | import flow from './parser/flow' 6 | import dagreD3 from 'dagre-d3-renderer' 7 | import { logger } from '../../logger' 8 | import { interpolateToCurve } from '../../utils' 9 | 10 | const conf = { 11 | } 12 | export const setConf = function (cnf) { 13 | const keys = Object.keys(cnf) 14 | for (let i = 0; i < keys.length; i++) { 15 | conf[keys[i]] = cnf[keys[i]] 16 | } 17 | } 18 | 19 | /** 20 | * Function that adds the vertices found in the graph definition to the graph to be rendered. 21 | * @param vert Object containing the vertices. 22 | * @param g The graph that is to be drawn. 23 | */ 24 | export const addVertices = function (vert, g) { 25 | const keys = Object.keys(vert) 26 | 27 | const styleFromStyleArr = function (styleStr, arr) { 28 | // Create a compound style definition from the style definitions found for the node in the graph definition 29 | for (let i = 0; i < arr.length; i++) { 30 | if (typeof arr[i] !== 'undefined') { 31 | styleStr = styleStr + arr[i] + ';' 32 | } 33 | } 34 | 35 | return styleStr 36 | } 37 | 38 | // Iterate through each item in the vertice object (containing all the vertices found) in the graph definition 39 | keys.forEach(function (id) { 40 | const vertice = vert[id] 41 | let verticeText 42 | 43 | /** 44 | * Variable for storing the classes for the vertice 45 | * @type {string} 46 | */ 47 | let classStr = '' 48 | if (vertice.classes.length > 0) { 49 | classStr = vertice.classes.join(' ') 50 | } 51 | 52 | /** 53 | * Variable for storing the extracted style for the vertice 54 | * @type {string} 55 | */ 56 | let style = '' 57 | // Create a compound style definition from the style definitions found for the node in the graph definition 58 | style = styleFromStyleArr(style, vertice.styles) 59 | 60 | // Use vertice id as text in the box if no text is provided by the graph definition 61 | if (typeof vertice.text === 'undefined') { 62 | verticeText = vertice.id 63 | } else { 64 | verticeText = vertice.text 65 | } 66 | 67 | let labelTypeStr = '' 68 | if (conf.htmlLabels) { 69 | labelTypeStr = 'html' 70 | verticeText = verticeText.replace(/fa:fa[\w-]+/g, function (s) { 71 | return '<i class="fa ' + s.substring(3) + '"></i>' 72 | }) 73 | } else { 74 | const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text') 75 | 76 | const rows = verticeText.split(/<br>/) 77 | 78 | for (let j = 0; j < rows.length; j++) { 79 | const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan') 80 | tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve') 81 | tspan.setAttribute('dy', '1em') 82 | tspan.setAttribute('x', '1') 83 | tspan.textContent = rows[j] 84 | svgLabel.appendChild(tspan) 85 | } 86 | 87 | labelTypeStr = 'svg' 88 | verticeText = svgLabel 89 | } 90 | 91 | let radious = 0 92 | let _shape = '' 93 | // Set the shape based parameters 94 | switch (vertice.type) { 95 | case 'round': 96 | radious = 5 97 | _shape = 'rect' 98 | break 99 | case 'square': 100 | _shape = 'rect' 101 | break 102 | case 'diamond': 103 | _shape = 'question' 104 | break 105 | case 'odd': 106 | _shape = 'rect_left_inv_arrow' 107 | break 108 | case 'odd_right': 109 | _shape = 'rect_left_inv_arrow' 110 | break 111 | case 'circle': 112 | _shape = 'circle' 113 | break 114 | case 'ellipse': 115 | _shape = 'ellipse' 116 | break 117 | case 'group': 118 | _shape = 'rect' 119 | // Need to create a text node if using svg labels, see #367 120 | verticeText = conf.htmlLabels ? '' : document.createElementNS('http://www.w3.org/2000/svg', 'text') 121 | break 122 | default: 123 | _shape = 'rect' 124 | } 125 | // Add the node 126 | g.setNode(vertice.id, { labelType: labelTypeStr, shape: _shape, label: verticeText, rx: radious, ry: radious, 'class': classStr, style: style, id: vertice.id }) 127 | }) 128 | } 129 | 130 | /** 131 | * Add edges to graph based on parsed graph defninition 132 | * @param {Object} edges The edges to add to the graph 133 | * @param {Object} g The graph object 134 | */ 135 | export const addEdges = function (edges, g) { 136 | let cnt = 0 137 | 138 | let defaultStyle 139 | if (typeof edges.defaultStyle !== 'undefined') { 140 | defaultStyle = edges.defaultStyle.toString().replace(/,/g, ';') 141 | } 142 | 143 | edges.forEach(function (edge) { 144 | cnt++ 145 | const edgeData = {} 146 | 147 | // Set link type for rendering 148 | if (edge.type === 'arrow_open') { 149 | edgeData.arrowhead = 'none' 150 | } else { 151 | edgeData.arrowhead = 'normal' 152 | } 153 | 154 | let style = '' 155 | if (typeof edge.style !== 'undefined') { 156 | edge.style.forEach(function (s) { 157 | style = style + s + ';' 158 | }) 159 | } else { 160 | switch (edge.stroke) { 161 | case 'normal': 162 | style = 'fill:none' 163 | if (typeof defaultStyle !== 'undefined') { 164 | style = defaultStyle 165 | } 166 | break 167 | case 'dotted': 168 | style = 'stroke: #333; fill:none;stroke-width:2px;stroke-dasharray:3;' 169 | break 170 | case 'thick': 171 | style = 'stroke: #333; stroke-width: 3.5px;fill:none' 172 | break 173 | } 174 | } 175 | edgeData.style = style 176 | 177 | if (typeof edge.interpolate !== 'undefined') { 178 | edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear) 179 | } else if (typeof edges.defaultInterpolate !== 'undefined') { 180 | edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear) 181 | } else { 182 | edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear) 183 | } 184 | 185 | if (typeof edge.text === 'undefined') { 186 | if (typeof edge.style !== 'undefined') { 187 | edgeData.arrowheadStyle = 'fill: #333' 188 | } 189 | } else { 190 | edgeData.arrowheadStyle = 'fill: #333' 191 | if (typeof edge.style === 'undefined') { 192 | edgeData.labelpos = 'c' 193 | if (conf.htmlLabels) { 194 | edgeData.labelType = 'html' 195 | edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>' 196 | } else { 197 | edgeData.labelType = 'text' 198 | edgeData.style = 'stroke: #333; stroke-width: 1.5px;fill:none' 199 | edgeData.label = edge.text.replace(/<br>/g, '\n') 200 | } 201 | } else { 202 | edgeData.label = edge.text.replace(/<br>/g, '\n') 203 | } 204 | } 205 | // Add the edge to the graph 206 | g.setEdge(edge.start, edge.end, edgeData, cnt) 207 | }) 208 | } 209 | 210 | /** 211 | * Returns the all the styles from classDef statements in the graph definition. 212 | * @returns {object} classDef styles 213 | */ 214 | export const getClasses = function (text) { 215 | flowDb.clear() 216 | const parser = flow.parser 217 | parser.yy = flowDb 218 | 219 | // Parse the graph definition 220 | parser.parse(text) 221 | return flowDb.getClasses() 222 | } 223 | 224 | /** 225 | * Draws a flowchart in the tag with id: id based on the graph definition in text. 226 | * @param text 227 | * @param id 228 | */ 229 | export const draw = function (text, id) { 230 | logger.debug('Drawing flowchart') 231 | flowDb.clear() 232 | const parser = flow.parser 233 | parser.yy = flowDb 234 | 235 | // Parse the graph definition 236 | try { 237 | parser.parse(text) 238 | } catch (err) { 239 | logger.debug('Parsing failed') 240 | } 241 | 242 | // Fetch the default direction, use TD if none was found 243 | let dir = flowDb.getDirection() 244 | if (typeof dir === 'undefined') { 245 | dir = 'TD' 246 | } 247 | 248 | // Create the input mermaid.graph 249 | const g = new graphlib.Graph({ 250 | multigraph: true, 251 | compound: true 252 | }) 253 | .setGraph({ 254 | rankdir: dir, 255 | marginx: 20, 256 | marginy: 20 257 | 258 | }) 259 | .setDefaultEdgeLabel(function () { 260 | return {} 261 | }) 262 | 263 | let subG 264 | const subGraphs = flowDb.getSubGraphs() 265 | for (let i = subGraphs.length - 1; i >= 0; i--) { 266 | subG = subGraphs[i] 267 | flowDb.addVertex(subG.id, subG.title, 'group', undefined) 268 | } 269 | 270 | // Fetch the verices/nodes and edges/links from the parsed graph definition 271 | const vert = flowDb.getVertices() 272 | 273 | const edges = flowDb.getEdges() 274 | 275 | let i = 0 276 | for (i = subGraphs.length - 1; i >= 0; i--) { 277 | subG = subGraphs[i] 278 | 279 | d3.selectAll('cluster').append('text') 280 | 281 | for (let j = 0; j < subG.nodes.length; j++) { 282 | g.setParent(subG.nodes[j], subG.id) 283 | } 284 | } 285 | addVertices(vert, g) 286 | addEdges(edges, g) 287 | 288 | // Create the renderer 289 | const Render = dagreD3.render 290 | const render = new Render() 291 | 292 | // Add custom shape for rhombus type of boc (decision) 293 | render.shapes().question = function (parent, bbox, node) { 294 | const w = bbox.width 295 | const h = bbox.height 296 | const s = (w + h) * 0.9 297 | const points = [ 298 | { x: s / 2, y: 0 }, 299 | { x: s, y: -s / 2 }, 300 | { x: s / 2, y: -s }, 301 | { x: 0, y: -s / 2 } 302 | ] 303 | const shapeSvg = parent.insert('polygon', ':first-child') 304 | .attr('points', points.map(function (d) { 305 | return d.x + ',' + d.y 306 | }).join(' ')) 307 | .attr('rx', 5) 308 | .attr('ry', 5) 309 | .attr('transform', 'translate(' + (-s / 2) + ',' + (s * 2 / 4) + ')') 310 | node.intersect = function (point) { 311 | return dagreD3.intersect.polygon(node, points, point) 312 | } 313 | return shapeSvg 314 | } 315 | 316 | // Add custom shape for box with inverted arrow on left side 317 | render.shapes().rect_left_inv_arrow = function (parent, bbox, node) { 318 | const w = bbox.width 319 | const h = bbox.height 320 | const points = [ 321 | { x: -h / 2, y: 0 }, 322 | { x: w, y: 0 }, 323 | { x: w, y: -h }, 324 | { x: -h / 2, y: -h }, 325 | { x: 0, y: -h / 2 } 326 | ] 327 | const shapeSvg = parent.insert('polygon', ':first-child') 328 | .attr('points', points.map(function (d) { 329 | return d.x + ',' + d.y 330 | }).join(' ')) 331 | .attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') 332 | node.intersect = function (point) { 333 | return dagreD3.intersect.polygon(node, points, point) 334 | } 335 | return shapeSvg 336 | } 337 | 338 | // Add custom shape for box with inverted arrow on right side 339 | render.shapes().rect_right_inv_arrow = function (parent, bbox, node) { 340 | const w = bbox.width 341 | const h = bbox.height 342 | const points = [ 343 | { x: 0, y: 0 }, 344 | { x: w + h / 2, y: 0 }, 345 | { x: w, y: -h / 2 }, 346 | { x: w + h / 2, y: -h }, 347 | { x: 0, y: -h } 348 | ] 349 | const shapeSvg = parent.insert('polygon', ':first-child') 350 | .attr('points', points.map(function (d) { 351 | return d.x + ',' + d.y 352 | }).join(' ')) 353 | .attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')') 354 | node.intersect = function (point) { 355 | return dagreD3.intersect.polygon(node, points, point) 356 | } 357 | return shapeSvg 358 | } 359 | 360 | // Add our custom arrow - an empty arrowhead 361 | render.arrows().none = function normal (parent, id, edge, type) { 362 | const marker = parent.append('marker') 363 | .attr('id', id) 364 | .attr('viewBox', '0 0 10 10') 365 | .attr('refX', 9) 366 | .attr('refY', 5) 367 | .attr('markerUnits', 'strokeWidth') 368 | .attr('markerWidth', 8) 369 | .attr('markerHeight', 6) 370 | .attr('orient', 'auto') 371 | 372 | const path = marker.append('path') 373 | .attr('d', 'M 0 0 L 0 0 L 0 0 z') 374 | dagreD3.util.applyStyle(path, edge[type + 'Style']) 375 | } 376 | 377 | // Override normal arrowhead defined in d3. Remove style & add class to allow css styling. 378 | render.arrows().normal = function normal (parent, id, edge, type) { 379 | const marker = parent.append('marker') 380 | .attr('id', id) 381 | .attr('viewBox', '0 0 10 10') 382 | .attr('refX', 9) 383 | .attr('refY', 5) 384 | .attr('markerUnits', 'strokeWidth') 385 | .attr('markerWidth', 8) 386 | .attr('markerHeight', 6) 387 | .attr('orient', 'auto') 388 | 389 | marker.append('path') 390 | .attr('d', 'M 0 0 L 10 5 L 0 10 z') 391 | .attr('class', 'arrowheadPath') 392 | .style('stroke-width', 1) 393 | .style('stroke-dasharray', '1,0') 394 | } 395 | 396 | // Set up an SVG group so that we can translate the final graph. 397 | const svg = d3.select(`[id="${id}"]`) 398 | 399 | // Run the renderer. This is what draws the final graph. 400 | const element = d3.select('#' + id + ' g') 401 | render(element, g) 402 | 403 | element.selectAll('g.node') 404 | .attr('title', function () { 405 | return flowDb.getTooltip(this.id) 406 | }) 407 | 408 | const padding = 8 409 | const width = g.maxX - g.minX + padding * 2 410 | const height = g.maxY - g.minY + padding * 2 411 | svg.attr('width', '100%') 412 | svg.attr('style', `max-width: ${width}px;`) 413 | svg.attr('viewBox', `0 0 ${width} ${height}`) 414 | svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`) 415 | 416 | // Index nodes 417 | flowDb.indexNodes('subGraph' + i) 418 | 419 | for (i = 0; i < subGraphs.length; i++) { 420 | subG = subGraphs[i] 421 | 422 | if (subG.title !== 'undefined') { 423 | const clusterRects = document.querySelectorAll('#' + id + ' #' + subG.id + ' rect') 424 | const clusterEl = document.querySelectorAll('#' + id + ' #' + subG.id) 425 | 426 | const xPos = clusterRects[0].x.baseVal.value 427 | const yPos = clusterRects[0].y.baseVal.value 428 | const width = clusterRects[0].width.baseVal.value 429 | const cluster = d3.select(clusterEl[0]) 430 | const te = cluster.append('text') 431 | te.attr('x', xPos + width / 2) 432 | te.attr('y', yPos + 14) 433 | te.attr('fill', 'black') 434 | te.attr('stroke', 'none') 435 | te.attr('id', id + 'Text') 436 | te.style('text-anchor', 'middle') 437 | 438 | if (typeof subG.title === 'undefined') { 439 | te.text('Undef') 440 | } else { 441 | te.text(subG.title) 442 | } 443 | } 444 | } 445 | 446 | // Add label rects for non html labels 447 | if (!conf.htmlLabels) { 448 | const labels = document.querySelectorAll('#' + id + ' .edgeLabel .label') 449 | for (let k = 0; k < labels.length; k++) { 450 | const label = labels[k] 451 | 452 | // Get dimensions of label 453 | const dim = label.getBBox() 454 | 455 | const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') 456 | rect.setAttribute('rx', 0) 457 | rect.setAttribute('ry', 0) 458 | rect.setAttribute('width', dim.width) 459 | rect.setAttribute('height', dim.height) 460 | rect.setAttribute('style', 'fill:#e8e8e8;') 461 | 462 | label.insertBefore(rect, label.firstChild) 463 | } 464 | } 465 | } 466 | 467 | export default { 468 | setConf, 469 | addVertices, 470 | addEdges, 471 | getClasses, 472 | draw 473 | } 474 | -------------------------------------------------------------------------------- /assets/diagrams/flowchart/parser/flow.jison: -------------------------------------------------------------------------------- 1 | /** mermaid 2 | * https://mermaidjs.github.io/ 3 | * (c) 2015 Knut Sveidqvist 4 | * MIT license. 5 | */ 6 | 7 | /* lexical grammar */ 8 | %lex 9 | %x string 10 | 11 | %% 12 | \%\%[^\n]* /* do nothing */ 13 | ["] this.begin("string"); 14 | <string>["] this.popState(); 15 | <string>[^"]* return "STR"; 16 | "style" return 'STYLE'; 17 | "default" return 'DEFAULT'; 18 | "linkStyle" return 'LINKSTYLE'; 19 | "interpolate" return 'INTERPOLATE'; 20 | "classDef" return 'CLASSDEF'; 21 | "class" return 'CLASS'; 22 | "click" return 'CLICK'; 23 | "graph" return 'GRAPH'; 24 | "subgraph" return 'subgraph'; 25 | "end"\b\s* return 'end'; 26 | "LR" return 'DIR'; 27 | "RL" return 'DIR'; 28 | "TB" return 'DIR'; 29 | "BT" return 'DIR'; 30 | "TD" return 'DIR'; 31 | "BR" return 'DIR'; 32 | [0-9]+ return 'NUM'; 33 | \# return 'BRKT'; 34 | ":" return 'COLON'; 35 | ";" return 'SEMI'; 36 | "," return 'COMMA'; 37 | "*" return 'MULT'; 38 | "<" return 'TAGSTART'; 39 | ">" return 'TAGEND'; 40 | "^" return 'UP'; 41 | "v" return 'DOWN'; 42 | \s*\-\-[x]\s* return 'ARROW_CROSS'; 43 | \s*\-\-\>\s* return 'ARROW_POINT'; 44 | \s*\-\-[o]\s* return 'ARROW_CIRCLE'; 45 | \s*\-\-\-\s* return 'ARROW_OPEN'; 46 | \s*\-\.\-[x]\s* return 'DOTTED_ARROW_CROSS'; 47 | \s*\-\.\-\>\s* return 'DOTTED_ARROW_POINT'; 48 | \s*\-\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE'; 49 | \s*\-\.\-\s* return 'DOTTED_ARROW_OPEN'; 50 | \s*.\-[x]\s* return 'DOTTED_ARROW_CROSS'; 51 | \s*\.\-\>\s* return 'DOTTED_ARROW_POINT'; 52 | \s*\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE'; 53 | \s*\.\-\s* return 'DOTTED_ARROW_OPEN'; 54 | \s*\=\=[x]\s* return 'THICK_ARROW_CROSS'; 55 | \s*\=\=\>\s* return 'THICK_ARROW_POINT'; 56 | \s*\=\=[o]\s* return 'THICK_ARROW_CIRCLE'; 57 | \s*\=\=[\=]\s* return 'THICK_ARROW_OPEN'; 58 | \s*\-\-\s* return '--'; 59 | \s*\-\.\s* return '-.'; 60 | \s*\=\=\s* return '=='; 61 | "(-" return '(-'; 62 | "-)" return '-)'; 63 | \- return 'MINUS'; 64 | "." return 'DOT'; 65 | \+ return 'PLUS'; 66 | \% return 'PCT'; 67 | "=" return 'EQUALS'; 68 | \= return 'EQUALS'; 69 | [A-Za-z]+ return 'ALPHA'; 70 | [!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION'; 71 | [\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]| 72 | [\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]| 73 | [\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]| 74 | [\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]| 75 | [\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]| 76 | [\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]| 77 | [\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]| 78 | [\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]| 79 | [\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]| 80 | [\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]| 81 | [\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]| 82 | [\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]| 83 | [\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]| 84 | [\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]| 85 | [\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]| 86 | [\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]| 87 | [\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]| 88 | [\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]| 89 | [\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]| 90 | [\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]| 91 | [\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]| 92 | [\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]| 93 | [\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]| 94 | [\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]| 95 | [\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]| 96 | [\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]| 97 | [\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]| 98 | [\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]| 99 | [\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]| 100 | [\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]| 101 | [\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]| 102 | [\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]| 103 | [\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]| 104 | [\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]| 105 | [\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]| 106 | [\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]| 107 | [\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]| 108 | [\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]| 109 | [\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]| 110 | [\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]| 111 | [\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]| 112 | [\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]| 113 | [\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]| 114 | [\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]| 115 | [\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]| 116 | [\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]| 117 | [\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]| 118 | [\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]| 119 | [\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]| 120 | [\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]| 121 | [\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]| 122 | [\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]| 123 | [\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]| 124 | [\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]| 125 | [\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]| 126 | [\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]| 127 | [\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]| 128 | [\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]| 129 | [\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]| 130 | [\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]| 131 | [\uFFD2-\uFFD7\uFFDA-\uFFDC] 132 | return 'UNICODE_TEXT'; 133 | "|" return 'PIPE'; 134 | "(" return 'PS'; 135 | ")" return 'PE'; 136 | "[" return 'SQS'; 137 | "]" return 'SQE'; 138 | "{" return 'DIAMOND_START' 139 | "}" return 'DIAMOND_STOP' 140 | "\"" return 'QUOTE'; 141 | \n+ return 'NEWLINE'; 142 | \s return 'SPACE'; 143 | <<EOF>> return 'EOF'; 144 | 145 | /lex 146 | 147 | /* operator associations and precedence */ 148 | 149 | %left '^' 150 | 151 | %start mermaidDoc 152 | 153 | %% /* language grammar */ 154 | 155 | mermaidDoc: graphConfig document; 156 | 157 | document 158 | : /* empty */ 159 | { $$ = [];} 160 | | document line 161 | { 162 | if($2 !== []){ 163 | $1.push($2); 164 | } 165 | $$=$1;} 166 | ; 167 | 168 | line 169 | : statement 170 | {$$=$1;} 171 | | SEMI 172 | | NEWLINE 173 | | SPACE 174 | | EOF 175 | ; 176 | 177 | graphConfig 178 | : SPACE graphConfig 179 | | NEWLINE graphConfig 180 | | GRAPH SPACE DIR FirstStmtSeperator 181 | { yy.setDirection($3);$$ = $3;} 182 | | GRAPH SPACE TAGEND FirstStmtSeperator 183 | { yy.setDirection("LR");$$ = $3;} 184 | | GRAPH SPACE TAGSTART FirstStmtSeperator 185 | { yy.setDirection("RL");$$ = $3;} 186 | | GRAPH SPACE UP FirstStmtSeperator 187 | { yy.setDirection("BT");$$ = $3;} 188 | | GRAPH SPACE DOWN FirstStmtSeperator 189 | { yy.setDirection("TB");$$ = $3;} 190 | ; 191 | 192 | ending: endToken ending 193 | | endToken 194 | ; 195 | 196 | endToken: NEWLINE | SPACE | EOF; 197 | 198 | FirstStmtSeperator 199 | : SEMI | NEWLINE | spaceList NEWLINE ; 200 | 201 | 202 | spaceListNewline 203 | : SPACE spaceListNewline 204 | | NEWLINE spaceListNewline 205 | | NEWLINE 206 | | SPACE 207 | ; 208 | 209 | 210 | spaceList 211 | : SPACE spaceList 212 | | SPACE 213 | ; 214 | 215 | statement 216 | : verticeStatement separator 217 | {$$=$1} 218 | | styleStatement separator 219 | {$$=[];} 220 | | linkStyleStatement separator 221 | {$$=[];} 222 | | classDefStatement separator 223 | {$$=[];} 224 | | classStatement separator 225 | {$$=[];} 226 | | clickStatement separator 227 | {$$=[];} 228 | | subgraph text separator document end 229 | {$$=yy.addSubGraph($4,$2);} 230 | | subgraph separator document end 231 | {$$=yy.addSubGraph($3,undefined);} 232 | ; 233 | 234 | separator: NEWLINE | SEMI | EOF ; 235 | 236 | verticeStatement: 237 | vertex link vertex 238 | { yy.addLink($1,$3,$2);$$ = [$1,$3];} 239 | | vertex 240 | {$$ = [$1];} 241 | ; 242 | 243 | vertex: alphaNum SQS text SQE 244 | {$$ = $1;yy.addVertex($1,$3,'square');} 245 | | alphaNum SQS text SQE spaceList 246 | {$$ = $1;yy.addVertex($1,$3,'square');} 247 | | alphaNum PS PS text PE PE 248 | {$$ = $1;yy.addVertex($1,$4,'circle');} 249 | | alphaNum PS PS text PE PE spaceList 250 | {$$ = $1;yy.addVertex($1,$4,'circle');} 251 | | alphaNum '(-' text '-)' 252 | {$$ = $1;yy.addVertex($1,$3,'ellipse');} 253 | | alphaNum '(-' text '-)' spaceList 254 | {$$ = $1;yy.addVertex($1,$3,'ellipse');} 255 | | alphaNum PS text PE 256 | {$$ = $1;yy.addVertex($1,$3,'round');} 257 | | alphaNum PS text PE spaceList 258 | {$$ = $1;yy.addVertex($1,$3,'round');} 259 | | alphaNum DIAMOND_START text DIAMOND_STOP 260 | {$$ = $1;yy.addVertex($1,$3,'diamond');} 261 | | alphaNum DIAMOND_START text DIAMOND_STOP spaceList 262 | {$$ = $1;yy.addVertex($1,$3,'diamond');} 263 | | alphaNum TAGEND text SQE 264 | {$$ = $1;yy.addVertex($1,$3,'odd');} 265 | | alphaNum TAGEND text SQE spaceList 266 | {$$ = $1;yy.addVertex($1,$3,'odd');} 267 | /* | alphaNum SQS text TAGSTART 268 | {$$ = $1;yy.addVertex($1,$3,'odd_right');} 269 | | alphaNum SQS text TAGSTART spaceList 270 | {$$ = $1;yy.addVertex($1,$3,'odd_right');} */ 271 | | alphaNum 272 | {$$ = $1;yy.addVertex($1);} 273 | | alphaNum spaceList 274 | {$$ = $1;yy.addVertex($1);} 275 | ; 276 | 277 | alphaNum 278 | : alphaNumStatement 279 | {$$=$1;} 280 | | alphaNum alphaNumStatement 281 | {$$=$1+''+$2;} 282 | ; 283 | 284 | alphaNumStatement 285 | : DIR 286 | {$$=$1;} 287 | | alphaNumToken 288 | {$$=$1;} 289 | | DOWN 290 | {$$='v';} 291 | | MINUS 292 | {$$='-';} 293 | ; 294 | 295 | link: linkStatement arrowText 296 | {$1.text = $2;$$ = $1;} 297 | | linkStatement TESTSTR SPACE 298 | {$1.text = $2;$$ = $1;} 299 | | linkStatement arrowText SPACE 300 | {$1.text = $2;$$ = $1;} 301 | | linkStatement 302 | {$$ = $1;} 303 | | '--' text ARROW_POINT 304 | {$$ = {"type":"arrow","stroke":"normal","text":$2};} 305 | | '--' text ARROW_CIRCLE 306 | {$$ = {"type":"arrow_circle","stroke":"normal","text":$2};} 307 | | '--' text ARROW_CROSS 308 | {$$ = {"type":"arrow_cross","stroke":"normal","text":$2};} 309 | | '--' text ARROW_OPEN 310 | {$$ = {"type":"arrow_open","stroke":"normal","text":$2};} 311 | | '-.' text DOTTED_ARROW_POINT 312 | {$$ = {"type":"arrow","stroke":"dotted","text":$2};} 313 | | '-.' text DOTTED_ARROW_CIRCLE 314 | {$$ = {"type":"arrow_circle","stroke":"dotted","text":$2};} 315 | | '-.' text DOTTED_ARROW_CROSS 316 | {$$ = {"type":"arrow_cross","stroke":"dotted","text":$2};} 317 | | '-.' text DOTTED_ARROW_OPEN 318 | {$$ = {"type":"arrow_open","stroke":"dotted","text":$2};} 319 | | '==' text THICK_ARROW_POINT 320 | {$$ = {"type":"arrow","stroke":"thick","text":$2};} 321 | | '==' text THICK_ARROW_CIRCLE 322 | {$$ = {"type":"arrow_circle","stroke":"thick","text":$2};} 323 | | '==' text THICK_ARROW_CROSS 324 | {$$ = {"type":"arrow_cross","stroke":"thick","text":$2};} 325 | | '==' text THICK_ARROW_OPEN 326 | {$$ = {"type":"arrow_open","stroke":"thick","text":$2};} 327 | ; 328 | 329 | linkStatement: ARROW_POINT 330 | {$$ = {"type":"arrow","stroke":"normal"};} 331 | | ARROW_CIRCLE 332 | {$$ = {"type":"arrow_circle","stroke":"normal"};} 333 | | ARROW_CROSS 334 | {$$ = {"type":"arrow_cross","stroke":"normal"};} 335 | | ARROW_OPEN 336 | {$$ = {"type":"arrow_open","stroke":"normal"};} 337 | | DOTTED_ARROW_POINT 338 | {$$ = {"type":"arrow","stroke":"dotted"};} 339 | | DOTTED_ARROW_CIRCLE 340 | {$$ = {"type":"arrow_circle","stroke":"dotted"};} 341 | | DOTTED_ARROW_CROSS 342 | {$$ = {"type":"arrow_cross","stroke":"dotted"};} 343 | | DOTTED_ARROW_OPEN 344 | {$$ = {"type":"arrow_open","stroke":"dotted"};} 345 | | THICK_ARROW_POINT 346 | {$$ = {"type":"arrow","stroke":"thick"};} 347 | | THICK_ARROW_CIRCLE 348 | {$$ = {"type":"arrow_circle","stroke":"thick"};} 349 | | THICK_ARROW_CROSS 350 | {$$ = {"type":"arrow_cross","stroke":"thick"};} 351 | | THICK_ARROW_OPEN 352 | {$$ = {"type":"arrow_open","stroke":"thick"};} 353 | ; 354 | 355 | arrowText: 356 | PIPE text PIPE 357 | {$$ = $2;} 358 | ; 359 | 360 | text: textToken 361 | {$$=$1;} 362 | | text textToken 363 | {$$=$1+''+$2;} 364 | | STR 365 | {$$=$1;} 366 | ; 367 | 368 | 369 | 370 | commentText: commentToken 371 | {$$=$1;} 372 | | commentText commentToken 373 | {$$=$1+''+$2;} 374 | ; 375 | 376 | 377 | keywords 378 | : STYLE | LINKSTYLE | CLASSDEF | CLASS | CLICK | GRAPH | DIR | subgraph | end | DOWN | UP; 379 | 380 | 381 | textNoTags: textNoTagsToken 382 | {$$=$1;} 383 | | textNoTags textNoTagsToken 384 | {$$=$1+''+$2;} 385 | ; 386 | 387 | 388 | classDefStatement:CLASSDEF SPACE DEFAULT SPACE stylesOpt 389 | {$$ = $1;yy.addClass($3,$5);} 390 | | CLASSDEF SPACE alphaNum SPACE stylesOpt 391 | {$$ = $1;yy.addClass($3,$5);} 392 | ; 393 | 394 | classStatement:CLASS SPACE alphaNum SPACE alphaNum 395 | {$$ = $1;yy.setClass($3, $5);} 396 | ; 397 | 398 | clickStatement 399 | : CLICK SPACE alphaNum SPACE alphaNum {$$ = $1;yy.setClickEvent($3, $5, undefined, undefined);} 400 | | CLICK SPACE alphaNum SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, $5, undefined, $7) ;} 401 | | CLICK SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, undefined);} 402 | | CLICK SPACE alphaNum SPACE STR SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, $7 );} 403 | ; 404 | 405 | styleStatement:STYLE SPACE alphaNum SPACE stylesOpt 406 | {$$ = $1;yy.addVertex($3,undefined,undefined,$5);} 407 | | STYLE SPACE HEX SPACE stylesOpt 408 | {$$ = $1;yy.updateLink($3,$5);} 409 | ; 410 | 411 | linkStyleStatement 412 | : LINKSTYLE SPACE DEFAULT SPACE stylesOpt 413 | {$$ = $1;yy.updateLink($3,$5);} 414 | | LINKSTYLE SPACE NUM SPACE stylesOpt 415 | {$$ = $1;yy.updateLink($3,$5);} 416 | | LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt 417 | {$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);} 418 | | LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt 419 | {$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);} 420 | | LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum 421 | {$$ = $1;yy.updateLinkInterpolate($3,$7);} 422 | | LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum 423 | {$$ = $1;yy.updateLinkInterpolate($3,$7);} 424 | ; 425 | 426 | commentStatement: PCT PCT commentText; 427 | 428 | stylesOpt: style 429 | {$$ = [$1]} 430 | | stylesOpt COMMA style 431 | {$1.push($3);$$ = $1;} 432 | ; 433 | 434 | style: styleComponent 435 | |style styleComponent 436 | {$$ = $1 + $2;} 437 | ; 438 | 439 | styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT | STYLE | PCT ; 440 | 441 | /* Token lists */ 442 | 443 | commentToken : textToken | graphCodeTokens ; 444 | 445 | textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT; 446 | 447 | textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ; 448 | 449 | alphaNumToken : ALPHA | PUNCTUATION | UNICODE_TEXT | NUM | COLON | COMMA | PLUS | EQUALS | MULT | DOT | BRKT ; 450 | 451 | graphCodeTokens: PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAG_START | TAG_END | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI ; 452 | %% 453 | --------------------------------------------------------------------------------