Skip to content

Commit

Permalink
Merge pull request #37 from apiaryio/kubula/add_commonmark
Browse files Browse the repository at this point in the history
Support 2 markdown renderers = Robotskirt & markdown-it
  • Loading branch information
freaz authored Aug 9, 2016
2 parents dddf5dd + 18dc285 commit f5edcab
Show file tree
Hide file tree
Showing 15 changed files with 555 additions and 144 deletions.
18 changes: 7 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
"precompile": "npm run lint",
"compile": "coffee -b -c -o lib/ src/",
"server-test": "mocha --compilers coffee:coffee-script/register -R spec --recursive --timeout 5000",
"browser-test": "echo 'Skipping `karma start` because robotskirt does not run in browser - we can turn on browser tests only when we migrate to something else...'",
"test": "npm run lint && npm run server-test && npm run browser-test",
"test": "npm run lint && npm run server-test",
"prepublish": "npm run compile"
},
"engines": {
"node": "",
"npm": ""
},
"repository": {
"type": "git",
"url": "git+ssh://[email protected]:apiaryio/metamorphoses.git"
Expand All @@ -24,11 +27,10 @@
},
"homepage": "https://github.com/apiaryio/metamorphoses",
"dependencies": {
"blueprint-markdown-renderer": "^0.2.3",
"lodash": "^3.10.1",
"lodash-api-description": "0.0.2",
"media-typer": "^0.3.0",
"robotskirt": "^2.7.1",
"sanitizer": "^0.1.2"
"media-typer": "^0.3.0"
},
"devDependencies": {
"@apiaryio/swagger-zoo": "1.0.0",
Expand All @@ -38,12 +40,6 @@
"coffeeify": "^1.1.0",
"coffeelint": "^1.11.1",
"drafter": "^0.2.8",
"karma": "^0.13.9",
"karma-browserify": "^4.3.0",
"karma-chai": "^0.1.0",
"karma-firefox-launcher": "^0.1.6",
"karma-mocha": "^0.2.0",
"karma-mocha-reporter": "^1.1.1",
"mocha": "^2.3.0",
"protagonist": "1.3.2",
"sinon": "^1.17.2"
Expand Down
40 changes: 20 additions & 20 deletions src/adapters/api-blueprint-adapter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -132,29 +132,29 @@ getAttributesElements = (elementContent) ->
elements


legacyRequestsFrom1AExamples = (action, resource) ->
legacyRequestsFrom1AExamples = (action, resource, options) ->
requests = []
for example, exampleIndex in action.examples or []
for req in example.requests or []
requests.push(legacyRequestFrom1ARequest(req, action, resource, exampleId = exampleIndex))
requests.push(legacyRequestFrom1ARequest(req, action, resource, exampleId = exampleIndex, options))

if requests.length < 1
return [legacyRequestFrom1ARequest({}, action, resource, exampleId = 0)]
return [legacyRequestFrom1ARequest({}, action, resource, exampleId = 0, options)]
requests


# ## `legacyRequestFrom1ARequest`
#
# Transform 1A Format Request into 'legacy request'
legacyRequestFrom1ARequest = (request, action, resource, exampleId = undefined) ->
legacyRequestFrom1ARequest = (request, action, resource, exampleId = undefined, options) ->
legacyRequest = new blueprintApi.Request(
headers: legacyHeadersCombinedFrom1A(request, action, resource)
exampleId: exampleId
)

if request.description
legacyRequest.description = trimLastNewline(request.description)
legacyRequest.htmlDescription = trimLastNewline(markdown.toHtmlSync(request.description))
legacyRequest.htmlDescription = trimLastNewline(markdown.toHtmlSync(request.description, options))
else
legacyRequest.description = ''
legacyRequest.htmlDescription = ''
Expand Down Expand Up @@ -185,15 +185,15 @@ legacyResponsesFrom1AExamples = (action, resource) ->
# ## `legacyResponseFrom1AResponse`
#
# Transform 1A Format Response into 'legacy response'
legacyResponseFrom1AResponse = (response, action, resource, exampleId = undefined) ->
legacyResponseFrom1AResponse = (response, action, resource, exampleId = undefined, options) ->
legacyResponse = new blueprintApi.Response(
headers: legacyHeadersCombinedFrom1A(response, action, resource)
exampleId: exampleId
)

if response.description
legacyResponse.description = trimLastNewline(response.description)
legacyResponse.htmlDescription = trimLastNewline(markdown.toHtmlSync(response.description))
legacyResponse.htmlDescription = trimLastNewline(markdown.toHtmlSync(response.description, options))
else
legacyResponse.description = ''
legacyResponse.htmlDescription = ''
Expand All @@ -219,7 +219,7 @@ legacyResponseFrom1AResponse = (response, action, resource, exampleId = undefine
# ## `getParametersOf`
#
# Produces an array of URI parameters.
getParametersOf = (obj) ->
getParametersOf = (obj, options) ->
if not obj
return undefined

Expand All @@ -229,7 +229,7 @@ getParametersOf = (obj) ->
for own key, param of paramsObj
param.key = key
if param.description
param.description = markdown.toHtmlSync(param.description)
param.description = markdown.toHtmlSync(param.description, options)
param.values = ((if typeof item is 'string' then item else item.value) for item in param.values)
params.push(param)

Expand All @@ -243,11 +243,11 @@ getParametersOf = (obj) ->
#
# Transform 1A Format Resource into 'legacy resources', squashing action and resource
# NOTE: One 1A Resource might split into more legacy resources (actions[].transactions[].resource)
legacyResourcesFrom1AResource = (legacyUrlConverterFn, resource, sourcemap) ->
legacyResourcesFrom1AResource = (legacyUrlConverterFn, resource, sourcemap, options) ->
legacyResources = []

# resource-wide parameters
resourceParameters = getParametersOf(resource)
resourceParameters = getParametersOf(resource, options)

for action, actionIndex in resource.actions or []
# Combine resource & action section, preferring action
Expand All @@ -273,7 +273,7 @@ legacyResourcesFrom1AResource = (legacyUrlConverterFn, resource, sourcemap) ->
legacyResource.description = trimLastNewline(resource.description)

if resource.description?.length
legacyResource.htmlDescription = trimLastNewline(markdown.toHtmlSync(resource.description.trim()))
legacyResource.htmlDescription = trimLastNewline(markdown.toHtmlSync(resource.description.trim(), options))
else
legacyResource.htmlDescription = ''

Expand All @@ -288,7 +288,7 @@ legacyResourcesFrom1AResource = (legacyUrlConverterFn, resource, sourcemap) ->
legacyResource.model = resource.model

if resource.model.description and resource.model.description.length
legacyResource.model.description = markdown.toHtmlSync(resource.model.description)
legacyResource.model.description = markdown.toHtmlSync(resource.model.description, options)

if resource.model.headers and resource.model.headers.length
legacyResource.model.headers = legacyHeadersFrom1AHeaders(resource.model.headers)
Expand All @@ -297,13 +297,13 @@ legacyResourcesFrom1AResource = (legacyUrlConverterFn, resource, sourcemap) ->
legacyResource.model = {}

legacyResource.resourceParameters = resourceParameters
legacyResource.actionParameters = getParametersOf(action)
legacyResource.actionParameters = getParametersOf(action, options)

legacyResource.parameters = legacyResource.actionParameters or resourceParameters or undefined

if action.description
legacyResource.actionDescription = trimLastNewline(action.description)
legacyResource.actionHtmlDescription = trimLastNewline(markdown.toHtmlSync(action.description))
legacyResource.actionHtmlDescription = trimLastNewline(markdown.toHtmlSync(action.description, options))
else
legacyResource.actionDescription = ''
legacyResource.actionHtmlDescription = ''
Expand All @@ -314,7 +314,7 @@ legacyResourcesFrom1AResource = (legacyUrlConverterFn, resource, sourcemap) ->
legacyResource.request = requests[0]

# Responses
legacyResource.responses = legacyResponsesFrom1AExamples(action, resource)
legacyResource.responses = legacyResponsesFrom1AExamples(action, resource, options)

# Resource Attributes
attributesElements = getAttributesElements(resource.content)
Expand All @@ -338,7 +338,7 @@ legacyResourcesFrom1AResource = (legacyUrlConverterFn, resource, sourcemap) ->
#
# This method will hopefully be superseeded by transformOldAstToProtagonist
# once we'll be comfortable with new format and it'll be our default.
legacyASTfrom1AAST = (ast, sourcemap) ->
legacyASTfrom1AAST = (ast, sourcemap, options) ->
return null unless ast

# Using current Application AST version only for API Blueprint ASTs
Expand All @@ -359,7 +359,7 @@ legacyASTfrom1AAST = (ast, sourcemap) ->
})

legacyAST.description = "#{ast.description}".trim() or ''
legacyAST.htmlDescription = trimLastNewline(markdown.toHtmlSync(ast.description))
legacyAST.htmlDescription = trimLastNewline(markdown.toHtmlSync(ast.description, options))

# Metadata
metadata = []
Expand Down Expand Up @@ -403,15 +403,15 @@ legacyASTfrom1AAST = (ast, sourcemap) ->

if resourceGroupDescription
legacySection.description = trimLastNewline(resourceGroupDescription)
legacySection.htmlDescription = trimLastNewline(markdown.toHtmlSync(resourceGroupDescription))
legacySection.htmlDescription = trimLastNewline(markdown.toHtmlSync(resourceGroupDescription, options))
else
legacySection.description = ''
legacySection.htmlDescription = ''

# Resources
for resource, j in resourceGroup.resources
resources = legacyResourcesFrom1AResource(legacyUrlConverter, resource,
sourcemap?.resourceGroups?[i]?.resources?[j])
sourcemap?.resourceGroups?[i]?.resources?[j], options)
legacySection.resources = legacySection.resources.concat(resources)

legacyAST.sections.push(legacySection)
Expand Down
12 changes: 6 additions & 6 deletions src/adapters/apiary-blueprint-adapter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ blueprintApi = require('../blueprint-api')
markdown = require('./markdown')


applymarkdownHtml = (obj, targetHtmlProperty) ->
obj[targetHtmlProperty] = markdown.toHtmlSync(obj.description or '').trim()
applymarkdownHtml = (obj, targetHtmlProperty, options) ->
obj[targetHtmlProperty] = markdown.toHtmlSync(obj.description or '', options).trim()
obj


Expand All @@ -14,17 +14,17 @@ applymarkdownHtml = (obj, targetHtmlProperty) ->
#
# Go through the AST object and render
# markdown descriptions.
apiaryAstToApplicationAst = (ast) ->
apiaryAstToApplicationAst = (ast, sourcemap, options) ->
return null unless ast
plainJsObject = applymarkdownHtml(ast, 'htmlDescription')
plainJsObject = applymarkdownHtml(ast, 'htmlDescription', options)

for section, sectionKey in plainJsObject.sections or [] when section.resources?.length
for resource, resourceKey in section.resources
section.resources[resourceKey].uriTemplate = resolveUriTemplate(section.resources[resourceKey], plainJsObject.location)
section.resources[resourceKey] = applymarkdownHtml(resource, 'htmlDescription')
section.resources[resourceKey] = applymarkdownHtml(resource, 'htmlDescription', options)
section.resources[resourceKey].requests = [section.resources[resourceKey].request]

plainJsObject.sections[sectionKey] = applymarkdownHtml(section, 'htmlDescription')
plainJsObject.sections[sectionKey] = applymarkdownHtml(section, 'htmlDescription', options)

plainJsObject.version = blueprintApi.Version
return blueprintApi.Blueprint.fromJSON(plainJsObject)
Expand Down
114 changes: 34 additions & 80 deletions src/adapters/markdown.coffee
Original file line number Diff line number Diff line change
@@ -1,105 +1,59 @@
# This is our Markdown parser implementation
# Uses Robotskirt, which is a node binding for a C markdown parser Sundown (also used by Github)
rs = require('robotskirt')
sanitizer = require('sanitizer')

renderer = new rs.HtmlRenderer()
{renderHtml, renderRobotskirtHtml} = require('blueprint-markdown-renderer')

flags = [
# ### Autolink
#
# Parse links even when they are not enclosed in
# `<>` characters. Autolinks for the http, https and ftp
# protocols will be automatically detected. Email addresses
# are also handled, and http links without protocol, but
# starting with `www.`
rs.EXT_AUTOLINK

# ### Fenced Code
#
# Parse fenced code blocks, PHP-Markdown
# style. Blocks delimited with 3 or more `~` or backticks
# will be considered as code, without the need to be
# indented. An optional language name may be added at the
# end of the opening fence for the code block.
rs.EXT_FENCED_CODE

# ### Lax Spacing
#
# HTML blocks do not require to be surrounded
# by an empty line as in the Markdown standard.
rs.EXT_LAX_HTML_BLOCKS

# ### Tables
#
# Parse tables, PHP-Markdown style.
rs.EXT_TABLES

# ### No Intra Emphasis
#
# Do not parse emphasis inside of words.
# Strings such as `foo_bar_baz` will not generate `<em>`
# tags.
rs.EXT_NO_INTRA_EMPHASIS

# ### Strikethrough
#
# Parse strikethrough, PHP-Markdown style
# Two `~` characters mark the start of a strikethrough,
# e.g. `this is ~~good~~ bad`.
rs.EXT_STRIKETHROUGH

# ### Superscript
#
# Parse superscripts after the `^` character;
# contiguous superscripts are nested together, and complex
# values can be enclosed in parenthesis,
# e.g. `this is the 2^(nd) time`.
rs.EXT_SUPERSCRIPT
]

parser = new rs.Markdown(renderer, flags)
parserSync = new rs.Markdown(renderer, flags)

# By default, sanitizer removes src and href attributes
# if a url policy is not given.
uriPolicy = (value) -> value


parseMarkdown = (markdown, options = {}) ->
unless markdown
return ''
parseMarkdown = (markdown, params, cb) ->
# do not mutate passed-in argument "params", create our own options
options = {
sanitize: params?.sanitize
commonMark: params?.commonMark
}

# sanitize is enabled by default
options.sanitize ?= true
parsed = parserSync.render(markdown)

if options.sanitize
results = sanitizer.sanitize(parsed, uriPolicy)
if options.commonMark
results = renderHtml(markdown, options)
else
results = parsed
results = renderRobotskirtHtml(markdown, options)

# Return <span> if the results are empty. This way other code
# that renders knows this code has been parsed.
return results unless results.trim() is ''
return '<span></span>'
if results.trim() is ''
results = '<span></span>'

if cb
return cb(null, results)
else
return results


toHtml = (markdown, options = {}, cb) ->
toHtml = (markdown, params, cb) ->
options = {}
# Allow for second arg to be the callback
if typeof options is 'function'
[cb, options] = [options, {}]
if typeof params is 'function'
cb = params
else
# do not mutate passed-in argument "params", create our own options
options = {
sanitize: params?.sanitize
commonMark: params?.commonMark
}

unless cb
return parseMarkdown(markdown, options)

unless markdown
return cb(null, '')

cb(null, parseMarkdown(markdown, options))
parseMarkdown(markdown, options, cb)
return


toHtmlSync = (markdown, options = {}) ->
parseMarkdown(markdown, options)
toHtmlSync = (markdown, params) ->
if not markdown
return ''
return parseMarkdown(markdown, params)

module.exports = {
toHtml
Expand Down
Loading

0 comments on commit f5edcab

Please sign in to comment.