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

org.dhallj.imports.Canonicalization.scala Maven / Gradle / Ivy

The newest version!
package org.dhallj.imports

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

import cats.{ApplicativeError, MonadError}
import cats.implicits._
import cats.effect.Sync
import org.dhallj.core.DhallException.ResolutionFailure
import org.dhallj.imports.ImportContext._

import scala.collection.JavaConverters._

object Canonicalization {

  def canonicalize[F[_]](imp: ImportContext)(implicit F: ApplicativeError[F, Throwable]): F[ImportContext] = imp match {
    case Remote(uri, headers) => F.pure(Remote(uri.normalize, headers))
    case Local(path)          => LocalFile[F](path).map(_.canonicalize.toPath).map(Local)
    case Classpath(path)      => LocalFile[F](path).map(_.canonicalize.toPath).map(Classpath)
    case i                    => F.pure(i)
  }

  def canonicalize[F[_]](parent: ImportContext, child: ImportContext)(implicit
    F: MonadError[F, Throwable]
  ): F[ImportContext] =
    parent match {
      case Remote(uri, headers) =>
        child match {
          //A transitive relative import is parsed as local but is resolved as a remote import
          // eg https://github.com/dhall-lang/dhall-lang/blob/master/Prelude/Integer/add has a local import but we still
          //need to resolve this as a remote import
          //Also note that if the path is absolute then this violates referential sanity but we handle that elsewhere
          case Local(path) =>
            if (path.isAbsolute) canonicalize[F](child)
            else canonicalize[F](Remote(uri.resolve(path.toString), headers))
          case _ => canonicalize[F](child)
        }
      case Local(path) =>
        child match {
          case Local(path2) =>
            for {
              parent <- LocalFile[F](path)
              c <- LocalFile[F](path2)
            } yield Local(parent.chain(c).canonicalize.toPath)
          case _ => canonicalize[F](child)
        }
      //TODO - determine semantics of classpath imports
      case Classpath(path) =>
        child match {
          //Also note that if the path is absolute then this violates referential sanity but we handle that elsewhere
          case Local(path2) =>
            for {
              parent <- LocalFile[F](path)
              c <- LocalFile[F](path2)
            } yield Classpath(parent.chain(c).canonicalize.toPath)
          case _ => canonicalize[F](child)
        }
      case _ => canonicalize[F](child)
    }

  private case class LocalFile(dirs: LocalDirs, filename: String) {
    def toPath: Path = {
      def toPath(l: List[String]) = "/" + l.intercalate("/")

      val s: String = dirs.ds match {
        case Nil => ""
        case l @ (h :: t) =>
          h match {
            case h if h == "." || h == ".." || h == "~" => s"$h${toPath(t)}"
            case _                                      => toPath(l)
          }
      }
      Paths.get(s"$s/$filename")
    }

    def chain(other: LocalFile): LocalFile = LocalFile.chain(this, other)

    def canonicalize: LocalFile = LocalFile.canonicalize(this)
  }

  private object LocalFile {
    def apply[F[_]](path: Path)(implicit F: ApplicativeError[F, Throwable]): F[LocalFile] =
      path.iterator().asScala.toList.map(_.toString) match {
        case Nil => F.raiseError(new ResolutionFailure("This shouldn't happen - / can't import a dhall expression"))
        case l   => F.pure(LocalFile(LocalDirs(l.take(l.length - 1)), l.last))
      }

    def canonicalize(f: LocalFile): LocalFile =
      LocalFile(f.dirs.canonicalize, f.filename.stripPrefix("\"").stripSuffix("\""))

    def chain(lhs: LocalFile, rhs: LocalFile): LocalFile = LocalFile(LocalDirs.chain(lhs.dirs, rhs.dirs), rhs.filename)
  }

  private case class LocalDirs(ds: List[String]) {
    def isRelative: Boolean = ds.nonEmpty && (ds.head == "." || ds.head == "..")

    def canonicalize: LocalDirs = LocalDirs.canonicalize(this)

    def chain(other: LocalDirs): LocalDirs = LocalDirs.chain(this, other)
  }

  private object LocalDirs {
    def chain(lhs: LocalDirs, rhs: LocalDirs): LocalDirs = if (rhs.isRelative) LocalDirs(lhs.ds ++ rhs.ds) else rhs

    def canonicalize(d: LocalDirs): LocalDirs = d.ds match {
      case Nil => d
      case l   => LocalDirs(canonicalize(l.map(_.stripPrefix("\"").stripSuffix("\""))))
    }

    def canonicalize(l: List[String]): List[String] =
      l.tail
        .foldLeft(List(l.head)) { (acc, next) =>
          next match {
            case "."  => acc
            case ".." => acc.tail
            case o    => o :: acc
          }
        }
        .reverse

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy