Skip to content

Commit

Permalink
Merge pull request #49 from marp-team/lazy-yaml
Browse files Browse the repository at this point in the history
Lazy yaml support
  • Loading branch information
yhatt authored Aug 11, 2018
2 parents a6d2bc1 + 3dc29e7 commit 17804a4
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

- Bugfix: Pass `class` attribute to pseudo section on advanced background ([#48](https://github.com/marp-team/marpit/pull/48))
- Lazy yaml support by `lazyYAML` option ([#49](https://github.com/marp-team/marpit/pull/49))

## v0.0.10 - 2018-08-05

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Marpit will become a core of _the next version of **[Marp](https://github.com/yh
#### Difference from [pre-released Marp](https://github.com/yhatt/marp/)

- Removed directives about slide size. [Use `width` / `height` declaration of theme CSS.](#slide-size)
- Parse directives by YAML parser. ([js-yaml](https://github.com/nodeca/js-yaml) + [`FAILSAFE_SCHEMA`](http://www.yaml.org/spec/1.2/spec.html#id2802346))
- Parse directives by YAML parser. ([js-yaml](https://github.com/nodeca/js-yaml) + [`FAILSAFE_SCHEMA`](http://www.yaml.org/spec/1.2/spec.html#id2802346), but we still support lazy YAML parser by `lazyYAML` option)
- Support [Jekyll style front-matter](https://jekyllrb.com/docs/frontmatter/).
- _[Global directives](https://github.com/yhatt/marp/blob/master/example.md#global-directives)_ is no longer requires `$` prefix. (but it still supports because of compatibility and clarity)
- [Page directives](https://github.com/yhatt/marp/blob/master/example.md#page-directives) is renamed to _local directives_.
Expand Down Expand Up @@ -118,6 +118,8 @@ footer: "![image](https://example.com/image.jpg)"
```

> :warning: Marpit uses YAML for parsing directives, so **you should wrap with (double-)quotes** when the value includes invalid chars in YAML.
>
> You can enable a lazy YAML parser by `lazyYAML` Marpit constructor option if you want to recognize string without quotes.
> :information_source: Due to the parsing order of Markdown, you cannot use [slide background images](#slide-background) in `header` and `footer` directives.
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ declare module '@marp-team/marpit' {
filters?: boolean
headingDivider?: false | MarpitHeadingDivider | MarpitHeadingDivider[]
inlineStyle?: boolean
lazyYAML?: boolean
markdown?: string | object | [string, object]
printable?: boolean
slideContainer?: Element | Element[]
Expand Down
21 changes: 0 additions & 21 deletions src/helpers/parse_yaml.js

This file was deleted.

12 changes: 7 additions & 5 deletions src/markdown/comment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @module */
import parseYAML from '../helpers/parse_yaml'
import yaml from './directives/yaml'

const commentMatcher = /<!--+\s*([\s\S]*?)\s*--+>/
const commentMatcherOpening = /^<!--/
Expand All @@ -13,8 +13,10 @@ const commentMatcherClosing = /-->/
*
* @alias module:markdown/comment
* @param {MarkdownIt} md markdown-it instance.
* @param {Object} [opts]
* @param {boolean} [opts.lazyYAML=false] Allow lazy YAML for directives.
*/
function comment(md) {
function comment(md, opts = {}) {
/**
* Based on markdown-it html_block rule
* https://github.com/markdown-it/markdown-it/blob/master/lib/rules_block/html_block.js
Expand Down Expand Up @@ -60,9 +62,9 @@ function comment(md) {
const matchedContent = commentMatcher.exec(token.markup)
token.content = matchedContent ? matchedContent[1].trim() : ''

// Parse YAML
const yaml = parseYAML(token.content)
token.meta = { marpitParsedYAML: yaml === false ? {} : yaml }
// Parse object
const parsed = yaml(token.content, !!opts.lazyYAML)
token.meta = { marpitParsedDirectives: parsed === false ? {} : parsed }

return true
}
Expand Down
4 changes: 3 additions & 1 deletion src/markdown/directives/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,6 @@ export const locals = {
},
}

export default { globals, locals }
const directiveNames = [...Object.keys(globals), ...Object.keys(locals)]

export default directiveNames
29 changes: 15 additions & 14 deletions src/markdown/directives/parse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @module */
import MarkdownItFrontMatter from 'markdown-it-front-matter'
import parseYAML from '../../helpers/parse_yaml'
import yaml from './yaml'
import { globals, locals } from './directives'

/**
Expand All @@ -16,6 +16,7 @@ import { globals, locals } from './directives'
* @param {boolean} [opts.frontMatter=true] Switch feature to support YAML
* front-matter. If true, you can use Jekyll style directive setting to the
* first page.
* @param {boolean} [opts.lazyYAML=false] Allow lazy YAML for directives.
*/
function parse(md, marpit, opts = {}) {
// Front-matter support
Expand All @@ -30,8 +31,8 @@ function parse(md, marpit, opts = {}) {
md.use(MarkdownItFrontMatter, fm => {
frontMatterObject.text = fm

const yaml = parseYAML(fm)
if (yaml !== false) frontMatterObject.yaml = yaml
const parsed = yaml(fm, !!opts.lazyYAML)
if (parsed !== false) frontMatterObject.yaml = parsed
})
}

Expand All @@ -40,23 +41,23 @@ function parse(md, marpit, opts = {}) {
if (state.inlineMode) return

let globalDirectives = {}
const applyDirectives = yaml => {
Object.keys(yaml).forEach(key => {
const applyDirectives = obj => {
Object.keys(obj).forEach(key => {
const globalKey = key.startsWith('$') ? key.slice(1) : key

if (globals[globalKey])
globalDirectives = {
...globalDirectives,
...globals[globalKey](yaml[key], marpit),
...globals[globalKey](obj[key], marpit),
}
})
}

if (frontMatterObject.yaml) applyDirectives(frontMatterObject.yaml)

state.tokens.forEach(token => {
if (token.type === 'marpit_comment' && token.meta.marpitParsedYAML)
applyDirectives(token.meta.marpitParsedYAML)
if (token.type === 'marpit_comment' && token.meta.marpitParsedDirectives)
applyDirectives(token.meta.marpitParsedDirectives)
})

marpit.lastGlobalDirectives = { ...globalDirectives }
Expand All @@ -69,10 +70,10 @@ function parse(md, marpit, opts = {}) {
const slides = []
const cursor = { slide: undefined, local: {}, spot: {} }

const applyDirectives = yaml => {
Object.keys(yaml).forEach(key => {
const applyDirectives = obj => {
Object.keys(obj).forEach(key => {
if (locals[key])
cursor.local = { ...cursor.local, ...locals[key](yaml[key], marpit) }
cursor.local = { ...cursor.local, ...locals[key](obj[key], marpit) }

// Spot directives
// (Apply local directive to only current slide by prefix "_")
Expand All @@ -82,7 +83,7 @@ function parse(md, marpit, opts = {}) {
if (locals[spotKey])
cursor.spot = {
...cursor.spot,
...locals[spotKey](yaml[key], marpit),
...locals[spotKey](obj[key], marpit),
}
}
})
Expand All @@ -108,9 +109,9 @@ function parse(md, marpit, opts = {}) {
cursor.spot = {}
} else if (
token.type === 'marpit_comment' &&
token.meta.marpitParsedYAML
token.meta.marpitParsedDirectives
) {
applyDirectives(token.meta.marpitParsedYAML)
applyDirectives(token.meta.marpitParsedDirectives)
}
})

Expand Down
58 changes: 58 additions & 0 deletions src/markdown/directives/yaml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** @module */
import YAML, { FAILSAFE_SCHEMA } from 'js-yaml'
import directives from './directives'

/**
* Parse text as YAML by using js-yaml's FAILSAFE_SCHEMA.
*
* @alias module:markdown/directives/yaml
* @param {String} text Target text.
* @param {boolean} allowLazy By `true`, it try to parse lazy YAML at first.
* @returns {Object|false} Return parse result, or `false` when failed to parse.
*/

const keyPattern = `[_$]?(?:${directives.join('|')})`
const directivesMatcher = new RegExp(`^\\s*(${keyPattern})\\s*:(.*)$`)
const specialChars = `["'{|>~&*`
const whitespaceMatcher = /^\s*$/

function strict(text) {
try {
const obj = YAML.safeLoad(text, { schema: FAILSAFE_SCHEMA })
if (obj === null || typeof obj !== 'object') return false

return obj
} catch (e) {
return false
}
}

function lazy(text) {
const collected = {}
const lines = text.split(/\r?\n/)

return lines.every(line => {
if (whitespaceMatcher.test(line)) return true

const matched = directivesMatcher.exec(line)
if (!matched) return false

const [, directive, originalValue] = matched
const value = originalValue.trim()
if (specialChars.includes(value[0])) return false

collected[directive] = value
return true
})
? collected
: false
}

export default function(text, allowLazy) {
if (allowLazy) {
const lazyResult = lazy(text)
if (lazyResult !== false) return lazyResult
}

return strict(text)
}
12 changes: 8 additions & 4 deletions src/marpit.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const defaultOptions = {
filters: true,
headingDivider: false,
inlineStyle: true,
lazyYAML: false,
markdown: 'commonmark',
printable: true,
slideContainer: undefined,
Expand Down Expand Up @@ -54,6 +55,7 @@ class Marpit {
* @param {boolean} [opts.inlineStyle=true] Recognize `<style>` elements to
* append additional styles to theme. When it is `true`, Marpit will parse
* style regardless markdown-it's `html` option.
* @param {boolean} [opts.lazyYAML=false] Allow lazy YAML for directives.
* @param {string|Object|Array} [opts.markdown='commonmark'] markdown-it
* initialize option(s).
* @param {boolean} [opts.printable=true] Make style printable to PDF.
Expand Down Expand Up @@ -109,22 +111,24 @@ class Marpit {

/** @private */
applyMarkdownItPlugins(md = this.markdown) {
md.use(marpitComment)
const { backgroundSyntax, filters, lazyYAML } = this.options

md.use(marpitComment, { lazyYAML })
.use(marpitStyleParse, this)
.use(marpitSlide)
.use(marpitParseDirectives, this)
.use(marpitParseDirectives, this, { lazyYAML })
.use(marpitApplyDirectives)
.use(marpitHeaderAndFooter)
.use(marpitHeadingDivider, this)
.use(marpitSlideContainer, this.slideContainers)
.use(marpitContainerPlugin, this.containers)
.use(marpitParseImage, { filters: this.options.filters })
.use(marpitParseImage, { filters })
.use(marpitUnicodeEmoji)
.use(marpitSweep)
.use(marpitInlineSVG, this)
.use(marpitStyleAssign, this)

if (this.options.backgroundSyntax) md.use(marpitBackgroundImage)
if (backgroundSyntax) md.use(marpitBackgroundImage)
}

/**
Expand Down
44 changes: 44 additions & 0 deletions test/markdown/directives/yaml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import dedent from 'dedent'
import yaml from '../../../src/markdown/directives/yaml'

describe('Marpit directives YAML parser', () => {
it("ignores directive's special char with false allowLazy option", () =>
expect(yaml('color: #f00', false).color).toBeFalsy())

context('with allowLazy option as true', () => {
it("parses directive's special char as string", () =>
expect(yaml('color: #f00', true).color).toBe('#f00'))

it('fallbacks to regular YAML parser when passed like strict YAML', () => {
const confirm = text =>
expect(yaml(text, true)).toMatchObject(yaml(text, false))

confirm('headingDivider: [3]')
confirm('backgroundPosition: "left center"')
confirm("backgroundSize: '100px 200px'")
confirm(dedent`
color: #f00
notSupported: key
`)
confirm(dedent`
class:
- first
- second
`)
confirm(dedent`
header: >
Hello,
world!
`)
confirm(dedent`
footer: |
Multiline
footer
`)
confirm(dedent`
class: &anchored klass
_class: *anchored
`)
})
})
})
Loading

0 comments on commit 17804a4

Please sign in to comment.