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

dotty.tastydoc.comment.HtmlParsers.scala Maven / Gradle / Ivy

The newest version!
package dotty.tastydoc.comment

import util.MemberLookup

import dotty.tastydoc.representations._

import java.util.{ Arrays }

import com.vladsch.flexmark.util.ast.{ Node => MarkdownNode}
import com.vladsch.flexmark.formatter.Formatter
import com.vladsch.flexmark.parser.Parser
import com.vladsch.flexmark.util.sequence.CharSubSequence
import com.vladsch.flexmark.parser.ParserEmulationProfile
import com.vladsch.flexmark.ext.gfm.tables.TablesExtension
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension
import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension
import com.vladsch.flexmark.ext.emoji.EmojiExtension
import com.vladsch.flexmark.ext.autolink.AutolinkExtension
import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension
import com.vladsch.flexmark.util.options.{ DataHolder, MutableDataSet }

object HtmlParsers {

  val markdownOptions: DataHolder =
    new MutableDataSet()
      .setFrom(ParserEmulationProfile.KRAMDOWN.getOptions)
      .set(Parser.EXTENSIONS, Arrays.asList(
        TablesExtension.create(),
        TaskListExtension.create(),
        AutolinkExtension.create(),
        AnchorLinkExtension.create(),
        EmojiExtension.create(),
        YamlFrontMatterExtension.create(),
        StrikethroughExtension.create()
      ))
      .set(EmojiExtension.ROOT_IMAGE_PATH,
        "https://github.global.ssl.fastly.net/images/icons/emoji/")

  val RENDERER = Formatter.builder(markdownOptions).build()

  implicit class StringToMarkdown(val text: String) extends AnyVal {
    def toMarkdown(origin: Representation, packages: Map[String, EmulatedPackageRepresentation]): MarkdownNode = {
      import com.vladsch.flexmark.ast.Link
      import com.vladsch.flexmark.util.ast.{Visitor, VisitHandler, NodeVisitor }

      val inlineToMarkdown = InlineToMarkdown(origin)

      val node = Parser.builder(markdownOptions)
        .build.parse(text)

      def isOuter(url: String) =
        url.startsWith("http://") ||
        url.startsWith("https://") ||
        url.startsWith("ftp://") ||
        url.startsWith("ftps://")

      def isRelative(url: String) =
        url.startsWith("../") ||
        url.startsWith("./")

      val linkVisitor = new NodeVisitor(
        new VisitHandler(classOf[Link], new Visitor[Link] with MemberLookup {
          def queryToUrl(title: String, link: String) = makeRepresentationLink(origin, packages, Text(title), link).link match {
            case Tooltip(_) => "#"
            case LinkToExternal(_, url) => url
            case LinkToRepresentation(t: Representation) => t match {
              case e: Representation with Members => inlineToMarkdown.relativePath(t)
              case x => x.parentRepresentation.fold("#") { xpar => inlineToMarkdown.relativePath(xpar) }
            }
          }

          override def visit(link: Link) = {
            val linkUrl = link.getUrl.toString
            if (!isOuter(linkUrl) && !isRelative(linkUrl))
              link.setUrl(CharSubSequence.of(queryToUrl(link.getTitle.toString, linkUrl)))
          }
        })
      )

      linkVisitor.visit(node)
      node
    }

    def toMarkdownString(origin: Representation, packages: Map[String, EmulatedPackageRepresentation]): String =
      toMarkdown(origin, packages).show
  }

  implicit class MarkdownToHtml(val markdown: MarkdownNode) extends AnyVal {
    def show: String =
      RENDERER.render(markdown)

    def shortenAndShow: String =
      (new MarkdownShortener).shorten(markdown).show
  }

  implicit class StringToWiki(val text: String) extends AnyVal {
    def toWiki(origin: Representation, packages: Map[String, EmulatedPackageRepresentation]): Body =
      new WikiParser(origin, packages, text).document()
  }

  implicit class BodyToMarkdown(val body: Body) extends AnyVal {
    def show(origin: Representation): String = {
      val inlineToMarkdown = InlineToMarkdown(origin)

      def bodyToMarkdown(body: Body): String =
        (body.blocks map blockToMarkdown).mkString

      def listItemsToMarkdown(items: Seq[Block], level: Int = 0, ordered: Boolean = false): String ={
        if(ordered){
          items.foldLeft(("", 1)){ (list, item) =>
            item match {
              case OrderedList(itemsLvl2, _) => val x = itemsLvl2; (list._1 + s"${listItemsToMarkdown(x, level + 1, true)}\n", list._2 + 1)
              case UnorderedList(itemsLvl2) => val x = itemsLvl2; (list._1 + s"${listItemsToMarkdown(x, level + 1, false)}\n", list._2 + 1)
              case Paragraph(inl) => (list._1 + s"${"\t"*level}${list._2}. ${inlineToMarkdown(inl)}\n", list._2 + 1)
              case block => (list._1 + s"${"\t"*level}${list._2}. ${blockToMarkdown(block)}\n", list._2 + 1)
            }
          }._1
        }else{
          items.foldLeft(""){ (list, item) =>
            item match {
              case OrderedList(itemsLvl2, _) => val x = itemsLvl2; list + s"${listItemsToMarkdown(x, level + 1, true)}\n"
              case UnorderedList(itemsLvl2) => val x = itemsLvl2; list + s"${listItemsToMarkdown(x, level + 1, false)}\n"
              case Paragraph(inl) => list + s"${"\t"*level}* ${inlineToMarkdown(inl)}\n"
              case block => list + s"${"\t"*level}* ${blockToMarkdown(block)}\n"
            }
          }
        }
      }

      def blockToMarkdown(block: Block): String = {
        (block match {
          case Title(in, 1)  => s"# ${inlineToMarkdown(in)}"
          case Title(in, 2)  => s"## ${inlineToMarkdown(in)}"
          case Title(in, 3)  => s"### ${inlineToMarkdown(in)}"
          case Title(in, _)  => s"#### ${inlineToMarkdown(in)}"
          case Paragraph(in) => s"${inlineToMarkdown(in)}"
          case Code(data)    => s"```scala\n$data\n```"
          case UnorderedList(items) => s"${listItemsToMarkdown(items)}"
          case OrderedList(items, listStyle) => s"${listItemsToMarkdown(items, ordered=true)}"
          case DefinitionList(items) =>
            s"${items map { case (t, d) => s"${inlineToMarkdown(t)}\n: ${blockToMarkdown(d)}" } }" //Not widely supported
          case HorizontalRule() =>
            "***"
        }) +
        "\n"
      }

      bodyToMarkdown(body)
    }
  }

  case class InlineToMarkdown(origin: Representation) {
    def apply(inl: Inline) = toMarkdown(inl)

    def relativePath(target: Representation) =
      util.traversing.relativePath(origin, target)

    def toMarkdown(inl: Inline): String = inl match {
      case Chain(items)     => (items map toMarkdown).mkString
      case Italic(in)       => s"*${toMarkdown(in)}*"
      case Bold(in)         => s"**${toMarkdown(in)}**"
      case Underline(in)    => s"__${toMarkdown(in)}__"
      case Superscript(in)  => s"${toMarkdown(in)}" //No Markdown equival
      case Subscript(in)    => s"${toMarkdown(in) }" //No Markdown equivalent
      case Link(raw, title) => s"[${toMarkdown(title)}]($raw)"
      case Monospace(in)    => s"`${toMarkdown(in)}`"
      case Text(text)       => text
      case Summary(in)      => toMarkdown(in)
      case HtmlTag(tag)     => tag
      case RepresentationLink(target, link) => enityLinktoMarkdown(target, link)
    }

    def enityLinktoMarkdown(target: Inline, link: LinkTo) = link match {
      case Tooltip(_) => toMarkdown(target)
      case LinkToExternal(n, url) => s"[$n]($url)"
      case LinkToRepresentation(t: Representation) => t match {
        // Representation is a package member
        case e: Representation with Members =>
          s"[${toMarkdown(target)}](${relativePath(t)}.md)"
        // Representation is a Val / Def
        case x => x.parentRepresentation.fold(toMarkdown(target)) { xpar =>
          s"[${toMarkdown(target)}](${relativePath(xpar)}.md#${x.name})"
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy