Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. 'use strict'
  2. module.exports = fromMarkdown
  3. // These three are compiled away in the `dist/`
  4. var toString = require('mdast-util-to-string')
  5. var assign = require('micromark/dist/constant/assign')
  6. var own = require('micromark/dist/constant/has-own-property')
  7. var normalizeIdentifier = require('micromark/dist/util/normalize-identifier')
  8. var safeFromInt = require('micromark/dist/util/safe-from-int')
  9. var parser = require('micromark/dist/parse')
  10. var preprocessor = require('micromark/dist/preprocess')
  11. var postprocess = require('micromark/dist/postprocess')
  12. var decode = require('parse-entities/decode-entity')
  13. var stringifyPosition = require('unist-util-stringify-position')
  14. function fromMarkdown(value, encoding, options) {
  15. if (typeof encoding !== 'string') {
  16. options = encoding
  17. encoding = undefined
  18. }
  19. return compiler(options)(
  20. postprocess(
  21. parser(options).document().write(preprocessor()(value, encoding, true))
  22. )
  23. )
  24. }
  25. // Note this compiler only understand complete buffering, not streaming.
  26. function compiler(options) {
  27. var settings = options || {}
  28. var config = configure(
  29. {
  30. transforms: [],
  31. canContainEols: [
  32. 'emphasis',
  33. 'fragment',
  34. 'heading',
  35. 'paragraph',
  36. 'strong'
  37. ],
  38. enter: {
  39. autolink: opener(link),
  40. autolinkProtocol: onenterdata,
  41. autolinkEmail: onenterdata,
  42. atxHeading: opener(heading),
  43. blockQuote: opener(blockQuote),
  44. characterEscape: onenterdata,
  45. characterReference: onenterdata,
  46. codeFenced: opener(codeFlow),
  47. codeFencedFenceInfo: buffer,
  48. codeFencedFenceMeta: buffer,
  49. codeIndented: opener(codeFlow, buffer),
  50. codeText: opener(codeText, buffer),
  51. codeTextData: onenterdata,
  52. data: onenterdata,
  53. codeFlowValue: onenterdata,
  54. definition: opener(definition),
  55. definitionDestinationString: buffer,
  56. definitionLabelString: buffer,
  57. definitionTitleString: buffer,
  58. emphasis: opener(emphasis),
  59. hardBreakEscape: opener(hardBreak),
  60. hardBreakTrailing: opener(hardBreak),
  61. htmlFlow: opener(html, buffer),
  62. htmlFlowData: onenterdata,
  63. htmlText: opener(html, buffer),
  64. htmlTextData: onenterdata,
  65. image: opener(image),
  66. label: buffer,
  67. link: opener(link),
  68. listItem: opener(listItem),
  69. listItemValue: onenterlistitemvalue,
  70. listOrdered: opener(list, onenterlistordered),
  71. listUnordered: opener(list),
  72. paragraph: opener(paragraph),
  73. reference: onenterreference,
  74. referenceString: buffer,
  75. resourceDestinationString: buffer,
  76. resourceTitleString: buffer,
  77. setextHeading: opener(heading),
  78. strong: opener(strong),
  79. thematicBreak: opener(thematicBreak)
  80. },
  81. exit: {
  82. atxHeading: closer(),
  83. atxHeadingSequence: onexitatxheadingsequence,
  84. autolink: closer(),
  85. autolinkEmail: onexitautolinkemail,
  86. autolinkProtocol: onexitautolinkprotocol,
  87. blockQuote: closer(),
  88. characterEscapeValue: onexitdata,
  89. characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
  90. characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
  91. characterReferenceValue: onexitcharacterreferencevalue,
  92. codeFenced: closer(onexitcodefenced),
  93. codeFencedFence: onexitcodefencedfence,
  94. codeFencedFenceInfo: onexitcodefencedfenceinfo,
  95. codeFencedFenceMeta: onexitcodefencedfencemeta,
  96. codeFlowValue: onexitdata,
  97. codeIndented: closer(onexitcodeindented),
  98. codeText: closer(onexitcodetext),
  99. codeTextData: onexitdata,
  100. data: onexitdata,
  101. definition: closer(),
  102. definitionDestinationString: onexitdefinitiondestinationstring,
  103. definitionLabelString: onexitdefinitionlabelstring,
  104. definitionTitleString: onexitdefinitiontitlestring,
  105. emphasis: closer(),
  106. hardBreakEscape: closer(onexithardbreak),
  107. hardBreakTrailing: closer(onexithardbreak),
  108. htmlFlow: closer(onexithtmlflow),
  109. htmlFlowData: onexitdata,
  110. htmlText: closer(onexithtmltext),
  111. htmlTextData: onexitdata,
  112. image: closer(onexitimage),
  113. label: onexitlabel,
  114. labelText: onexitlabeltext,
  115. lineEnding: onexitlineending,
  116. link: closer(onexitlink),
  117. listItem: closer(),
  118. listOrdered: closer(),
  119. listUnordered: closer(),
  120. paragraph: closer(),
  121. referenceString: onexitreferencestring,
  122. resourceDestinationString: onexitresourcedestinationstring,
  123. resourceTitleString: onexitresourcetitlestring,
  124. resource: onexitresource,
  125. setextHeading: closer(onexitsetextheading),
  126. setextHeadingLineSequence: onexitsetextheadinglinesequence,
  127. setextHeadingText: onexitsetextheadingtext,
  128. strong: closer(),
  129. thematicBreak: closer()
  130. }
  131. },
  132. settings.mdastExtensions || []
  133. )
  134. var data = {}
  135. return compile
  136. function compile(events) {
  137. var tree = {type: 'root', children: []}
  138. var stack = [tree]
  139. var tokenStack = []
  140. var listStack = []
  141. var index = -1
  142. var handler
  143. var listStart
  144. var context = {
  145. stack: stack,
  146. tokenStack: tokenStack,
  147. config: config,
  148. enter: enter,
  149. exit: exit,
  150. buffer: buffer,
  151. resume: resume,
  152. setData: setData,
  153. getData: getData
  154. }
  155. while (++index < events.length) {
  156. // We preprocess lists to add `listItem` tokens, and to infer whether
  157. // items the list itself are spread out.
  158. if (
  159. events[index][1].type === 'listOrdered' ||
  160. events[index][1].type === 'listUnordered'
  161. ) {
  162. if (events[index][0] === 'enter') {
  163. listStack.push(index)
  164. } else {
  165. listStart = listStack.pop(index)
  166. index = prepareList(events, listStart, index)
  167. }
  168. }
  169. }
  170. index = -1
  171. while (++index < events.length) {
  172. handler = config[events[index][0]]
  173. if (own.call(handler, events[index][1].type)) {
  174. handler[events[index][1].type].call(
  175. assign({sliceSerialize: events[index][2].sliceSerialize}, context),
  176. events[index][1]
  177. )
  178. }
  179. }
  180. if (tokenStack.length) {
  181. throw new Error(
  182. 'Cannot close document, a token (`' +
  183. tokenStack[tokenStack.length - 1].type +
  184. '`, ' +
  185. stringifyPosition({
  186. start: tokenStack[tokenStack.length - 1].start,
  187. end: tokenStack[tokenStack.length - 1].end
  188. }) +
  189. ') is still open'
  190. )
  191. }
  192. // Figure out `root` position.
  193. tree.position = {
  194. start: point(
  195. events.length ? events[0][1].start : {line: 1, column: 1, offset: 0}
  196. ),
  197. end: point(
  198. events.length
  199. ? events[events.length - 2][1].end
  200. : {line: 1, column: 1, offset: 0}
  201. )
  202. }
  203. index = -1
  204. while (++index < config.transforms.length) {
  205. tree = config.transforms[index](tree) || tree
  206. }
  207. return tree
  208. }
  209. function prepareList(events, start, length) {
  210. var index = start - 1
  211. var containerBalance = -1
  212. var listSpread = false
  213. var listItem
  214. var tailIndex
  215. var lineIndex
  216. var tailEvent
  217. var event
  218. var firstBlankLineIndex
  219. var atMarker
  220. while (++index <= length) {
  221. event = events[index]
  222. if (
  223. event[1].type === 'listUnordered' ||
  224. event[1].type === 'listOrdered' ||
  225. event[1].type === 'blockQuote'
  226. ) {
  227. if (event[0] === 'enter') {
  228. containerBalance++
  229. } else {
  230. containerBalance--
  231. }
  232. atMarker = undefined
  233. } else if (event[1].type === 'lineEndingBlank') {
  234. if (event[0] === 'enter') {
  235. if (
  236. listItem &&
  237. !atMarker &&
  238. !containerBalance &&
  239. !firstBlankLineIndex
  240. ) {
  241. firstBlankLineIndex = index
  242. }
  243. atMarker = undefined
  244. }
  245. } else if (
  246. event[1].type === 'linePrefix' ||
  247. event[1].type === 'listItemValue' ||
  248. event[1].type === 'listItemMarker' ||
  249. event[1].type === 'listItemPrefix' ||
  250. event[1].type === 'listItemPrefixWhitespace'
  251. ) {
  252. // Empty.
  253. } else {
  254. atMarker = undefined
  255. }
  256. if (
  257. (!containerBalance &&
  258. event[0] === 'enter' &&
  259. event[1].type === 'listItemPrefix') ||
  260. (containerBalance === -1 &&
  261. event[0] === 'exit' &&
  262. (event[1].type === 'listUnordered' ||
  263. event[1].type === 'listOrdered'))
  264. ) {
  265. if (listItem) {
  266. tailIndex = index
  267. lineIndex = undefined
  268. while (tailIndex--) {
  269. tailEvent = events[tailIndex]
  270. if (
  271. tailEvent[1].type === 'lineEnding' ||
  272. tailEvent[1].type === 'lineEndingBlank'
  273. ) {
  274. if (tailEvent[0] === 'exit') continue
  275. if (lineIndex) {
  276. events[lineIndex][1].type = 'lineEndingBlank'
  277. listSpread = true
  278. }
  279. tailEvent[1].type = 'lineEnding'
  280. lineIndex = tailIndex
  281. } else if (
  282. tailEvent[1].type === 'linePrefix' ||
  283. tailEvent[1].type === 'blockQuotePrefix' ||
  284. tailEvent[1].type === 'blockQuotePrefixWhitespace' ||
  285. tailEvent[1].type === 'blockQuoteMarker' ||
  286. tailEvent[1].type === 'listItemIndent'
  287. ) {
  288. // Empty
  289. } else {
  290. break
  291. }
  292. }
  293. if (
  294. firstBlankLineIndex &&
  295. (!lineIndex || firstBlankLineIndex < lineIndex)
  296. ) {
  297. listItem._spread = true
  298. }
  299. // Fix position.
  300. listItem.end = point(
  301. lineIndex ? events[lineIndex][1].start : event[1].end
  302. )
  303. events.splice(lineIndex || index, 0, ['exit', listItem, event[2]])
  304. index++
  305. length++
  306. }
  307. // Create a new list item.
  308. if (event[1].type === 'listItemPrefix') {
  309. listItem = {
  310. type: 'listItem',
  311. _spread: false,
  312. start: point(event[1].start)
  313. }
  314. events.splice(index, 0, ['enter', listItem, event[2]])
  315. index++
  316. length++
  317. firstBlankLineIndex = undefined
  318. atMarker = true
  319. }
  320. }
  321. }
  322. events[start][1]._spread = listSpread
  323. return length
  324. }
  325. function setData(key, value) {
  326. data[key] = value
  327. }
  328. function getData(key) {
  329. return data[key]
  330. }
  331. function point(d) {
  332. return {line: d.line, column: d.column, offset: d.offset}
  333. }
  334. function opener(create, and) {
  335. return open
  336. function open(token) {
  337. enter.call(this, create(token), token)
  338. if (and) and.call(this, token)
  339. }
  340. }
  341. function buffer() {
  342. this.stack.push({type: 'fragment', children: []})
  343. }
  344. function enter(node, token) {
  345. this.stack[this.stack.length - 1].children.push(node)
  346. this.stack.push(node)
  347. this.tokenStack.push(token)
  348. node.position = {start: point(token.start)}
  349. return node
  350. }
  351. function closer(and) {
  352. return close
  353. function close(token) {
  354. if (and) and.call(this, token)
  355. exit.call(this, token)
  356. }
  357. }
  358. function exit(token) {
  359. var node = this.stack.pop()
  360. var open = this.tokenStack.pop()
  361. if (!open) {
  362. throw new Error(
  363. 'Cannot close `' +
  364. token.type +
  365. '` (' +
  366. stringifyPosition({start: token.start, end: token.end}) +
  367. '): it’s not open'
  368. )
  369. } else if (open.type !== token.type) {
  370. throw new Error(
  371. 'Cannot close `' +
  372. token.type +
  373. '` (' +
  374. stringifyPosition({start: token.start, end: token.end}) +
  375. '): a different token (`' +
  376. open.type +
  377. '`, ' +
  378. stringifyPosition({start: open.start, end: open.end}) +
  379. ') is open'
  380. )
  381. }
  382. node.position.end = point(token.end)
  383. return node
  384. }
  385. function resume() {
  386. return toString(this.stack.pop())
  387. }
  388. //
  389. // Handlers.
  390. //
  391. function onenterlistordered() {
  392. setData('expectingFirstListItemValue', true)
  393. }
  394. function onenterlistitemvalue(token) {
  395. if (getData('expectingFirstListItemValue')) {
  396. this.stack[this.stack.length - 2].start = parseInt(
  397. this.sliceSerialize(token),
  398. 10
  399. )
  400. setData('expectingFirstListItemValue')
  401. }
  402. }
  403. function onexitcodefencedfenceinfo() {
  404. var data = this.resume()
  405. this.stack[this.stack.length - 1].lang = data
  406. }
  407. function onexitcodefencedfencemeta() {
  408. var data = this.resume()
  409. this.stack[this.stack.length - 1].meta = data
  410. }
  411. function onexitcodefencedfence() {
  412. // Exit if this is the closing fence.
  413. if (getData('flowCodeInside')) return
  414. this.buffer()
  415. setData('flowCodeInside', true)
  416. }
  417. function onexitcodefenced() {
  418. var data = this.resume()
  419. this.stack[this.stack.length - 1].value = data.replace(
  420. /^(\r?\n|\r)|(\r?\n|\r)$/g,
  421. ''
  422. )
  423. setData('flowCodeInside')
  424. }
  425. function onexitcodeindented() {
  426. var data = this.resume()
  427. this.stack[this.stack.length - 1].value = data
  428. }
  429. function onexitdefinitionlabelstring(token) {
  430. // Discard label, use the source content instead.
  431. var label = this.resume()
  432. this.stack[this.stack.length - 1].label = label
  433. this.stack[this.stack.length - 1].identifier = normalizeIdentifier(
  434. this.sliceSerialize(token)
  435. ).toLowerCase()
  436. }
  437. function onexitdefinitiontitlestring() {
  438. var data = this.resume()
  439. this.stack[this.stack.length - 1].title = data
  440. }
  441. function onexitdefinitiondestinationstring() {
  442. var data = this.resume()
  443. this.stack[this.stack.length - 1].url = data
  444. }
  445. function onexitatxheadingsequence(token) {
  446. if (!this.stack[this.stack.length - 1].depth) {
  447. this.stack[this.stack.length - 1].depth = this.sliceSerialize(
  448. token
  449. ).length
  450. }
  451. }
  452. function onexitsetextheadingtext() {
  453. setData('setextHeadingSlurpLineEnding', true)
  454. }
  455. function onexitsetextheadinglinesequence(token) {
  456. this.stack[this.stack.length - 1].depth =
  457. this.sliceSerialize(token).charCodeAt(0) === 61 ? 1 : 2
  458. }
  459. function onexitsetextheading() {
  460. setData('setextHeadingSlurpLineEnding')
  461. }
  462. function onenterdata(token) {
  463. var siblings = this.stack[this.stack.length - 1].children
  464. var tail = siblings[siblings.length - 1]
  465. if (!tail || tail.type !== 'text') {
  466. // Add a new text node.
  467. tail = text()
  468. tail.position = {start: point(token.start)}
  469. this.stack[this.stack.length - 1].children.push(tail)
  470. }
  471. this.stack.push(tail)
  472. }
  473. function onexitdata(token) {
  474. var tail = this.stack.pop()
  475. tail.value += this.sliceSerialize(token)
  476. tail.position.end = point(token.end)
  477. }
  478. function onexitlineending(token) {
  479. var context = this.stack[this.stack.length - 1]
  480. // If we’re at a hard break, include the line ending in there.
  481. if (getData('atHardBreak')) {
  482. context.children[context.children.length - 1].position.end = point(
  483. token.end
  484. )
  485. setData('atHardBreak')
  486. return
  487. }
  488. if (
  489. !getData('setextHeadingSlurpLineEnding') &&
  490. config.canContainEols.indexOf(context.type) > -1
  491. ) {
  492. onenterdata.call(this, token)
  493. onexitdata.call(this, token)
  494. }
  495. }
  496. function onexithardbreak() {
  497. setData('atHardBreak', true)
  498. }
  499. function onexithtmlflow() {
  500. var data = this.resume()
  501. this.stack[this.stack.length - 1].value = data
  502. }
  503. function onexithtmltext() {
  504. var data = this.resume()
  505. this.stack[this.stack.length - 1].value = data
  506. }
  507. function onexitcodetext() {
  508. var data = this.resume()
  509. this.stack[this.stack.length - 1].value = data
  510. }
  511. function onexitlink() {
  512. var context = this.stack[this.stack.length - 1]
  513. // To do: clean.
  514. if (getData('inReference')) {
  515. context.type += 'Reference'
  516. context.referenceType = getData('referenceType') || 'shortcut'
  517. delete context.url
  518. delete context.title
  519. } else {
  520. delete context.identifier
  521. delete context.label
  522. delete context.referenceType
  523. }
  524. setData('referenceType')
  525. }
  526. function onexitimage() {
  527. var context = this.stack[this.stack.length - 1]
  528. // To do: clean.
  529. if (getData('inReference')) {
  530. context.type += 'Reference'
  531. context.referenceType = getData('referenceType') || 'shortcut'
  532. delete context.url
  533. delete context.title
  534. } else {
  535. delete context.identifier
  536. delete context.label
  537. delete context.referenceType
  538. }
  539. setData('referenceType')
  540. }
  541. function onexitlabeltext(token) {
  542. this.stack[this.stack.length - 2].identifier = normalizeIdentifier(
  543. this.sliceSerialize(token)
  544. ).toLowerCase()
  545. }
  546. function onexitlabel() {
  547. var fragment = this.stack[this.stack.length - 1]
  548. var value = this.resume()
  549. this.stack[this.stack.length - 1].label = value
  550. // Assume a reference.
  551. setData('inReference', true)
  552. if (this.stack[this.stack.length - 1].type === 'link') {
  553. this.stack[this.stack.length - 1].children = fragment.children
  554. } else {
  555. this.stack[this.stack.length - 1].alt = value
  556. }
  557. }
  558. function onexitresourcedestinationstring() {
  559. var data = this.resume()
  560. this.stack[this.stack.length - 1].url = data
  561. }
  562. function onexitresourcetitlestring() {
  563. var data = this.resume()
  564. this.stack[this.stack.length - 1].title = data
  565. }
  566. function onexitresource() {
  567. setData('inReference')
  568. }
  569. function onenterreference() {
  570. setData('referenceType', 'collapsed')
  571. }
  572. function onexitreferencestring(token) {
  573. var label = this.resume()
  574. this.stack[this.stack.length - 1].label = label
  575. this.stack[this.stack.length - 1].identifier = normalizeIdentifier(
  576. this.sliceSerialize(token)
  577. ).toLowerCase()
  578. setData('referenceType', 'full')
  579. }
  580. function onexitcharacterreferencemarker(token) {
  581. setData('characterReferenceType', token.type)
  582. }
  583. function onexitcharacterreferencevalue(token) {
  584. var data = this.sliceSerialize(token)
  585. var type = getData('characterReferenceType')
  586. var value
  587. var tail
  588. if (type) {
  589. value = safeFromInt(
  590. data,
  591. type === 'characterReferenceMarkerNumeric' ? 10 : 16
  592. )
  593. setData('characterReferenceType')
  594. } else {
  595. value = decode(data)
  596. }
  597. tail = this.stack.pop()
  598. tail.value += value
  599. tail.position.end = point(token.end)
  600. }
  601. function onexitautolinkprotocol(token) {
  602. onexitdata.call(this, token)
  603. this.stack[this.stack.length - 1].url = this.sliceSerialize(token)
  604. }
  605. function onexitautolinkemail(token) {
  606. onexitdata.call(this, token)
  607. this.stack[this.stack.length - 1].url =
  608. 'mailto:' + this.sliceSerialize(token)
  609. }
  610. //
  611. // Creaters.
  612. //
  613. function blockQuote() {
  614. return {type: 'blockquote', children: []}
  615. }
  616. function codeFlow() {
  617. return {type: 'code', lang: null, meta: null, value: ''}
  618. }
  619. function codeText() {
  620. return {type: 'inlineCode', value: ''}
  621. }
  622. function definition() {
  623. return {
  624. type: 'definition',
  625. identifier: '',
  626. label: null,
  627. title: null,
  628. url: ''
  629. }
  630. }
  631. function emphasis() {
  632. return {type: 'emphasis', children: []}
  633. }
  634. function heading() {
  635. return {type: 'heading', depth: undefined, children: []}
  636. }
  637. function hardBreak() {
  638. return {type: 'break'}
  639. }
  640. function html() {
  641. return {type: 'html', value: ''}
  642. }
  643. function image() {
  644. return {type: 'image', title: null, url: '', alt: null}
  645. }
  646. function link() {
  647. return {type: 'link', title: null, url: '', children: []}
  648. }
  649. function list(token) {
  650. return {
  651. type: 'list',
  652. ordered: token.type === 'listOrdered',
  653. start: null,
  654. spread: token._spread,
  655. children: []
  656. }
  657. }
  658. function listItem(token) {
  659. return {
  660. type: 'listItem',
  661. spread: token._spread,
  662. checked: null,
  663. children: []
  664. }
  665. }
  666. function paragraph() {
  667. return {type: 'paragraph', children: []}
  668. }
  669. function strong() {
  670. return {type: 'strong', children: []}
  671. }
  672. function text() {
  673. return {type: 'text', value: ''}
  674. }
  675. function thematicBreak() {
  676. return {type: 'thematicBreak'}
  677. }
  678. }
  679. function configure(config, extensions) {
  680. var index = -1
  681. while (++index < extensions.length) {
  682. extension(config, extensions[index])
  683. }
  684. return config
  685. }
  686. function extension(config, extension) {
  687. var key
  688. var left
  689. for (key in extension) {
  690. left = own.call(config, key) ? config[key] : (config[key] = {})
  691. if (key === 'canContainEols' || key === 'transforms') {
  692. config[key] = [].concat(left, extension[key])
  693. } else {
  694. Object.assign(left, extension[key])
  695. }
  696. }
  697. }