All Downloads are FREE. Search and download functionalities are using the official Maven repository.

laika.markdown.github.FencedCodeBlocks.scala Maven / Gradle / Ivy

/*
 * Copyright 2013-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package laika.markdown.github

import laika.ast.{Block, CodeBlock, LiteralBlock, Text, ~}
import laika.bundle.{BlockParser, BlockParserBuilder}
import laika.parse.text.TextParsers._

/** Parser for fenced code blocks as defined by GitHub Flavored Markdown and CommonMark.
  *
  * For the spec see [[https://github.github.com/gfm/#fenced-code-blocks]].
  *
  * This implementation differs from the spec in one aspect: a fenced code block must be preceded by a blank line.
  * This is due to technical reasons of fenced code blocks being an extension and the default paragraph parser
  * not knowing about the installed extensions and which of them are allowed to interrupt a paragraph.
  * Later version might support the exact spec, but that would require changes in how parser extensions
  * are registered and combined with the native parsers.
  *
  * @author Jens Halm
  */
object FencedCodeBlocks {

  /** Creates a parser for a fenced code block with the specified fence character.
    */
  def codeBlock (fenceChar: Char): BlockParserBuilder = BlockParser.forStartChar(fenceChar).standalone {

    val infoString = restOfLine.map(Some(_)
      .filter(_.nonEmpty)
      .flatMap(_.trim.split(" ").headOption)
    )
    val openingFence = anyOf(fenceChar).min(2).count ~ infoString

    openingFence >> { case charCount ~ info =>

      val closingFence = anyOf(fenceChar).min(charCount + 1) ~ wsEol

      (not(closingFence | eof) ~> textLine).rep <~ opt(closingFence) ^^ { lines =>
        val code = lines.mkString("\n")
        info.fold(LiteralBlock(code): Block) { lang => CodeBlock(lang, Seq(Text(code))) }
      }
    }
  }

  /** Parsers for fenced code blocks delimited by any of the two fence characters
    * defined by GitHub Flavored Markdown (tilde and backtick).
    */
  val parsers: Seq[BlockParserBuilder] = Seq('~','`').map(codeBlock)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy