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

laika.io.InputTree.scala Maven / Gradle / Ivy

/*
 * Copyright 2013-2016 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.io

import java.io.File

import laika.ast.{DocumentType, Path}

import scala.io.Codec

/** Represents a tree structure of Inputs, abstracting over various types of IO resources. 
 *  
 *  While the default implementations wrap the structure of directories in the file system,
 *  other implementations may build an entirely virtual input tree structure. 
 * 
 *  @author Jens Halm
 */
trait InputTree {


  /** The local name of the tree represented by this tree.
   */
  lazy val name: String = path.name

  /** The full path of the tree represented by this tree.
   *  This path is always an absolute path
   *  from the root of the (virtual) input tree,
   *  therefore does not represent the filesystem
   *  path in case of file I/O.
   */
  def path: Path

  /** All inputs for configuration files
   *  on this level of the input hierarchy.
   */
  def configDocuments: Seq[Input]

  /** All inputs for markup documents
   *  that need to be parsed
   *  on this level of the input hierarchy.
   */
  def markupDocuments: Seq[Input]

  /** All inputs for dynamic files
   *  that need to be processed
   *  on this level of the input hierarchy.
   */
  def dynamicDocuments: Seq[Input]

  /** All inputs for style sheets
   *  that need to be processed
   *  on this level of the input hierarchy,
   *  mapped to their format.
   */
  def styleSheets: Map[String,Seq[Input]]

  /** All inputs for static files
   *  that need to be copied
   *  from this level of the input hierarchy.
   */
  def staticDocuments: Seq[Input]

  /** All inputs for template files
   *  on this level of the input hierarchy.
   */
  def templates: Seq[Input]

  /** All subtrees of this provider.
   */
  def subtrees: Seq[InputTree]

  /** The paths this tree has been created from
   *  or an empty list if this input tree does
   *  not originate from the file system.
   */
  def sourcePaths: Seq[String]

}


/** Factory methods for creating `InputTree` instances.
 */
object InputTree {

  type FileFilter = File => Boolean


  private class DirectoryInputTree(dirs: Seq[File], val path: Path, exclude: FileFilter, docTypeMatcher: Path => DocumentType, codec: Codec) extends InputTree {

    import DocumentType._

    private def pathFor (f: File) = path / f.getName

    private def toInput (pair: (DocumentType,File)) = Input.fromFile(pair._2, path)(codec)

    private lazy val files = {
      def filesInDir (dir: File) = dir.listFiles filter (f => f.isFile && !exclude(f))
      dirs flatMap filesInDir map (f => (docTypeMatcher(pathFor(f)), f)) groupBy (_._1) withDefaultValue Nil
    }

    private def documents (docType: DocumentType) = files(docType).map(toInput)

    lazy val configDocuments: Seq[Input] = documents(Config)

    lazy val markupDocuments: Seq[Input] = documents(Markup)

    lazy val dynamicDocuments: Seq[Input] = documents(Dynamic)

    lazy val styleSheets: Map[String, Seq[Input]] = files collect { case p@(StyleSheet(format), pairs) => (format, pairs map toInput) }

    lazy val staticDocuments: Seq[Input] = documents(Static)

    lazy val templates: Seq[Input] =  documents(Template)

    lazy val sourcePaths: Seq[String] = dirs map (_.getAbsolutePath)

    lazy val subtrees: Seq[InputTree] = {
      def subDirs (dir: File) = dir.listFiles filter (f => f.isDirectory && !exclude(f) && docTypeMatcher(pathFor(f)) != Ignored)
      val byName = (dirs flatMap subDirs groupBy (_.getName)).values
      byName map (subs => new DirectoryInputTree(subs, path / subs.head.getName, exclude, docTypeMatcher, codec)) toList
    }

  }

  /**  Creates an instance based on the current working directory, including
    *  all subdirectories.
    *
    *  @param docTypeMatcher a function determining the document type based on the path of the input
    *  @param exclude the files to exclude from processing
    *  @param codec the character encoding of the files, if not specified the platform default will be used
    */
  def forWorkingDirectory (docTypeMatcher: Path => DocumentType, exclude: FileFilter)(implicit codec: Codec): InputTree =
    forRootDirectories(Seq(new File(System.getProperty("user.dir"))), docTypeMatcher, exclude)

  /** Creates an instance based on the specified directories, including
   *  all subdirectories. The directories will be merged into a tree with a single
   *  root. If any of the specified root directories contain sub-directories with
   *  the same name, these sub-directories will be merged, too.
   *
   *  @param roots the root directories of the input tree
   *  @param docTypeMatcher a function determining the document type based on the path of the input
   *  @param exclude the files to exclude from processing
   *  @param codec the character encoding of the files, if not specified the platform default will be used
   */
  def forRootDirectories (roots: Seq[File], docTypeMatcher: Path => DocumentType, exclude: FileFilter)(implicit codec: Codec): InputTree = {
    require(roots.nonEmpty, "The specified roots sequence must contain at least one directory")
    for (root <- roots) {
      require(root.exists, s"Directory ${root.getAbsolutePath} does not exist")
      require(root.isDirectory, s"File ${root.getAbsolutePath} is not a directory")
    }

    new DirectoryInputTree(roots, Path.Root, exclude, docTypeMatcher, codec)
  }

  /** Responsible for building new InputTrees based
   *  on the specified document type matcher and codec.
   */
  trait InputTreeBuilder {
    def build (docTypeMatcher: Path => DocumentType): InputTree
  }
  
  /** A filter that selects files that are hidden according to `java.io.File.isHidden`.
   */
  val hiddenFileFilter: FileFilter = file => file.isHidden && file.getName != "."

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy