Skip to content

Commit

Permalink
Basic test for running markdown with separate parse and render calls.
Browse files Browse the repository at this point in the history
  • Loading branch information
alinex committed Jun 8, 2016
1 parent d36a38b commit 105af69
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 200 deletions.
10 changes: 5 additions & 5 deletions src/html.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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'
Expand Down Expand Up @@ -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
Expand Down
273 changes: 78 additions & 195 deletions src/plugin.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -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'
'<pre><b>PROGRESS</b></pre>\n'
when 'mermaid'
'<pre><b>MERMAID</b></pre>\n'
when 'chart'
'<pre><b>CHART</b></pre>\n'
else
escapeHtml token.content


# Helper
# -------------------------------------------
escapeHtmlChars =
'&': '&amp;'
'<': '&lt;'
'>': '&gt;'
'"': '&quot;'

escapeHtml = (str) ->
str.replace /[&<>"]/g, (e) -> escapeHtmlChars[e]
23 changes: 23 additions & 0 deletions test/mocha/plugin.coffee
Original file line number Diff line number Diff line change
@@ -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('<h1>Heading</h1>\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('<h1>Heading</h1>\n')

0 comments on commit 105af69

Please sign in to comment.