mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-10-31 02:44:17 +01:00 
			
		
		
		
	Adds CLI for rendering mermaid files
This commit is contained in:
		
							
								
								
									
										27
									
								
								bin/mermaid.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										27
									
								
								bin/mermaid.js
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | #!/usr/bin/env node | ||||||
|  |  | ||||||
|  | var fs = require('fs') | ||||||
|  |   , chalk = require('chalk') | ||||||
|  |   , error = chalk.bold.red | ||||||
|  |   , cli = require('../lib/cli.js') | ||||||
|  |   , lib = require('../lib') | ||||||
|  |  | ||||||
|  | cli.parse(process.argv.slice(2), function(err, message, options) { | ||||||
|  |   if (err) { | ||||||
|  |     console.error( | ||||||
|  |       error('\nYou had errors in your syntax. Use --help for further information.') | ||||||
|  |     ) | ||||||
|  |     err.forEach(function (e) { | ||||||
|  |       console.error(e.message) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  |   else if (message) { | ||||||
|  |     console.log(message) | ||||||
|  |  | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   lib.process(options.files, options) | ||||||
|  | }) | ||||||
							
								
								
									
										135
									
								
								lib/cli.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								lib/cli.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | var fs = require('fs') | ||||||
|  |   , exec = require('child_process').exec | ||||||
|  |   , chalk = require('chalk') | ||||||
|  |   , which = require('which') | ||||||
|  |   , parseArgs = require('minimist') | ||||||
|  |   , semver = require('semver') | ||||||
|  |  | ||||||
|  | var PHANTOM_VERSION = "^1.9.0" | ||||||
|  |  | ||||||
|  | var info = chalk.blue.bold | ||||||
|  |   , note = chalk.green.bold | ||||||
|  |  | ||||||
|  | var cli = function(options) { | ||||||
|  |   this.options = { | ||||||
|  |       alias: { | ||||||
|  |           help: 'h' | ||||||
|  |         , png: 'p' | ||||||
|  |         , outputDir: 'o' | ||||||
|  |         , svg: 's' | ||||||
|  |         , verbose: 'v' | ||||||
|  |         , phantomPath: 'e' | ||||||
|  |       } | ||||||
|  |     , 'boolean': ['help', 'png', 'svg'] | ||||||
|  |     , 'string': ['outputDir'] | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   this.errors = [] | ||||||
|  |   this.message = null | ||||||
|  |  | ||||||
|  |   this.helpMessage = [ | ||||||
|  |     , info('Usage: mermaid [options] <file>...') | ||||||
|  |     , "" | ||||||
|  |     , "file    The mermaid description file to be rendered" | ||||||
|  |     , "" | ||||||
|  |     , "Options:" | ||||||
|  |     , "  -s --svg          Output SVG instead of PNG (experimental)" | ||||||
|  |     , "  -p --png          If SVG was selected, and you also want PNG, set this flag" | ||||||
|  |     , "  -o --outputDir    Directory to save files, will be created automatically, defaults to `cwd`" | ||||||
|  |     , "  -e --phantomPath  Specify the path to the phantomjs executable" | ||||||
|  |     , "  -h --help         Show this message" | ||||||
|  |     , "  -v --verbose      Show logging" | ||||||
|  |     , "  --version         Print version and quit" | ||||||
|  |   ] | ||||||
|  |  | ||||||
|  |   return this | ||||||
|  | } | ||||||
|  |  | ||||||
|  | cli.prototype.parse = function(argv, next) { | ||||||
|  |   var options = parseArgs(argv, this.options) | ||||||
|  |     , phantom | ||||||
|  |  | ||||||
|  |   if (options.version) { | ||||||
|  |     var pkg = require('../package.json') | ||||||
|  |     this.message = "" + pkg.version | ||||||
|  |   } | ||||||
|  |   else if (options.help) { | ||||||
|  |     this.message = this.helpMessage.join('\n') | ||||||
|  |   } | ||||||
|  |   else { | ||||||
|  |     options.files = options._ | ||||||
|  |  | ||||||
|  |     if (!options.files.length) { | ||||||
|  |       this.errors.push(new Error("You must specify at least one source file.")) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // ensure that parameter-expecting options have parameters | ||||||
|  |     ;['outputDir', 'phantomPath'].forEach(function(i) { | ||||||
|  |       if(typeof options[i] !== 'undefined') { | ||||||
|  |         if (typeof options[i] !== 'string' || options[i].length < 1) { | ||||||
|  |           this.errors.push(new Error(i + " expects a value.")) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }.bind(this)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (options.svg && !options.png) { | ||||||
|  |     options.png = false | ||||||
|  |   } | ||||||
|  |   else { | ||||||
|  |     options.png = true | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // If phantom hasn't been specified, see if we can find it | ||||||
|  |   if (!options.phantomPath) { | ||||||
|  |     try { | ||||||
|  |       var phantom = require('phantomjs') | ||||||
|  |       options.phantomPath = phantom.path | ||||||
|  |     } catch (e) { | ||||||
|  |       try { | ||||||
|  |         options.phantomPath = which.sync('phantomjs') | ||||||
|  |       } catch (e) { | ||||||
|  |         if (!options.phantomPath) { | ||||||
|  |           var err = [ | ||||||
|  |               "Cannot find phantomjs in your PATH. If phantomjs is installed" | ||||||
|  |             , "you may need to specify its path manually with the '-e' option." | ||||||
|  |             , "If it is not installed, you should view the README for further" | ||||||
|  |             , "details." | ||||||
|  |           ] | ||||||
|  |  | ||||||
|  |           this.errors.push(new Error(err.join('\n'))) | ||||||
|  |           next( | ||||||
|  |               this.errors.length > 0 ? this.errors : null | ||||||
|  |             , this.message | ||||||
|  |             , options) | ||||||
|  |  | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // If we have phantompath, see if its version satisfies our requirements | ||||||
|  |   exec(options.phantomPath + ' --version', function(err, stdout, stderr) { | ||||||
|  |     if (err) { | ||||||
|  |       this.errors.push( | ||||||
|  |         new Error("Could not find phantomjs at the specified path.") | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|  |     else if (!semver.satisfies(stdout, PHANTOM_VERSION)) { | ||||||
|  |       this.message = note( | ||||||
|  |           'mermaid requires phantomjs ' | ||||||
|  |         + PHANTOM_VERSION | ||||||
|  |         + ' to be installed, found version ' | ||||||
|  |         + stdout | ||||||
|  |       ) | ||||||
|  |     } | ||||||
|  |     next(this.errors.length > 0 ? this.errors : null, this.message, options) | ||||||
|  |   }.bind(this)) | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = function() { | ||||||
|  |   return new cli() | ||||||
|  | }() | ||||||
							
								
								
									
										40
									
								
								lib/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | var os = require('os') | ||||||
|  |   , fs = require('fs') | ||||||
|  |   , path = require('path') | ||||||
|  |   , spawn = require('child_process').spawn | ||||||
|  |  | ||||||
|  | var mkdirp = require('mkdirp') | ||||||
|  |  | ||||||
|  | var phantomscript = path.join(__dirname, 'phantomscript.js') | ||||||
|  |  | ||||||
|  | module.exports = { process: processMermaid } | ||||||
|  |  | ||||||
|  | function processMermaid(files, _options, _next) { | ||||||
|  |   var options = _options || {} | ||||||
|  |     , outputDir = options.outputDir || process.cwd() | ||||||
|  |     , next = _next || function() {} | ||||||
|  |     , phantomArgs = [ | ||||||
|  |           phantomscript | ||||||
|  |         , outputDir | ||||||
|  |         , options.png | ||||||
|  |         , options.svg | ||||||
|  |         , options.verbose | ||||||
|  |       ] | ||||||
|  |  | ||||||
|  |   files.forEach(function(file) { | ||||||
|  |     phantomArgs.push(file) | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   mkdirp(outputDir, function(err) { | ||||||
|  |     if (err) { | ||||||
|  |       throw err | ||||||
|  |       return | ||||||
|  |     } | ||||||
|  |     phantom = spawn(options.phantomPath, phantomArgs) | ||||||
|  |  | ||||||
|  |     phantom.on('exit', next) | ||||||
|  |  | ||||||
|  |     phantom.stderr.pipe(process.stderr) | ||||||
|  |     phantom.stdout.pipe(process.stdout) | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										213
									
								
								lib/phantomscript.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								lib/phantomscript.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,213 @@ | |||||||
|  | /** | ||||||
|  |  * Credits: | ||||||
|  |  * - SVG Processing from the NYTimes svg-crowbar, under an MIT license | ||||||
|  |  *   https://github.com/NYTimes/svg-crowbar | ||||||
|  |  * - Thanks to the grunticon project for some guidance | ||||||
|  |  *   https://github.com/filamentgroup/grunticon | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | phantom.onError = function(msg, trace) { | ||||||
|  |   var msgStack = ['PHANTOM ERROR: ' + msg] | ||||||
|  |   if (trace && trace.length) { | ||||||
|  |     msgStack.push('TRACE:') | ||||||
|  |     trace.forEach(function(t) { | ||||||
|  |       msgStack.push( | ||||||
|  |           ' -> ' | ||||||
|  |         + (t.file || t.sourceURL) | ||||||
|  |         + ': ' | ||||||
|  |         + t.line | ||||||
|  |         + (t.function ? ' (in function ' + t.function +')' : '') | ||||||
|  |       ) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  |   system.stderr.write(msgStack.join('\n')) | ||||||
|  |   phantom.exit(1) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var system = require('system') | ||||||
|  |   , fs = require('fs') | ||||||
|  |   , webpage = require('webpage') | ||||||
|  |  | ||||||
|  | var page = webpage.create() | ||||||
|  |   , files = phantom.args.slice(4, phantom.args.length) | ||||||
|  |   , options = { | ||||||
|  |         outputDir: phantom.args[0] | ||||||
|  |       , png: phantom.args[1] === 'true' ? true : false | ||||||
|  |       , svg: phantom.args[2] === 'true' ? true : false | ||||||
|  |       , verbose: phantom.args[3] === 'true' ? true : false | ||||||
|  |     } | ||||||
|  |   , log = logger(options.verbose) | ||||||
|  |  | ||||||
|  | page.content = [ | ||||||
|  |     '<html>' | ||||||
|  |   , '<head>' | ||||||
|  |   , '<style type="text/css">' | ||||||
|  |   , '* { margin: 0; padding: 0; }' | ||||||
|  |   , '</style>' | ||||||
|  |   , '</head>' | ||||||
|  |   , '<body>' | ||||||
|  |   , '</body>' | ||||||
|  |   , '</html>' | ||||||
|  | ].join('\n') | ||||||
|  |  | ||||||
|  | page.injectJs('../dist/mermaid.full.js') | ||||||
|  |  | ||||||
|  | files.forEach(function(file) { | ||||||
|  |   var contents = fs.read(file) | ||||||
|  |     , filename = file.split(fs.separator).slice(-1) | ||||||
|  |     , oParser = new DOMParser() | ||||||
|  |     , oDOM | ||||||
|  |     , svgContent | ||||||
|  |     , allElements | ||||||
|  |  | ||||||
|  |   // this JS is executed in this statement is sandboxed, even though it doesn't | ||||||
|  |   // look like it. we need to serialize then unserialize the svgContent that's | ||||||
|  |   // taken from the DOM | ||||||
|  |   svgContent = page.evaluate(executeInPage, contents) | ||||||
|  |   oDOM = oParser.parseFromString(svgContent, "text/xml") | ||||||
|  |  | ||||||
|  |   resolveSVGElement(oDOM.firstChild) | ||||||
|  |  | ||||||
|  |   // traverse the SVG, and replace all foreignObject elements | ||||||
|  |   // can be removed when https://github.com/knsv/mermaid/issues/58 is resolved | ||||||
|  |   allElements = traverse(oDOM) | ||||||
|  |   for (var i = 0, len = allElements.length; i < len; i++) { | ||||||
|  |     resolveForeignObjects(allElements[i]) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (options.png) { | ||||||
|  |     page.viewportSize = { | ||||||
|  |         width: ~~oDOM.documentElement.attributes.getNamedItem('width').value | ||||||
|  |       , height: ~~oDOM.documentElement.attributes.getNamedItem('height').value | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     page.render(options.outputDir + fs.separator + filename + '.png') | ||||||
|  |     log('saved png: ' + filename + '.png') | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (options.svg) { | ||||||
|  |     var serialize = new XMLSerializer() | ||||||
|  |     fs.write( | ||||||
|  |         options.outputDir + fs.separator + filename + '.svg' | ||||||
|  |       , serialize.serializeToString(oDOM) | ||||||
|  |       , 'w' | ||||||
|  |     ) | ||||||
|  |     log('saved svg: ' + filename + '.svg') | ||||||
|  |   } | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | phantom.exit() | ||||||
|  |  | ||||||
|  | function logger(_verbose) { | ||||||
|  |   var verbose = _verbose | ||||||
|  |  | ||||||
|  |   return function(_message, _level) { | ||||||
|  |     var level = level | ||||||
|  |       , message = _message | ||||||
|  |       , log | ||||||
|  |  | ||||||
|  |     log = level === 'error' ? system.stderr : system.stdout | ||||||
|  |  | ||||||
|  |     if (verbose) { | ||||||
|  |       log.write(message + '\n') | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function traverse(obj){ | ||||||
|  |   var tree = [] | ||||||
|  |  | ||||||
|  |   tree.push(obj) | ||||||
|  |   visit(obj) | ||||||
|  |  | ||||||
|  |   function visit(node) { | ||||||
|  |     if (node && node.hasChildNodes()) { | ||||||
|  |       var child = node.firstChild | ||||||
|  |       while (child) { | ||||||
|  |         if (child.nodeType === 1 && child.nodeName != 'SCRIPT'){ | ||||||
|  |           tree.push(child) | ||||||
|  |           visit(child) | ||||||
|  |         } | ||||||
|  |         child = child.nextSibling | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return tree | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function resolveSVGElement(element) { | ||||||
|  |   var prefix = { | ||||||
|  |           xmlns: "http://www.w3.org/2000/xmlns/" | ||||||
|  |         , xlink: "http://www.w3.org/1999/xlink" | ||||||
|  |         , svg: "http://www.w3.org/2000/svg" | ||||||
|  |       } | ||||||
|  |     , doctype = '<!DOCTYPE svg:svg PUBLIC' | ||||||
|  |         + ' "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"' | ||||||
|  |         + ' "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">' | ||||||
|  |  | ||||||
|  |   element.setAttribute("version", "1.1") | ||||||
|  |   // removing attributes so they aren't doubled up | ||||||
|  |   element.removeAttribute("xmlns") | ||||||
|  |   element.removeAttribute("xlink") | ||||||
|  |   // These are needed for the svg | ||||||
|  |   if (!element.hasAttributeNS(prefix.xmlns, "xmlns")) { | ||||||
|  |     element.setAttributeNS(prefix.xmlns, "xmlns", prefix.svg) | ||||||
|  |   } | ||||||
|  |   if (!element.hasAttributeNS(prefix.xmlns, "xmlns:xlink")) { | ||||||
|  |     element.setAttributeNS(prefix.xmlns, "xmlns:xlink", prefix.xlink) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function resolveForeignObjects(element) { | ||||||
|  |   var children | ||||||
|  |     , textElement | ||||||
|  |     , textSpan | ||||||
|  |  | ||||||
|  |   if (element.tagName === 'foreignObject') { | ||||||
|  |     textElement = document.createElement('text') | ||||||
|  |     textSpan = document.createElement('tspan') | ||||||
|  |     textSpan.setAttribute( | ||||||
|  |         'style' | ||||||
|  |       , 'font-size: 11.5pt; font-family: "sans-serif";' | ||||||
|  |     ) | ||||||
|  |     textSpan.setAttribute('x', 0) | ||||||
|  |     textSpan.setAttribute('y', 14.5) | ||||||
|  |     textSpan.textContent = element.textContent | ||||||
|  |  | ||||||
|  |     textElement.appendChild(textSpan) | ||||||
|  |     element.parentElement.appendChild(textElement) | ||||||
|  |     element.parentElement.removeChild(element) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // The sandboxed function that's executed in-page by phantom | ||||||
|  | function executeInPage(contents) { | ||||||
|  |   var xmlSerializer = new XMLSerializer() | ||||||
|  |     , toRemove | ||||||
|  |     , el | ||||||
|  |     , elContent | ||||||
|  |     , svg | ||||||
|  |     , svgValue | ||||||
|  |  | ||||||
|  |   toRemove = document.getElementsByClassName('mermaid') | ||||||
|  |   if (toRemove && toRemove.length) { | ||||||
|  |     for (var i = 0, len = toRemove.length; i < len; i++) { | ||||||
|  |       toRemove[i].parentNode.removeChild(toRemove[i]) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   el = document.createElement("div") | ||||||
|  |   el.className = 'mermaid' | ||||||
|  |   elContent = document.createTextNode(contents) | ||||||
|  |   el.appendChild(elContent) | ||||||
|  |  | ||||||
|  |   document.body.appendChild(el) | ||||||
|  |  | ||||||
|  |   mermaid.init() | ||||||
|  |  | ||||||
|  |   svg = document.querySelector('svg') | ||||||
|  |   svgValue = xmlSerializer.serializeToString(svg) | ||||||
|  |  | ||||||
|  |   return svgValue | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							| @@ -3,6 +3,9 @@ | |||||||
|   "version": "0.2.16", |   "version": "0.2.16", | ||||||
|   "description": "Markdownish syntax for generating flowcharts", |   "description": "Markdownish syntax for generating flowcharts", | ||||||
|   "main": "src/main.js", |   "main": "src/main.js", | ||||||
|  |   "bin": { | ||||||
|  |     "mermaid": "./bin/mermaid.js" | ||||||
|  |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "test": "gulp coverage" |     "test": "gulp coverage" | ||||||
|   }, |   }, | ||||||
| @@ -13,8 +16,13 @@ | |||||||
|   "author": "", |   "author": "", | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "chalk": "^0.5.1", | ||||||
|  |     "dagre-d3": "~0.3.2", | ||||||
|     "he": "^0.5.0", |     "he": "^0.5.0", | ||||||
|     "dagre-d3": "~0.3.2" |     "minimist": "^1.1.0", | ||||||
|  |     "mkdirp": "^0.5.0", | ||||||
|  |     "semver": "^4.1.1", | ||||||
|  |     "which": "^1.0.8" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "browserify": "~6.2.0", |     "browserify": "~6.2.0", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 fardog
					fardog