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

laika.io.model.FilePath.scala Maven / Gradle / Ivy

/*
* Copyright 2012-2022 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.model

import cats.data.NonEmptyChain
import laika.ast.Path.Root
import laika.ast.{GenericPath, Path, RelativePath, SegmentedPath}
import laika.collection.TransitionalCollectionOps.JIteratorWrapper

import java.nio.file.{InvalidPathException, Paths}

/** Represents an absolute path on the file system, pointing to a file or directory that may or may not exist.
  * Relative paths are interpreted as relative to the current working directory.
  * 
  * This type has a lot of shared API with the `VirtualPath` abstraction in `laika-core` via their
  * common super-trait `GenericPath`.
  * Like the virtual path API it comes with convenience methods for querying and modifying
  * suffix and fragment components, a common requirement in processing internal links for example.
  * 
  * However, it differs in three ways from the virtual path abstraction: first, semantically,
  * as the latter is never intended to represent an actual file
  * and instead just describes a tree structure of inputs (and AST documents after parsing).
  * 
  * Secondly, this type comes with additional APIs to convert to and from other path representations,
  * namely `java.io.File`, `java.nio.file.Path` and `fs2.io.file.Path`.
  *
  * And finally, the `toString` method for this type returns a platform-dependent string representation
  * by using the file separator of the underlying file system.
  * Laika's virtual path abstractions on the other hand always uses a forward slash `/` as the separator.
  * 
  * Having a dedicated file path abstraction also helps with type signatures for methods in Laika's APIs 
  * that accept two path arguments: a file path and a virtual path indicating the mount point 
  * (at which the specified file is supposed to be inserted into the virtual tree).
  * 
  * @author Jens Halm
  */
class FilePath private (private val root: Option[String], private[FilePath] val underlying: Path) extends GenericPath {

  type Self = FilePath
  
  override val basename: String = underlying.basename
  
  def name: String = underlying.name

  def suffix: Option[String] = underlying.suffix

  def fragment: Option[String] = underlying.fragment

  protected def copyWith (basename: String, suffix: Option[String], fragment: Option[String]): FilePath =
    underlying match {
      case Root => this
      case sp: SegmentedPath => new FilePath(root, sp.copy(
        segments = NonEmptyChain.fromChainAppend(sp.segments.init, basename),
        suffix = suffix,
        fragment = fragment
      ))
    }

  override def / (name: String): FilePath = this / FilePath.parse(name).underlying.relative

  def / (path: RelativePath): FilePath = new FilePath(root, underlying / path)

  /** Indicates whether this path is absolute.
    * Relative paths are interpreted relative to the current working directory 
    * during a transformation.
    */
  val isAbsolute: Boolean = root.nonEmpty 

  /** Converts this `FilePath` to a `java.nio.file.Path`.
    */
  def toNioPath: java.nio.file.Path = underlying match {
    case Root => Paths.get(root.getOrElse(""))
    case sp: SegmentedPath => 
      val last = sp.name + fragment.fold("")("#" + _)
      val segments = root.toList ++: sp.segments.init.append(last).toList
      Paths.get(segments.head, segments.tail:_*)
  }

  /** Converts this `FilePath` to an `fs2.io.file.Path`.
    */
  def toFS2Path: fs2.io.file.Path = fs2.io.file.Path.fromNioPath(toNioPath)

  /** Converts this `FilePath` to an `java.io.File` instance.
    */
  def toJavaFile: java.io.File = toNioPath.toFile
  
  override def toString: String = toNioPath.toString

  override def equals (other: Any): Boolean = other match {
    case fp: FilePath => fp.root == root && fp.underlying == underlying
    case _ => false
  }

  override def hashCode (): Int = underlying.hashCode()
  
}

/** Companion for parsing path strings or creating `FilePath` instances from other
  * path representations.
  */
object FilePath {

  /** Creates a new `FilePath` from the specified NIO path after normalizing it.
    */
  def fromNioPath (path: java.nio.file.Path): FilePath = {
    val root = Option(path.getRoot).map(_.toString)
    val segments = JIteratorWrapper(path.normalize().iterator()).toList.map(_.toString)
    new FilePath(root, Path.apply(segments))
  }

  /** Creates a new `FilePath` from the specified fs2 path after normalizing it.
    */
  def fromFS2Path (path: fs2.io.file.Path): FilePath = fromNioPath(path.toNioPath)

  /** Creates a new `FilePath` from the specified File instance after normalizing its path.
    */
  def fromJavaFile (file: java.io.File): FilePath = fromNioPath(file.toPath)

  /** Creates a new `FilePath` by parsing the specified path string in a platform-specific manner.
    */
  def parse (path: String): FilePath = fromNioPath(Paths.get(path))
  
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy