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

ammonite.ops.FileOps.scala Maven / Gradle / Ivy

There is a newer version: 0.8.3-3
Show newest version
/**
 * Basic operations that take place on files. Intended to be
 * both light enough to use from the command line as well as
 * powerful and flexible enough to use in real applications to
 * perform filesystem operations
 */
package ammonite.ops

import java.io.{File, InputStream, OutputStream}
import java.nio.charset.Charset
import java.nio.file._
import java.util.Objects

import acyclic.file

import scala.io.Codec


object Internals{


  trait Mover{
    def check: Boolean
    def apply(t: PartialFunction[String, String])(from: Path) = {
      if (check || t.isDefinedAt(from.last)){
        val dest = from/RelPath.up/t(from.last)
        new File(from.toString).renameTo(new File(dest.toString))
      }
    }
    def *(t: PartialFunction[Path, Path])(from: Path) = {
      if (check || t.isDefinedAt(from)) {
        val dest = t(from)
        mkdir(dest/RelPath.up)
        new File(from.toString).renameTo(new File(t(from).toString))
      }
    }
  }


  class Writable(val writeableData: Iterator[Array[Byte]])

  object Writable{
    implicit def WritableString(s: String) = new Writable(
      Iterator(s.getBytes(java.nio.charset.StandardCharsets.UTF_8))
    )
    implicit def WritableBytes(a: Array[Byte]) = new Writable(Iterator(a))
    implicit def WritableArray[T](a: Array[T])(implicit f: T => Writable) = {
      new Writable(
        a.iterator.map(f(_).writeableData).flatten
      )
    }
    implicit def WritableTraversable[T](a: Traversable[T])(implicit f: T => Writable) = {
      new Writable(
        a.toIterator.map(f(_).writeableData).flatten
      )
    }
    implicit def WritableIterator[T](a: Iterator[T])(implicit f: T => Writable) = {
      new Writable(a.map(f(_).writeableData).flatten)
    }
  }
}

/**
 * An [[Callable1]] that returns a Seq[R], but can also do so
 * lazily (Iterator[R]) via `op.iter! arg`. You can then use
 * the iterator however you wish
 */
trait StreamableOp1[T1, R, C <: Seq[R]] extends Function1[T1, C]{
  def materialize(src: T1, i: geny.Generator[R]): C
  def apply(arg: T1) = materialize(arg, iter(arg))

  /**
    * Returns a lazy [[Iterator]] instead of an eager sequence of results.
    */
  val iter: T1 => geny.Generator[R]
}


/**
 * Makes directories up to the specified path. Equivalent
 * to `mkdir -p` in bash
 */
object mkdir extends Function1[Path, Unit]{
  def apply(path: Path) = new File(path.toString).mkdirs()
}


trait CopyMove extends Function2[Path, Path, Unit]{

  /**
    * Copy or move a file into a particular folder, rather
    * than into a particular path
    */
  object into extends Function2[Path, Path, Unit]{
    def apply(from: Path, to: Path) = {
      CopyMove.this(from, to/from.last)
    }
  }

  /**
    * Copy or move a file, stomping over anything
    * that may have been there before
    */
  object over extends Function2[Path, Path, Unit]{
    def apply(from: Path, to: Path) = {
      rm(to)
      CopyMove.this(from, to)
    }
  }
}

/**
 * Moves a file or folder from one place to another.
 *
 * Creates any necessary directories
 */
object mv extends Function2[Path, Path, Unit] with Internals.Mover with CopyMove{
  def apply(from: Path, to: Path) = {
    require(
      !to.startsWith(from),
      s"Can't move a directory into itself: $to is inside $from"
    )
    java.nio.file.Files.move(from.toNIO, to.toNIO)
  }


  def check = false

  object all extends Internals.Mover{
    def check = true
  }
}

/**
 * Copies a file or folder from one place to another.
 * Creates any necessary directories, and copies folders
 * recursively.
 */
object cp extends Function2[Path, Path, Unit] with CopyMove{
  def apply(from: Path, to: Path) = {
    require(
      !to.startsWith(from),
      s"Can't copy a directory into itself: $to is inside $from"
    )
    def copyOne(p: Path) = {
      Files.copy(p.toNIO, (to/(p relativeTo from)).toNIO)
    }

    copyOne(from)
    if (stat(from).isDir) Extensions.FilterMapExt(ls.rec! from) | copyOne
  }

}

/**
 * Roughly equivalent to bash's `rm -rf`. Deletes
 * any files or folders in the target path, or
 * does nothing if there aren't any
 */
object rm extends Function1[Path, Unit]{
  def apply(target: Path) = {
    require(
      target.segments.nonEmpty,
      s"Cannot rm a root directory: $target"
    )
    // Emulate `rm -rf` functionality by ignoring non-existent files
    val files =
      try ls.rec(target)
      catch {
        case e: NoSuchFileException => Nil
        case e: NotDirectoryException => Nil
      }

    files.toArray
         .reverseIterator
         .foreach(p => new File(p.toString).delete())
    new File(target.toString).delete
  }
}

/**
 * A specialized Seq[Path] used to provide better a better pretty-printed
 * experience
 */
case class LsSeq(base: Path, listed: RelPath*) extends Seq[Path]{
  def length = listed.length
  def apply(idx: Int) = base/listed.apply(idx)
  def iterator = listed.iterator.map(base/)
}

trait ImplicitOp[V] extends Function1[Path, V]{
  /**
   * Make the common case of looking around the current directory fast by
   * letting the user omit the argument if there's one in scope
   */
  def !(implicit arg: Path): V = apply(arg)
}
/**
  * List the files and folders in a directory. Can be called with `.iter`
  * to return an iterator, or `.rec` to recursively list everything in
  * subdirectories. `.rec` is a [[ls.Walker]] which means that apart from
  * straight-forwardly listing everything, you can pass in a `skip` predicate
  * to cause your recursion to skip certain files or folders.
  */
object ls extends StreamableOp1[Path, Path, LsSeq] with ImplicitOp[LsSeq]{
  def materialize(src: Path, i: geny.Generator[Path]) =
    new LsSeq(src, i.map(_ relativeTo src).toVector.sorted:_*)


  object iter extends (Path => geny.Generator[Path]){
    def apply(arg: Path) = geny.Generator.selfClosing{
      val dirStream = Files.newDirectoryStream(arg.toNIO)
      import collection.JavaConverters._
      (dirStream.iterator().asScala.map(Path(_)), () => dirStream.close())
    }
  }

  object rec extends Walker(){
    def apply(skip: Path => Boolean = _ => false,
              preOrder: Boolean = false) = Walker(skip, preOrder)
  }

  /**
    * Walks a directory recursively and returns a [[LsSeq]] of all its contents.
    *
    * @param skip Skip certain files or folders from appearing in the output.
    *             If you skip a folder, its entire subtree is ignored
    * @param preOrder Whether you want a folder to appear before or after its
    *                 contents in the final sequence. e.g. if you're deleting
    *                 them recursively you want it to be false so the folder
    *                 gets deleted last, but if you're copying them recursively
    *                 you want `preOrder` to be `true` so the folder gets
    *                 created first.
    */
  case class Walker(skip: Path => Boolean = _ => false,
                    preOrder: Boolean = false)
  extends StreamableOp1[Path, Path, LsSeq] with ImplicitOp[LsSeq]{

    def materialize(src: Path, i: geny.Generator[Path]) = ls.this.materialize(src, i)
    def recursiveListFiles(p: Path): geny.Generator[Path] = {
      def these = ls.iter(p)
      implicit class withFilterable[T](x: geny.Generator[T]){
        def withFilter(p: T => Boolean) = x.filter(p)
      }
      for{
        thing <- these
        if !skip(thing)
        sub <- {
          if (!stat(thing).isDir) geny.Generator(thing)
          else{
            val children = recursiveListFiles(thing)
            if (preOrder) geny.Generator(thing) ++ children
            else children ++ geny.Generator(thing)
          }
        }
      } yield sub
    }
    object iter extends (Path => geny.Generator[Path]){
      def apply(arg: Path) = recursiveListFiles(arg)
    }

  }
}

/**
 * Write some data to a file. This can be a String, an Array[Byte], or a
 * Seq[String] which is treated as consecutive lines. By default, this
 * fails if a file already exists at the target location. Use [[write.over]]
 * or [[write.append]] if you want to over-write it or add to what's already
 * there.
 */
object write extends Function2[Path, Internals.Writable, Unit]{
  /**
    * Performs the actual opening and writing to a file. Basically cribbed
    * from `java.nio.file.Files.write` so we could re-use it properly for
    * different combinations of flags and all sorts of [[Internals.Writable]]s
    */
  def write(target: Path, data: Internals.Writable, flags: StandardOpenOption*) = {

    val out = Files.newOutputStream(target.toNIO, flags:_*)
    try {
      for(bytes <- data.writeableData){
        val len: Int = bytes.length
        var rem: Int = len
        while (rem > 0) {
          val n: Int = Math.min(rem, 8192)
          out.write(bytes, len - rem, n)
          rem -= n
        }
      }
    } finally {
      if (out != null) out.close()
    }

  }
  def apply(target: Path, data: Internals.Writable) = {
    mkdir(target/RelPath.up)
    write(target, data, StandardOpenOption.CREATE_NEW)
  }

  /**
   * Identical to [[write]], except if the file already exists,
   * appends to the file instead of error-ing out
   */
  object append extends Function2[Path, Internals.Writable, Unit]{
    def apply(target: Path, data: Internals.Writable) = {
      mkdir(target/RelPath.up)
      write(target, data, StandardOpenOption.CREATE, StandardOpenOption.APPEND)
    }
  }
  /**
   * Identical to [[write]], except if the file already exists,
   * replaces the file instead of error-ing out
   */
  object over extends Function2[Path, Internals.Writable, Unit]{
    def apply(target: Path, data: Internals.Writable) = {
      mkdir(target/RelPath.up)
      write(target, data, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
    }
  }
}


/**
 * Reads a file into memory, either as a String,
 * as (read.lines(...): Seq[String]), or as (read.bytes(...): Array[Byte]).
 */
object read extends Function1[Readable, String]{
  def getInputStream(p: Readable) = p.getInputStream()

//  def apply(arg: InputPath) = new String(arg.getBytes, Charset.forName("UTF-8"))
  def apply(arg: Readable) = apply(arg, java.nio.charset.StandardCharsets.UTF_8)
  def apply(arg: Readable, charSet: Codec) = new String(arg.getBytes, charSet.charSet)

  object lines extends StreamableOp1[Readable, String, Vector[String]]{
    def materialize(src: Readable, i: geny.Generator[String]) = i.toVector

    object iter extends (Readable => geny.Generator[String]){
      def apply(arg: Readable) = arg.getLineIterator(java.nio.charset.StandardCharsets.UTF_8)

      def apply(arg: Readable, charSet: Codec) = arg.getLineIterator(charSet)
    }

    def apply(arg: Readable, charSet: Codec) = arg.getLines(charSet)
    override def apply(arg: Readable) = apply(arg, java.nio.charset.StandardCharsets.UTF_8)
  }
  object bytes extends Function1[Readable, Array[Byte]]{
    def apply(arg: Readable) = arg.getBytes
  }
}

/**
 * Checks if a file or folder exists at the given path.
 */
object exists extends Function1[Path, Boolean]{
  def apply(p: Path) = Files.exists(Paths.get(p.toString))
}

//object chmod extends Function2[Path, Unit, Unit]{
//  def apply(arg1: Path, arg2: Unit) = ???
//}
//object chgrp extends Function2[Path, Unit, Unit]{
//  def apply(arg1: Path, arg2: Unit) = ???
//}
//object chown extends Function2[Path, Unit, Unit]{
//  def apply(arg1: Path, arg2: Unit) = ???
//}
//object ps extends Function1[Unit, Unit]{
//  def apply(arg: Unit): Unit = ???
//  object tree extends Function1[Unit, Unit]{
//    def apply(arg: Unit): Unit = ???
//  }
//}



/**
 * Kills the given process with the given signal, e.g.
 * `kill(9)! pid`
 */
case class kill(signal: Int)(implicit wd: Path) extends Function1[Int, CommandResult]{
  def apply(pid: Int): CommandResult = {
    Shellout.%%('kill, "-" + signal, pid.toString)
  }
}

/**
  * Creates a hardlink between two paths. Use `.s(src, dest)` to create a
  * symlink
  */
object ln extends Function2[Path, Path, Unit]{
  def apply(src: Path, dest: Path) = {
    Files.createLink(Paths.get(dest.toString), Paths.get(src.toString))
  }
  object s extends Function2[Path, Path, Unit]{
    def apply(src: Path, dest: Path) = {
      Files.createSymbolicLink(Paths.get(dest.toString), Paths.get(src.toString))
    }
  }
}

/*object free{
  def memory: Long = ???
  def disk: Long = ???
}
object process{
  def pid: Long = ???
  def pgrp: Long = ???
}
object system{
  object uname{
    def sysname: String = ???
    def nodename: String = ???
    def release: String = ???
    def version: String = ???
    def machine: String = ???
  }
}*/




© 2015 - 2024 Weber Informatics LLC | Privacy Policy