From 6e34c9f92dfef8a68aead1bd8c6058131e7a6223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kor=C3=A1l?= Date: Mon, 8 Aug 2016 14:39:41 +0200 Subject: [PATCH 1/4] feat(CommonMark): Support both markdown renderers = robotskirt / markdown-it (CommonMark-like) --- package.json | 18 +- src/adapters/markdown.coffee | 97 ++------ test/markdown-commonmark-test.coffee | 342 +++++++++++++++++++++++++++ test/markdown-test.coffee | 2 +- 4 files changed, 369 insertions(+), 90 deletions(-) create mode 100644 test/markdown-commonmark-test.coffee diff --git a/package.json b/package.json index 170ce642..be70685c 100644 --- a/package.json +++ b/package.json @@ -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://git@github.com:apiaryio/metamorphoses.git" @@ -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", @@ -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" diff --git a/src/adapters/markdown.coffee b/src/adapters/markdown.coffee index c4264f79..4bc93c33 100644 --- a/src/adapters/markdown.coffee +++ b/src/adapters/markdown.coffee @@ -1,93 +1,31 @@ # 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() - -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 `` - # 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 '' +{renderHtml, renderRobotskirtHtml} = require('blueprint-markdown-renderer') +parseMarkdown = (markdown, options = {}, cb) -> 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 if the results are empty. This way other code # that renders knows this code has been parsed. - return results unless results.trim() is '' - return '' + if results.trim() is '' + results = '' + + if cb + return cb(null, results) + else + return results toHtml = (markdown, options = {}, cb) -> # Allow for second arg to be the callback if typeof options is 'function' - [cb, options] = [options, {}] + cb = options + options = {} unless cb return parseMarkdown(markdown, options) @@ -95,11 +33,14 @@ toHtml = (markdown, options = {}, cb) -> unless markdown return cb(null, '') - cb(null, parseMarkdown(markdown, options)) + parseMarkdown(markdown, options, cb) + return toHtmlSync = (markdown, options = {}) -> - parseMarkdown(markdown, options) + if not markdown + return '' + return parseMarkdown(markdown, options) module.exports = { toHtml diff --git a/test/markdown-commonmark-test.coffee b/test/markdown-commonmark-test.coffee new file mode 100644 index 00000000..cd118ca3 --- /dev/null +++ b/test/markdown-commonmark-test.coffee @@ -0,0 +1,342 @@ +{assert} = require('chai') +markdown = require('../src/adapters/markdown') + +describe('Markdown rendered with CommonMark', -> + describe('#toHtml', -> + it('Parse a plain paragraph', (done) -> + markdownString = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' + expectedHtml = '

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

\n' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse a bullet list (stars used as bullets)', (done) -> + markdownString = ''' + * Red + * Green + * Orange + * Blue + ''' + + expectedHtml = ''' +
    +
  • Red
  • +
  • Green
  • +
  • Orange
  • +
  • Blue
  • +
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse a bullet list (dashes used as bullets)', (done) -> + markdownString = ''' + - Red + - Green + - Orange + - Blue + ''' + + expectedHtml = ''' +
    +
  • Red
  • +
  • Green
  • +
  • Orange
  • +
  • Blue
  • +
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse an ordered list', (done) -> + markdownString = ''' + 1. Red + 2. Green + 3. Orange + 4. Blue + ''' + + expectedHtml = ''' +
    +
  1. Red
  2. +
  3. Green
  4. +
  5. Orange
  6. +
  7. Blue
  8. +
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse nested lists', (done) -> + markdownString = ''' +* Lorem +* Ipsum + * Dolor + * Ismaet + ''' + + expectedHtml = ''' +
    +
  • Lorem
  • +
  • Ipsum +
      +
    • Dolor
    • +
    • Ismaet
    • +
    +
  • +
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse headers', (done) -> + markdownString = ''' + # Level 1 + ## Level 2 + ### Level 3 + #### Level 4 + ##### Level 5 + ###### Level 6 + ''' + + expectedHtml = ''' +

Level 1

+

Level 2

+

Level 3

+

Level 4

+
Level 5
+
Level 6
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse a code block', (done) -> + markdownString = ''' +Lorem ipsum dolor isamet. + + alert('Hello!'); + ''' + + expectedHtml = ''' +

Lorem ipsum dolor isamet.

+
alert('Hello!');
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse a fenced code block', (done) -> + markdownString = ''' + ``` + alert('Hello!'); + ``` + ''' + + expectedHtml = ''' +
alert('Hello!');
+      
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse a Markdown table', (done) -> + markdownString = ''' + | First Header | Second Header | Third Header | + | :------------ | :-----------: | -------------------: | + | First row | Data | Very long data entry | + | Second row | **Cell** | *Cell* | + | Third row | Cell that spans across two columns || + ''' + + expectedHtml = ''' + + + + + + + + + + + + + + + + + + + + + + + + + +
First HeaderSecond HeaderThird Header
First rowDataVery long data entry
Second rowCellCell
Third rowCell that spans across two columns
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + describe('when sanitize is true', -> + it('Parse out script tags', (done) -> + markdownString = ''' +
+ ''' + + expectedHtml = ''' +
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse out custom tags and preserve contents', (done) -> + markdownString = ''' +

HTML tag

+ ''' + + expectedHtml = ''' +

HTML tag

+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse out custom attributes', (done) -> + markdownString = ''' +

HTML tag

+ ''' + + expectedHtml = ''' +

HTML tag

+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse preseves code block tags', (done) -> + markdownString = ''' + ```xml + Hello World + ``` + ''' + + expectedHtml = ''' +
<xml>Hello World</xml>\n
+ + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse and sanitize images', (done) -> + markdownString = ''' + + ''' + + expectedHtml = ''' + + + ''' + + markdown.toHtml(markdownString, {commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + ) + + describe('when sanitizing is false', -> + it('Parse and leave script tags', (done) -> + markdownString = ''' +
+ ''' + + expectedHtml = ''' +
+ + ''' + + markdown.toHtml(markdownString, {sanitize: false, commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + + it('Parse and leave custom tags and preserve contents', (done) -> + markdownString = ''' +

HTML tag

+ ''' + + expectedHtml = ''' +

HTML tag

+ + ''' + + markdown.toHtml(markdownString, {sanitize: false, commonMark: true}, (error, html) -> + assert.strictEqual(html, expectedHtml) + done(error) + ) + ) + ) + ) +) diff --git a/test/markdown-test.coffee b/test/markdown-test.coffee index ac5a7c79..3d8f4ab7 100644 --- a/test/markdown-test.coffee +++ b/test/markdown-test.coffee @@ -1,7 +1,7 @@ {assert} = require('chai') markdown = require('../src/adapters/markdown') -describe('Markdown', -> +describe('Markdown rendered with Robotskirt', -> describe('#toHtml', -> it('Parse a plain paragraph', (done) -> markdownString = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.' From 5afe02175032f0970e03a5e5372de4b8d1e441b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kor=C3=A1l?= Date: Mon, 8 Aug 2016 15:42:52 +0200 Subject: [PATCH 2/4] chore(adapters): Implement passing of "options" to markdown renderer in all adapters - feat(apiary-blueprint-adapter) Add support to pass options to markdown renderer - feat(API-Blueprint-adapter) Add support to pass options to markdown renderer - feat(Refract-adapter) Add support to pass options to markdown renderer --- src/adapters/api-blueprint-adapter.coffee | 40 +++++++++---------- src/adapters/apiary-blueprint-adapter.coffee | 12 +++--- src/adapters/refract-adapter.coffee | 8 ++-- src/adapters/refract/getDescription.coffee | 4 +- .../refract/getMetaDescription.coffee | 4 +- src/adapters/refract/getUriParameters.coffee | 4 +- src/adapters/refract/transformAuth.coffee | 2 +- src/adapters/refract/transformResource.coffee | 18 ++++----- .../refract/transformResources.coffee | 4 +- src/adapters/refract/transformSections.coffee | 8 ++-- 10 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/adapters/api-blueprint-adapter.coffee b/src/adapters/api-blueprint-adapter.coffee index 3cb322de..5a21baac 100644 --- a/src/adapters/api-blueprint-adapter.coffee +++ b/src/adapters/api-blueprint-adapter.coffee @@ -132,21 +132,21 @@ 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 @@ -154,7 +154,7 @@ legacyRequestFrom1ARequest = (request, action, resource, exampleId = undefined) 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 = '' @@ -185,7 +185,7 @@ 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 @@ -193,7 +193,7 @@ legacyResponseFrom1AResponse = (response, action, resource, exampleId = undefine 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 = '' @@ -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 @@ -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) @@ -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 @@ -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 = '' @@ -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) @@ -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 = '' @@ -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) @@ -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 @@ -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 = [] @@ -403,7 +403,7 @@ 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 = '' @@ -411,7 +411,7 @@ legacyASTfrom1AAST = (ast, sourcemap) -> # 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) diff --git a/src/adapters/apiary-blueprint-adapter.coffee b/src/adapters/apiary-blueprint-adapter.coffee index b02af87c..e8577a85 100644 --- a/src/adapters/apiary-blueprint-adapter.coffee +++ b/src/adapters/apiary-blueprint-adapter.coffee @@ -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 @@ -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) diff --git a/src/adapters/refract-adapter.coffee b/src/adapters/refract-adapter.coffee index c074bb89..e6d7127a 100644 --- a/src/adapters/refract-adapter.coffee +++ b/src/adapters/refract-adapter.coffee @@ -5,7 +5,7 @@ getDescription = require('./refract/getDescription') transformAuth = require('./refract/transformAuth') transformSections = require('./refract/transformSections') -transformAst = (element) -> +transformAst = (element, sourcemap, options) -> applicationAst = new blueprintApi.Blueprint({ name: _.chain(element).get('meta.title', '').contentOrValue().fixNewLines().value() @@ -30,16 +30,16 @@ transformAst = (element) -> ).value() # description - description = getDescription(element) + description = getDescription(element, options) applicationAst.description = description.raw applicationAst.htmlDescription = description.html # Authentication definitions - applicationAst.authDefinitions = transformAuth(element) + applicationAst.authDefinitions = transformAuth(element, options) # Sections - applicationAst.sections = transformSections(element) + applicationAst.sections = transformSections(element, options) applicationAst diff --git a/src/adapters/refract/getDescription.coffee b/src/adapters/refract/getDescription.coffee index b0bfae1b..dd0482ba 100644 --- a/src/adapters/refract/getDescription.coffee +++ b/src/adapters/refract/getDescription.coffee @@ -1,10 +1,10 @@ _ = require('./helper') markdown = require('../markdown') -module.exports = (element) -> +module.exports = (element, options) -> copyElement = _(element).copy().first() raw = _.fixNewLines(_.content(copyElement) or '') - html = _.fixNewLines(if raw then markdown.toHtmlSync(raw) else '') + html = _.fixNewLines(if raw then markdown.toHtmlSync(raw, options) else '') return {raw, html} diff --git a/src/adapters/refract/getMetaDescription.coffee b/src/adapters/refract/getMetaDescription.coffee index 7e721d4f..2a5d9794 100644 --- a/src/adapters/refract/getMetaDescription.coffee +++ b/src/adapters/refract/getMetaDescription.coffee @@ -1,7 +1,7 @@ lodash = require('./helper') markdown = require('../markdown') -module.exports = (element) -> +module.exports = (element, options) -> rawDescription = lodash .chain(element) .get('meta.description', '') @@ -9,4 +9,4 @@ module.exports = (element) -> .fixNewLines() .value() - markdown.toHtmlSync(rawDescription) + markdown.toHtmlSync(rawDescription, options) diff --git a/src/adapters/refract/getUriParameters.coffee b/src/adapters/refract/getUriParameters.coffee index cde3d45f..bc23f2eb 100644 --- a/src/adapters/refract/getUriParameters.coffee +++ b/src/adapters/refract/getUriParameters.coffee @@ -1,7 +1,7 @@ lodash = require('./helper') getMetaDescription = require('./getMetaDescription') -getUriParameters = (hrefVariables) -> +getUriParameters = (hrefVariables, options) -> hrefVariablesContent = lodash.content(hrefVariables) if hrefVariablesContent is undefined @@ -41,7 +41,7 @@ getUriParameters = (hrefVariables) -> default: defaultValue required type - description: getMetaDescription(hrefVariable) + description: getMetaDescription(hrefVariable, options) } ) diff --git a/src/adapters/refract/transformAuth.coffee b/src/adapters/refract/transformAuth.coffee index 5a4bb084..bc6b52aa 100644 --- a/src/adapters/refract/transformAuth.coffee +++ b/src/adapters/refract/transformAuth.coffee @@ -2,7 +2,7 @@ _ = require('./helper') getDescription = require('./getDescription') -module.exports = (parentElement) -> +module.exports = (parentElement, options) -> # Auth information can be present in two places: # 1. An `authSchemes` category that contains definitions # 2. An `authSchems` attribute that defines which definition to use diff --git a/src/adapters/refract/transformResource.coffee b/src/adapters/refract/transformResource.coffee index b88da40b..228eaa68 100644 --- a/src/adapters/refract/transformResource.coffee +++ b/src/adapters/refract/transformResource.coffee @@ -5,10 +5,10 @@ getHeaders = require('./getHeaders') getUriParameters = require('./getUriParameters') transformAuth = require('./transformAuth') -module.exports = (resourceElement) -> +module.exports = (resourceElement, options) -> resources = [] - resourceDescription = getDescription(resourceElement) + resourceDescription = getDescription(resourceElement, options) transitions = _.transitions(resourceElement) @@ -33,10 +33,10 @@ module.exports = (resourceElement) -> ] transitions.forEach((transitionElement) -> - description = getDescription(transitionElement) + description = getDescription(transitionElement, options) - resourceParameters = getUriParameters(_.get(resourceElement, 'attributes.hrefVariables')) - actionParameters = getUriParameters(_.get(transitionElement, 'attributes.hrefVariables')) + resourceParameters = getUriParameters(_.get(resourceElement, 'attributes.hrefVariables'), options) + actionParameters = getUriParameters(_.get(transitionElement, 'attributes.hrefVariables'), options) attributes = _.dataStructures(transitionElement) attributes = if _.isEmpty(attributes) then _.dataStructures(resourceElement) else attributes[0] @@ -80,7 +80,7 @@ module.exports = (resourceElement) -> httpRequest = _(httpTransaction).httpRequests().first() httpRequestBody = _(httpRequest).messageBodies().first() httpRequestBodySchemas = _(httpRequest).messageBodySchemas().first() - httpRequestDescription = getDescription(httpRequest) + httpRequestDescription = getDescription(httpRequest, options) httpRequestBodyDataStructures = _.dataStructures(httpRequest) if _.isEmpty(httpRequestBodyDataStructures) @@ -91,7 +91,7 @@ module.exports = (resourceElement) -> httpResponse = _(httpTransaction).httpResponses().first() httpResponseBody = _(httpResponse).messageBodies().first() httpResponseBodySchemas = _(httpResponse).messageBodySchemas().first() - httpResponseDescription = getDescription(httpResponse) + httpResponseDescription = getDescription(httpResponse, options) httpResponseBodyDataStructures = _.dataStructures(httpResponse) if _.isEmpty(httpResponseBodyDataStructures) @@ -103,7 +103,7 @@ module.exports = (resourceElement) -> resource.method = _.chain(httpRequest).get('attributes.method', '').contentOrValue().value() resource.actionUriTemplate = _.chain(httpRequest).get('attributes.href', '').contentOrValue().value() - requestParameters = getUriParameters(_.get(httpRequest, 'attributes.hrefVariables')) + requestParameters = getUriParameters(_.get(httpRequest, 'attributes.hrefVariables'), options) actionParameters = actionParameters.concat(requestParameters) httpRequestIsRedundant = _.every(httpTransactions, (httpTransaction) -> @@ -123,7 +123,7 @@ module.exports = (resourceElement) -> # exampleId attributes: requestAttributes resolvedAttributes: requestAttributes - authSchemes: transformAuth(httpTransaction) + authSchemes: transformAuth(httpTransaction, options) }) requests.push(request) diff --git a/src/adapters/refract/transformResources.coffee b/src/adapters/refract/transformResources.coffee index f7522a81..89897a4e 100644 --- a/src/adapters/refract/transformResources.coffee +++ b/src/adapters/refract/transformResources.coffee @@ -2,11 +2,11 @@ _ = require('./helper') blueprintApi = require('../../blueprint-api') transformResource = require('./transformResource') -module.exports = (element) -> +module.exports = (element, options) -> resources = [] _.resources(element).forEach((resourceElement) -> - resources = resources.concat(transformResource(resourceElement)) + resources = resources.concat(transformResource(resourceElement, options)) ) resources diff --git a/src/adapters/refract/transformSections.coffee b/src/adapters/refract/transformSections.coffee index c03f9718..ed32cf34 100644 --- a/src/adapters/refract/transformSections.coffee +++ b/src/adapters/refract/transformSections.coffee @@ -7,7 +7,7 @@ transformResources = require('./transformResources') transformResource = require('./transformResource') -module.exports = (parentElement) -> +module.exports = (parentElement, options) -> resourceGroups = [] # List of Application AST Resources (= already transformed @@ -22,7 +22,7 @@ module.exports = (parentElement) -> # an artificial section. if element.element is 'resource' resourcesWithoutGroup = resourcesWithoutGroup.concat( - transformResource(element) + transformResource(element, options) ) if element.element is 'category' @@ -37,7 +37,7 @@ module.exports = (parentElement) -> # Resources have been added to a group, reset. resourcesWithoutGroup = [] - description = getDescription(element) + description = getDescription(element, options) classes = _.get(element, 'meta.classes', []) if classes.length is 0 or classes.indexOf('resourceGroup') isnt -1 @@ -47,7 +47,7 @@ module.exports = (parentElement) -> name: _.chain(element).get('meta.title', '').contentOrValue().value() description: description.raw htmlDescription: description.html - resources: transformResources(element) + resources: transformResources(element, options) }) resourceGroups.push(resourceGroup) From 7100f8e3915af4c008e018c27e8ef8bc7f4330cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kor=C3=A1l?= Date: Tue, 9 Aug 2016 15:13:01 +0200 Subject: [PATCH 3/4] test(adapters): add test of passing options to markdown renderers --- test/options-test.coffee | 119 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 test/options-test.coffee diff --git a/test/options-test.coffee b/test/options-test.coffee new file mode 100644 index 00000000..1699d195 --- /dev/null +++ b/test/options-test.coffee @@ -0,0 +1,119 @@ +{assert} = require('chai') +sinon = require('sinon') + +markdown = require('../src/adapters/markdown') + +lodash = require('../src/adapters/refract/helper') + +refractAdapter = require('../src/adapters/refract-adapter') +apiaryBlueprintAdapter = require('../src/adapters/apiary-blueprint-adapter') +apiBlueprintAdapter = require('../src/adapters/api-blueprint-adapter') + +describe('Options are passed to markdown renderer functions', -> + markdownSpy = null + markdownAsyncSpy = null + sourcemaps = undefined + + before(-> + markdownSpy = sinon.spy(markdown, 'toHtmlSync') + markdownAsyncSpy = sinon.spy(markdown, 'toHtml') + ) + + beforeEach(-> + markdownSpy.reset() + markdownAsyncSpy.reset() + ) + + after(-> + markdown.toHtmlSync.restore() + markdown.toHtml.restore() + ) + + context('Refract adapter passes options to markdown renderer', -> + parseResultElement = JSON.parse(JSON.stringify(require('./fixtures/refract-parse-result-x-summary.json'))) + apiDescriptionElement = null + options = undefined + + beforeEach( -> + apiDescriptionElement = lodash.chain(parseResultElement) + .content() + .find({element: 'category', meta: {classes: ['api']}}) + .value() + refractAdapter.transformAst(apiDescriptionElement, sourcemaps, options) + ) + + describe('When called without options', -> + before(-> + options = undefined + ) + + it('It does call Robotskirt Markdown to HTML renderer', -> + assert.isTrue(markdownSpy.called) + for callArgs in markdownSpy.args + assert.isUndefined(callArgs[1]) + assert.isFalse(markdownAsyncSpy.called) + ) + ) + describe('When called with options `{commonMark:true}`', -> + before(-> + options = {commonMark: true} + ) + + it('It does call CommonMark Markdown to HTML renderer', -> + assert.isTrue(markdownSpy.called) + for callArgs in markdownSpy.args + assert.equal(callArgs[0], 'I am a description') + assert.propertyVal(callArgs[1], 'commonMark', true) + assert.isFalse(markdownAsyncSpy.called) + ) + ) + ) + + context('API Blueprint adapter passes options to markdown renderer', -> + parseResultElement = JSON.parse(JSON.stringify(require('./fixtures/refract-parse-result-x-summary.json'))) + apiDescriptionElement = null + options = undefined + + beforeEach((done) -> + Drafter = require('drafter') + drafter = new Drafter({requireBlueprintName: false}) + source = """ + FORMAT: 1A + # apiName + such _description_. + ## GET [/api] + Yours lines are good! + """ + drafter.make(source, (err, result = {}) -> + return done(err) if err + apiBlueprintAdapter.transformAst(result.ast, sourcemaps, options) + done(err) + ) + ) + + describe('When called without options', -> + before(-> + options = undefined + ) + it('It does call Robotskirt Markdown to HTML renderer', -> + assert.isTrue(markdownSpy.called) + for callArgs in markdownSpy.args + assert.oneOf(callArgs[0], ['Yours lines are good!', 'such _description_.\n']) + assert.isUndefined(callArgs[1]) + assert.isFalse(markdownAsyncSpy.called) + ) + ) + + describe('When called with options `{commonMark:true}`', -> + before(-> + options = {commonMark: true} + ) + it('It does call CommonMark Markdown to HTML renderer', -> + assert.isTrue(markdownSpy.called) + for callArgs in markdownSpy.args + assert.propertyVal(callArgs[1], 'commonMark', true) + assert.isFalse(markdownAsyncSpy.called) + ) + ) + ) +) From 18dc2853862a6d2f989f44b5341fb9b8134433f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kor=C3=A1l?= Date: Tue, 9 Aug 2016 15:26:28 +0200 Subject: [PATCH 4/4] chore(markdown): do not mutate passed-in arguments, create own options property --- src/adapters/markdown.coffee | 27 ++++++++++++++++++++------- test/options-test.coffee | 4 ++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/adapters/markdown.coffee b/src/adapters/markdown.coffee index 4bc93c33..3ec4587d 100644 --- a/src/adapters/markdown.coffee +++ b/src/adapters/markdown.coffee @@ -2,7 +2,14 @@ {renderHtml, renderRobotskirtHtml} = require('blueprint-markdown-renderer') -parseMarkdown = (markdown, options = {}, cb) -> +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 if options.commonMark @@ -21,11 +28,17 @@ parseMarkdown = (markdown, options = {}, cb) -> 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) @@ -37,10 +50,10 @@ toHtml = (markdown, options = {}, cb) -> return -toHtmlSync = (markdown, options = {}) -> +toHtmlSync = (markdown, params) -> if not markdown return '' - return parseMarkdown(markdown, options) + return parseMarkdown(markdown, params) module.exports = { toHtml diff --git a/test/options-test.coffee b/test/options-test.coffee index 1699d195..1127ea30 100644 --- a/test/options-test.coffee +++ b/test/options-test.coffee @@ -63,7 +63,7 @@ describe('Options are passed to markdown renderer functions', -> assert.isTrue(markdownSpy.called) for callArgs in markdownSpy.args assert.equal(callArgs[0], 'I am a description') - assert.propertyVal(callArgs[1], 'commonMark', true) + assert.deepEqual(callArgs[1], {'commonMark': true}) assert.isFalse(markdownAsyncSpy.called) ) ) @@ -111,7 +111,7 @@ describe('Options are passed to markdown renderer functions', -> it('It does call CommonMark Markdown to HTML renderer', -> assert.isTrue(markdownSpy.called) for callArgs in markdownSpy.args - assert.propertyVal(callArgs[1], 'commonMark', true) + assert.deepEqual(callArgs[1], {'commonMark': true}) assert.isFalse(markdownAsyncSpy.called) ) )