<commit_before>/*
 * Copyright 2014 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.sbt

import sbt._
import Keys._
import laika.api._
import laika.io.InputProvider.Directories
import laika.io.InputProvider.InputConfigBuilder
import laika.io.OutputProvider.Directory
import laika.io.OutputProvider.OutputConfigBuilder
import laika.template.ParseTemplate
import laika.template.DefaultTemplate
import laika.tree.Documents._
import laika.tree.Elements._
import laika.directive.Directives._
import laika.parse.rst.ReStructuredText
import laika.parse.rst.{Directives=>rst}
import laika.parse.rst.TextRoles.TextRole
import laika.parse.markdown.Markdown
import laika.render.HTML
import laika.render.HTMLWriter
import laika.io.InputProvider
import laika.io.Input.LazyFileInput
import laika.io.Input
import laika.tree.ElementTraversal
import laika.parse.markdown.html.VerbatimHTML
import laika.parse.rst.ExtendedHTML
import LaikaSbtPlugin.OutputFormats

object LaikaSbtPlugin extends Plugin {

  
  object LaikaKeys {
    
    val Laika               = config("laika")
    
    val site                = taskKey[File]("Generates a static website")
    
    val generate            = inputKey[Set[File]]("Generates the specified output formats")
    
    val docTypeMatcher      = settingKey[Option[Path => DocumentType]]("Matches a path to a Laika document type")
    
    val encoding            = settingKey[String]("The character encoding")

    val strict              = settingKey[Boolean]("Indicates whether all features not part of the original Markdown or reStructuredText syntax should be switched off")
    
    val renderMessageLevel  = settingKey[Option[MessageLevel]]("The minimum level for system messages to be rendered to HTML")

    val logMessageLevel     = settingKey[Option[MessageLevel]]("The minimum level for system messages to be logged")
    
    val markdown            = settingKey[Markdown]("The parser for Markdown files")
    
    val reStructuredText    = settingKey[ReStructuredText]("The parser for reStructuredText files")

    val markupParser        = settingKey[Parse]("The parser for all text markup files")
    
    val templateParser      = settingKey[ParseTemplate]("The parser for template files")
    
    val rawContent          = settingKey[Boolean]("Indicates whether embedding of raw content (like verbatim HTML) is supported in markup")
    
    val inputTree           = taskKey[InputConfigBuilder]("The configured input tree for the parser")

    val outputTree          = taskKey[OutputConfigBuilder]("The configured output tree for the renderer")
    
    val rewriteRules        = settingKey[Seq[DocumentContext => RewriteRule]]("Custom rewrite rules to add to the standard rules")
    
    val siteRenderers       = settingKey[Seq[HTMLWriter => RenderFunction]]("Custom renderers overriding the defaults per node type")
    
    val parallel            = settingKey[Boolean]("Indicates whether parsers and renderers should run in parallel")
    
    val spanDirectives      = settingKey[Seq[Spans.Directive]]("Directives for inline markup")

    val blockDirectives     = settingKey[Seq[Blocks.Directive]]("Directives for block-level markup")

    val templateDirectives  = settingKey[Seq[Templates.Directive]]("Directives for templates")
    
    val rstSpanDirectives   = settingKey[Seq[rst.Directive[Span]]]("Inline directives for reStructuredText")
    
    val rstBlockDirectives  = settingKey[Seq[rst.Directive[Block]]]("Block directives for reStructuredText")
    
    val rstTextRoles        = settingKey[Seq[TextRole]]("Custom text roles for reStructuredText")

    val includeAPI          = settingKey[Boolean]("Indicates whether API documentation should be copied to the site")
    
    val copyAPI             = taskKey[File]("Copies the API documentation to the site")

    val packageSite         = taskKey[File]("Create a zip file of the site")
    
    
    // helping the type inferrer:
    
    def siteRenderer (f: HTMLWriter => RenderFunction) = f
    def rewriteRule (rule: RewriteRule): DocumentContext => RewriteRule = _ => rule
    def rewriteRuleFactory (factory: DocumentContext => RewriteRule) = factory
    
  }
  
  
  object LaikaPlugin {
    import LaikaKeys._
    import Tasks._
    
    val defaults: Seq[Setting[_]] = inConfig(Laika)(Seq(
        
      sourceDirectories   := Seq(sourceDirectory.value / "docs"),
      
      target              := target.value / "docs",
      
      target in site      := target.value / "site",

      target in copyAPI   := (target in site).value / "api",
      
      excludeFilter       := HiddenFileFilter,
      
      encoding            := "UTF-8",
      
      strict              := false,
      
      renderMessageLevel  := None,
      
      logMessageLevel     := Some(Warning),

      docTypeMatcher      := None,
      
      rawContent          := false,
      
      markdown            := {
                            val md = (Markdown withBlockDirectives (blockDirectives.value: _*) withSpanDirectives (spanDirectives.value: _*))
                            val md2 = if (rawContent.value) md.withVerbatimHTML else md
                            if (strict.value) md2.strict else md2
                          },

      reStructuredText    := { 
                            val rst = (ReStructuredText withLaikaBlockDirectives (blockDirectives.value: _*) withLaikaSpanDirectives 
                            (spanDirectives.value: _*) withBlockDirectives (rstBlockDirectives.value: _*) withSpanDirectives 
                            (rstSpanDirectives.value: _*) withTextRoles (rstTextRoles.value: _*))
                            val rst2 = if (rawContent.value) rst.withRawContent else rst
                            if (strict.value) rst2.strict else rst2
                          },
      
      markupParser        := (Parse as markdown.value or reStructuredText.value withoutRewrite),
      
      templateParser      := (ParseTemplate as DefaultTemplate.withDirectives(templateDirectives.value: _*)),
      
      rewriteRules        := Nil,
      
      siteRenderers       := Nil,
      
      parallel            := true,
      
      spanDirectives      := Nil,
      blockDirectives     := Nil,
      templateDirectives  := Nil,
      
      rstSpanDirectives   := Nil,
      rstBlockDirectives  := Nil,
      rstTextRoles        := Nil,
      
      includeAPI          := false,
      
      inputTree           := inputTreeTask.value,
      outputTree          := outputTreeTask.value,
      site                := siteTask.value,
      generate            := generateTask.evaluated,
      copyAPI             := copyAPITask.value,
      packageSite         := packageSiteTask.value,
      clean               := cleanTask.value,
      
      mappings in site    := sbt.Path.allSubpaths(site.value).toSeq,
      
      artifact in packageSite     := Artifact(moduleName.value, Artifact.DocType, "zip", "site"),
      artifactPath in packageSite := {
                                    val art = (artifact in packageSite).value
                                    val classifier = art.classifier map ("-"+_) getOrElse ""
                                    target.value / (art.name + "-" + projectID.value.revision + classifier + "." + art.extension)
                                  }
      
    )) :+ (cleanFiles += (target in Laika).value)
    
  }
  
  
  object Tasks {
    import LaikaKeys._
    import Def._
    
    val inputTreeTask = task {
      val builder = Directories(sourceDirectories.value, excludeFilter.value.accept)(encoding.value)
        .withTemplates(templateParser.value)
      val builder2 = if (parallel.value) builder.inParallel else builder
      docTypeMatcher.value map (builder2 withDocTypeMatcher _) getOrElse builder2
    }
    
    val outputTreeTask = task {
      val builder = Directory((target in site).value)(encoding.value)
      if (parallel.value) builder.inParallel else builder
    }
    
    val generateTask = inputTask {
    
      /*
       * TODO
       * 
       * - move to sbt 0.13.2
       * 
       * - prettyPrint renderer
       * - xsl-fo renderer (ExtendedFO for rst?)
       * - pdf renderer
       * - produce set of targets as result
       * 
       * - create individual tasks for html, prettyPrint, xslfo, pdf
       * - set defaults for targets for all formats
       * - update site task to use generate task
       * - add includePDF setting
       */
      
      
      val formats = spaceDelimited("<format>").parsed.map(OutputFormats.OutputFormat.fromString)
      if (formats.isEmpty) throw new IllegalArgumentException("At least one format must be specified")
      
      val inputs = inputTree.value.build(markupParser.value.fileSuffixes)
      
      val cacheDir = streams.value.cacheDirectory / "laika"
      val targetDir = (target in site).value // TODO - more than one target now
      
      val cached = new Cached(cacheDir)
      
      val func = cached.inputs { inReport =>
        
        val tree = { // TODO - should be lazy, but requires bug fix in sbt 0.13.2
          
          streams.value.log.info("Reading files from " + sourceDirectories.value.mkString(", "))<commit_msg>sbt plugin: add html task<commit_after>/*
 * Copyright 2014 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.sbt

import sbt._
import Keys._
import laika.api._
import laika.io.InputProvider.Directories
import laika.io.InputProvider.InputConfigBuilder
import laika.io.OutputProvider.Directory
import laika.io.OutputProvider.OutputConfigBuilder
import laika.template.ParseTemplate
import laika.template.DefaultTemplate
import laika.tree.Documents._
import laika.tree.Elements._
import laika.directive.Directives._
import laika.parse.rst.ReStructuredText
import laika.parse.rst.{Directives=>rst}
import laika.parse.rst.TextRoles.TextRole
import laika.parse.markdown.Markdown
import laika.render.HTML
import laika.render.HTMLWriter
import laika.io.InputProvider
import laika.io.Input.LazyFileInput
import laika.io.Input
import laika.tree.ElementTraversal
import laika.parse.markdown.html.VerbatimHTML
import laika.parse.rst.ExtendedHTML
import LaikaSbtPlugin.OutputFormats

object LaikaSbtPlugin extends Plugin {

  
  object LaikaKeys {
    
    val Laika               = config("laika")
    
    val site                = taskKey[File]("Generates a static website")
    
    val generate            = inputKey[Set[File]]("Generates the specified output formats")
    
    val html                = inputKey[Set[File]]("Generates HTML output")
    
    val docTypeMatcher      = settingKey[Option[Path => DocumentType]]("Matches a path to a Laika document type")
    
    val encoding            = settingKey[String]("The character encoding")

    val strict              = settingKey[Boolean]("Indicates whether all features not part of the original Markdown or reStructuredText syntax should be switched off")
    
    val renderMessageLevel  = settingKey[Option[MessageLevel]]("The minimum level for system messages to be rendered to HTML")

    val logMessageLevel     = settingKey[Option[MessageLevel]]("The minimum level for system messages to be logged")
    
    val markdown            = settingKey[Markdown]("The parser for Markdown files")
    
    val reStructuredText    = settingKey[ReStructuredText]("The parser for reStructuredText files")

    val markupParser        = settingKey[Parse]("The parser for all text markup files")
    
    val templateParser      = settingKey[ParseTemplate]("The parser for template files")
    
    val rawContent          = settingKey[Boolean]("Indicates whether embedding of raw content (like verbatim HTML) is supported in markup")
    
    val inputTree           = taskKey[InputConfigBuilder]("The configured input tree for the parser")

    val outputTree          = taskKey[OutputConfigBuilder]("The configured output tree for the renderer")
    
    val rewriteRules        = settingKey[Seq[DocumentContext => RewriteRule]]("Custom rewrite rules to add to the standard rules")
    
    val siteRenderers       = settingKey[Seq[HTMLWriter => RenderFunction]]("Custom renderers overriding the defaults per node type")
    
    val parallel            = settingKey[Boolean]("Indicates whether parsers and renderers should run in parallel")
    
    val spanDirectives      = settingKey[Seq[Spans.Directive]]("Directives for inline markup")

    val blockDirectives     = settingKey[Seq[Blocks.Directive]]("Directives for block-level markup")

    val templateDirectives  = settingKey[Seq[Templates.Directive]]("Directives for templates")
    
    val rstSpanDirectives   = settingKey[Seq[rst.Directive[Span]]]("Inline directives for reStructuredText")
    
    val rstBlockDirectives  = settingKey[Seq[rst.Directive[Block]]]("Block directives for reStructuredText")
    
    val rstTextRoles        = settingKey[Seq[TextRole]]("Custom text roles for reStructuredText")

    val includeAPI          = settingKey[Boolean]("Indicates whether API documentation should be copied to the site")
    
    val copyAPI             = taskKey[File]("Copies the API documentation to the site")

    val packageSite         = taskKey[File]("Create a zip file of the site")
    
    
    // helping the type inferrer:
    
    def siteRenderer (f: HTMLWriter => RenderFunction) = f
    def rewriteRule (rule: RewriteRule): DocumentContext => RewriteRule = _ => rule
    def rewriteRuleFactory (factory: DocumentContext => RewriteRule) = factory
    
  }
  
  
  object LaikaPlugin {
    import LaikaKeys._
    import Tasks._
    
    val defaults: Seq[Setting[_]] = inConfig(Laika)(Seq(
        
      sourceDirectories   := Seq(sourceDirectory.value / "docs"),
      
      target              := target.value / "docs",
      
      target in site      := target.value / "site",

      target in copyAPI   := (target in site).value / "api",
      
      excludeFilter       := HiddenFileFilter,
      
      encoding            := "UTF-8",
      
      strict              := false,
      
      renderMessageLevel  := None,
      
      logMessageLevel     := Some(Warning),

      docTypeMatcher      := None,
      
      rawContent          := false,
      
      markdown            := {
                            val md = (Markdown withBlockDirectives (blockDirectives.value: _*) withSpanDirectives (spanDirectives.value: _*))
                            val md2 = if (rawContent.value) md.withVerbatimHTML else md
                            if (strict.value) md2.strict else md2
                          },

      reStructuredText    := { 
                            val rst = (ReStructuredText withLaikaBlockDirectives (blockDirectives.value: _*) withLaikaSpanDirectives 
                            (spanDirectives.value: _*) withBlockDirectives (rstBlockDirectives.value: _*) withSpanDirectives 
                            (rstSpanDirectives.value: _*) withTextRoles (rstTextRoles.value: _*))
                            val rst2 = if (rawContent.value) rst.withRawContent else rst
                            if (strict.value) rst2.strict else rst2
                          },
      
      markupParser        := (Parse as markdown.value or reStructuredText.value withoutRewrite),
      
      templateParser      := (ParseTemplate as DefaultTemplate.withDirectives(templateDirectives.value: _*)),
      
      rewriteRules        := Nil,
      
      siteRenderers       := Nil,
      
      parallel            := true,
      
      spanDirectives      := Nil,
      blockDirectives     := Nil,
      templateDirectives  := Nil,
      
      rstSpanDirectives   := Nil,
      rstBlockDirectives  := Nil,
      rstTextRoles        := Nil,
      
      includeAPI          := false,
      
      inputTree           := inputTreeTask.value,
      outputTree          := outputTreeTask.value,
      site                := siteTask.value,
      generate            := generateTask.evaluated,
      html                := generateTask.fullInput(" html").evaluated,
      copyAPI             := copyAPITask.value,
      packageSite         := packageSiteTask.value,
      clean               := cleanTask.value,
      
      mappings in site    := sbt.Path.allSubpaths(site.value).toSeq,
      
      artifact in packageSite     := Artifact(moduleName.value, Artifact.DocType, "zip", "site"),
      artifactPath in packageSite := {
                                    val art = (artifact in packageSite).value
                                    val classifier = art.classifier map ("-"+_) getOrElse ""
                                    target.value / (art.name + "-" + projectID.value.revision + classifier + "." + art.extension)
                                  }
      
    )) :+ (cleanFiles += (target in Laika).value)
    
  }
  
  
  object Tasks {
    import LaikaKeys._
    import Def._
    
    val inputTreeTask = task {
      val builder = Directories(sourceDirectories.value, excludeFilter.value.accept)(encoding.value)
        .withTemplates(templateParser.value)
      val builder2 = if (parallel.value) builder.inParallel else builder
      docTypeMatcher.value map (builder2 withDocTypeMatcher _) getOrElse builder2
    }
    
    val outputTreeTask = task {
      val builder = Directory((target in site).value)(encoding.value)
      if (parallel.value) builder.inParallel else builder
    }
    
    val generateTask = inputTask {
    
      /*
       * TODO
       * 
       * - move to sbt 0.13.2
       * 
       * - prettyPrint renderer
       * - xsl-fo renderer (ExtendedFO for rst?)
       * - pdf renderer
       * - produce set of targets as result
       * 
       * - create individual tasks for prettyPrint, xslfo, pdf
       * - set defaults for targets for all formats
       * - update site task to use generate task
       * - add includePDF setting
       */
      
      
      val formats = spaceDelimited("<format>").parsed.map(OutputFormats.OutputFormat.fromString)
      if (formats.isEmpty) throw new IllegalArgumentException("At least one format must be specified")
      
      val inputs = inputTree.value.build(markupParser.value.fileSuffixes)
      
      val cacheDir = streams.value.cacheDirectory / "laika"
      val targetDir = (target in site).value // TODO - more than one target now
      
      val cached = new Cached(cacheDir)
      
      val func = cached.inputs { inReport =>
        
        val tree = { // TODO - should be lazy, but requires bug fix in sbt 0.13.2
          
          streams.value.log.info("Reading files from " + sourceDirectories.value.mkString(", "))
{'fraction_non_alphanumeric': 0.08289420348601541, 'fraction_numerical': 0.00263477908390758, 'mean_word_length': 2.2977443609022554, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '669422', 'n_tokens_mistral': 5320, 'n_tokens_neox': 5168, 'n_words': 1443}