From 105af6956a7cf3dc2612e27a9ea5a960ee862ddf Mon Sep 17 00:00:00 2001 From: alinex Date: Wed, 8 Jun 2016 22:29:31 +0200 Subject: [PATCH] Basic test for running markdown with separate parse and render calls. --- src/html.coffee | 10 +- src/plugin.coffee | 273 +++++++++++---------------------------- test/mocha/plugin.coffee | 23 ++++ 3 files changed, 106 insertions(+), 200 deletions(-) create mode 100644 test/mocha/plugin.coffee diff --git a/src/html.coffee b/src/html.coffee index 37be552..73265ba 100644 --- a/src/html.coffee +++ b/src/html.coffee @@ -9,7 +9,7 @@ fs = require 'alinex-fs' mime = require 'mime' inlineCss = require 'inline-css' # markdown -md = require 'markdown-it' +markdownit = require 'markdown-it' hljs = require 'highlight.js' mdContainer = require 'markdown-it-container' mdTitle = require 'markdown-it-title' #extracting title from source (first heading) @@ -64,7 +64,6 @@ module.exports = (report, setup, cb) -> setup = null # create html md = initHtml() - data = {} content = report.toString() # make local files inline # replace local images with base64 @@ -80,9 +79,10 @@ module.exports = (report, setup, cb) -> ) ///, (_, b, f, a) -> f = path.resolve __dirname, '../', f - data = new Buffer(fs.readFileSync f).toString 'base64' - "#{b}data:#{mime.lookup f};base64,#{data}#{a}" + bin = new Buffer(fs.readFileSync f).toString 'base64' + "#{b}data:#{mime.lookup f};base64,#{bin}#{a}" # transform to html + data = {} content = optimizeHtml md.render(content, data), setup?.locale title = setup?.title ? data.title ? 'Report' style = setup?.style ? 'default' @@ -129,7 +129,7 @@ md2html = null initHtml = -> #async.once -> return md2html if md2html # setup markdown it - md2html = md + md2html = markdownit html: true linkify: true typographer: true diff --git a/src/plugin.coffee b/src/plugin.coffee index ec5c5fc..06ec511 100644 --- a/src/plugin.coffee +++ b/src/plugin.coffee @@ -22,208 +22,91 @@ MARKER = '$'.charCodeAt 0 module.exports = (md) -> debug "Init graph plugin..." - # register all graph dialects - for name in ['mermaid'] - # add parser rules - md.block.ruler.before 'fence', 'graph_' + name, parser.mermaid - # add render rules - md.renderer.rules['graph_' + name + '_open'] = renderer.mermaid - md.renderer.rules['graph_' + name + '_close'] = renderer.mermaid + # add parser rules + md.block.ruler.before 'fence', 'graph', parser, ['paragraph', 'reference', 'blockquote', 'list'] + # add render rules + md.renderer.rules.graph = renderer # Parser # ------------------------------------------- -parser = - mermaid: (state, startLine, endLine, silent) -> - haveEndMarker = false - pos = state.bMarks[startLine] + state.tShift[startLine] - max = state.eMarks[startLine] - return false if pos + 3 > max - return false unless MARKER id state.src.charCodeAt pos - # scan marker length - mem = pos - pos = state.skipChars pos, MARKER - len = pos - mem - return false if len < 3 - markup = state.src.slice mem, pos - params = state.src.slice(pos, max).trim() - # Since start is found, we can report success here in validation mode - return false if silent - # search end of block - nextLine = startLine; - loop - nextLine++ - # unclosed block should be autoclosed by end of document. - # also block seems to be autoclosed by end of parent - break if nextLine >= endLine - pos = mem = state.bMarks[nextLine] + state.tShift[nextLine] - max = state.eMarks[nextLine] - # non-empty line with negative indent should stop the list: - # - $$$ - # test - break if pos < max and state.sCount[nextLine] < state.blkIndent - continue unless state.src.charCodeAt(pos) is MARKER - # closing fence should be indented less than 4 spaces - continue if state.sCount[nextLine] - state.blkIndent >= 4 - pos = state.skipChars(pos, MARKER) - # closing code fence must be at least as long as the opening one - continue if pos - mem < len - # make sure tail has spaces only - pos = state.skipSpaces pos - continue if pos < max - haveEndMarker = true - # found! - break; - # If a fence has heading spaces, they should be removed from its inner block - len = state.sCount[startLine] - state.line = nextLine + if haveEndMarker then 1 else 0 - token = state.push 'fence', 'code', 0 - token.info = params - token.content = state.getLines startLine + 1, nextLine, len, true - token.markup = markup - token.map = [ startLine, state.line ] - return true +parser = (state, startLine, endLine, silent) -> + haveEndMarker = false + pos = state.bMarks[startLine] + state.tShift[startLine] + max = state.eMarks[startLine] + return false if pos + 3 > max + return false unless MARKER is state.src.charCodeAt pos + # scan marker length + mem = pos + pos = state.skipChars pos, MARKER + len = pos - mem + return false if len < 3 + markup = state.src.slice mem, pos + params = state.src.slice(pos, max).trim() + # Since start is found, we can report success here in validation mode + return false if silent + # search end of block + nextLine = startLine + loop + nextLine++ + # unclosed block should be autoclosed by end of document. + # also block seems to be autoclosed by end of parent + break if nextLine >= endLine + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + # non-empty line with negative indent should stop the list: + # - $$$ + # test + break if pos < max and state.sCount[nextLine] < state.blkIndent + continue unless state.src.charCodeAt(pos) is MARKER + # closing fence should be indented less than 4 spaces + continue if state.sCount[nextLine] - state.blkIndent >= 4 + pos = state.skipChars(pos, MARKER) + # closing code fence must be at least as long as the opening one + continue if pos - mem < len + # make sure tail has spaces only + pos = state.skipSpaces pos + continue if pos < max + haveEndMarker = true + # found! + break + # If a fence has heading spaces, they should be removed from its inner block + len = state.sCount[startLine] + state.line = nextLine + if haveEndMarker then 1 else 0 + token = state.push 'fence', 'code', 0 + token.info = params + token.content = state.getLines startLine + 1, nextLine, len, true + token.markup = markup + token.map = [ startLine, state.line ] + return true # Renderer # ------------------------------------------- -renderer = - mermaid: (tokens, idx, options, env, self) -> - return "htmlResult" - -return - - - - - -module.exports = (md, name, options) -> - - options = options or {} - min_markers = 3 - marker_str = options.marker or ':' - marker_char = marker_str.charCodeAt(0) - marker_len = marker_str.length - validate = options.validate or validateDefault - render = options.render or renderDefault - md.block.ruler.before 'fence', 'container_' + name, container, - alt: [ - 'paragraph' - 'reference' - 'blockquote' - 'list' - ] - md.renderer.rules['container_' + name + '_open'] = render - md.renderer.rules['container_' + name + '_close'] = render - return - - validateDefault = (params) -> - params.trim().split(' ', 2)[0] is name - - renderDefault = (tokens, idx, _options, env, self) -> - # add a class to the opening tag - if tokens[idx].nesting is 1 - tokens[idx].attrPush [ - 'class' - name - ] - self.renderToken tokens, idx, _options, env, self - - container = (state, startLine, endLine, silent) -> - pos = undefined - nextLine = undefined - marker_count = undefined - markup = undefined - params = undefined - token = undefined - old_parent = undefined - old_line_max = undefined - auto_closed = false - start = state.bMarks[startLine] + state.tShift[startLine] - max = state.eMarks[startLine] - # Check out the first character quickly, - # this should filter out most of non-containers - # - unless marker_char is state.src.charCodeAt(start) - return false - # Check out the rest of the marker string - # - pos = start + 1 - while pos <= max - if marker_str[(pos - start) % marker_len] isnt state.src[pos] - break - pos++ - marker_count = Math.floor((pos - start) / marker_len) - if marker_count < min_markers - return false - pos -= (pos - start) % marker_len - markup = state.src.slice(start, pos) - params = state.src.slice(pos, max) - if not validate(params) - return false - # Since start is found, we can report success here in validation mode - # - if silent - return true - # Search for the end of the block - # - nextLine = startLine - loop - nextLine++ - if nextLine >= endLine - # unclosed block should be autoclosed by end of document. - # also block seems to be autoclosed by end of parent - break - start = state.bMarks[nextLine] + state.tShift[nextLine] - max = state.eMarks[nextLine] - if start < max and state.sCount[nextLine] < state.blkIndent - # non-empty line with negative indent should stop the list: - # - ``` - # test - break - if marker_char isnt state.src.charCodeAt(start) - continue - if state.sCount[nextLine] - (state.blkIndent) >= 4 - # closing fence should be indented less than 4 spaces - continue - pos = start + 1 - while pos <= max - if marker_str[(pos - start) % marker_len] isnt state.src[pos] - break - pos++ - # closing code fence must be at least as long as the opening one - if Math.floor((pos - start) / marker_len) < marker_count - pos++ - continue - # make sure tail has spaces only - pos -= (pos - start) % marker_len - pos = state.skipSpaces(pos) - if pos < max - pos++ - continue - # found! - auto_closed = true - break - old_parent = state.parentType - old_line_max = state.lineMax - state.parentType = 'container' - # this will prevent lazy continuations from ever going past our end marker - state.lineMax = nextLine - token = state.push('container_' + name + '_open', 'div', 1) - token.markup = markup - token.block = true - token.info = params - token.map = [ - startLine - nextLine - ] - state.md.block.tokenize state, startLine + 1, nextLine - token = state.push('container_' + name + '_close', 'div', -1) - token.markup = state.src.slice(start, pos) - token.block = true - state.parentType = old_parent - state.lineMax = old_line_max - state.line = nextLine + (if auto_closed then 1 else 0) - true +renderer = (tokens, idx, options, env, self) -> + token = tokens[idx] + if token.info + [type] = token.info.split /\s+/g + switch type + when 'progress' + '
PROGRESS
\n' + when 'mermaid' + '
MERMAID
\n' + when 'chart' + '
CHART
\n' + else + escapeHtml token.content + + +# Helper +# ------------------------------------------- +escapeHtmlChars = + '&': '&' + '<': '<' + '>': '>' + '"': '"' + +escapeHtml = (str) -> + str.replace /[&<>"]/g, (e) -> escapeHtmlChars[e] diff --git a/test/mocha/plugin.coffee b/test/mocha/plugin.coffee new file mode 100644 index 0000000..ec9319b --- /dev/null +++ b/test/mocha/plugin.coffee @@ -0,0 +1,23 @@ +chai = require 'chai' +expect = chai.expect +### eslint-env node, mocha ### + +markdownit = require 'markdown-it' +plugin = require '../../src/plugin' + +describe "graph", -> + + describe.only "base", -> + + it "should work without plugin", -> + md = markdownit().use plugin + result = md.render "# Heading" + expect(result).to.equal('

Heading

\n') + + it "should work using extra renderer call", -> + md = markdownit().use plugin + env = {} + tokens = md.parse "# Heading", env + console.log tokens + result = md.renderer.render tokens, md.options, env + expect(result).to.equal('

Heading

\n')