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

replpp.shaded.os.Model.scala Maven / Gradle / Ivy

There is a newer version: 0.1.98
Show newest version
package replpp.shaded
package os

import java.nio.charset.StandardCharsets
import java.nio.file.{Files, LinkOption, Paths}
import java.nio.file.attribute._

import scala.io.Codec
import scala.language.implicitConversions
import scala.util.Try

/**
 * Simple enum with the possible filesystem objects a path can resolve to
 */
sealed trait FileType
object FileType {
  case object File extends FileType
  case object Dir extends FileType
  case object SymLink extends FileType
  case object Other extends FileType
}
object PermSet {
  implicit def fromSet(value: java.util.Set[PosixFilePermission]): PermSet = {
    var total = 0
    import PosixFilePermission._
    def add(perm: PosixFilePermission) = {
      if (value.contains(perm)) total += permToMask(perm)
    }
    add(OWNER_READ)
    add(OWNER_WRITE)
    add(OWNER_EXECUTE)
    add(GROUP_READ)
    add(GROUP_WRITE)
    add(GROUP_EXECUTE)
    add(OTHERS_READ)
    add(OTHERS_WRITE)
    add(OTHERS_EXECUTE)

    new PermSet(total)
  }

  /**
   * Parses a `rwx-wxr-w` string into a [[PermSet]]
   */
  implicit def fromString(arg: String): PermSet = {
    require(
      arg.length == 9,
      "Invalid permissions string: must be length 9, not " + arg.length
    )
    import PosixFilePermission._
    var perms = 0
    def add(perm: PosixFilePermission) = {
      val i = permToOffset(perm)
      val expected = permToChar(perm)
      if (arg(i) == expected) perms |= permToMask(perm)
      else if (arg(i) != '-') {
        throw new Exception(
          "Invalid permissions string: unknown character [" + arg(i) + "] " +
            "at index " + i + ". Must be [-] or [" + expected + "]."
        )
      }
    }
    add(OWNER_READ)
    add(OWNER_WRITE)
    add(OWNER_EXECUTE)
    add(GROUP_READ)
    add(GROUP_WRITE)
    add(GROUP_EXECUTE)
    add(OTHERS_READ)
    add(OTHERS_WRITE)
    add(OTHERS_EXECUTE)
    new PermSet(perms)
  }

  /**
   * Parses a 0x777 integer into a [[PermSet]]
   */
  implicit def fromInt(value: Int): PermSet = new PermSet(value)

  def permToMask(elem: PosixFilePermission) = 256 >> permToOffset(elem)
  def permToChar(elem: PosixFilePermission) = elem match {
    case PosixFilePermission.OWNER_READ => 'r'
    case PosixFilePermission.OWNER_WRITE => 'w'
    case PosixFilePermission.OWNER_EXECUTE => 'x'
    case PosixFilePermission.GROUP_READ => 'r'
    case PosixFilePermission.GROUP_WRITE => 'w'
    case PosixFilePermission.GROUP_EXECUTE => 'x'
    case PosixFilePermission.OTHERS_READ => 'r'
    case PosixFilePermission.OTHERS_WRITE => 'w'
    case PosixFilePermission.OTHERS_EXECUTE => 'x'
  }
  def permToOffset(elem: PosixFilePermission) = elem match {
    case PosixFilePermission.OWNER_READ => 0
    case PosixFilePermission.OWNER_WRITE => 1
    case PosixFilePermission.OWNER_EXECUTE => 2
    case PosixFilePermission.GROUP_READ => 3
    case PosixFilePermission.GROUP_WRITE => 4
    case PosixFilePermission.GROUP_EXECUTE => 5
    case PosixFilePermission.OTHERS_READ => 6
    case PosixFilePermission.OTHERS_WRITE => 7
    case PosixFilePermission.OTHERS_EXECUTE => 8
  }
}

/**
 * A set of permissions; can be converted easily to the rw-rwx-r-x form via
 * [[toString]], or to a set of [[PosixFilePermission]]s via [[toSet]] and the
 * other way via `PermSet.fromString`/`PermSet.fromSet`
 */
case class PermSet(value: Int) {
  def contains(elem: PosixFilePermission) = (PermSet.permToMask(elem) & value) != 0
  def +(elem: PosixFilePermission) = new PermSet(value | PermSet.permToMask(elem))
  def ++(other: PermSet) = new PermSet(value | other.value)

  def -(elem: PosixFilePermission) = new PermSet(value & (~PermSet.permToMask(elem)))
  def --(other: PermSet) = new PermSet(value & (~other.value))

  def toInt(): Int = value

  def toSet(): java.util.Set[PosixFilePermission] = {
    import PosixFilePermission._
    val perms = new java.util.HashSet[PosixFilePermission]()
    def add(perm: PosixFilePermission) = {
      if ((value & PermSet.permToMask(perm)) != 0) perms.add(perm)
    }
    add(OWNER_READ)
    add(OWNER_WRITE)
    add(OWNER_EXECUTE)
    add(GROUP_READ)
    add(GROUP_WRITE)
    add(GROUP_EXECUTE)
    add(OTHERS_READ)
    add(OTHERS_WRITE)
    add(OTHERS_EXECUTE)
    perms
  }

  override def toString() = {
    import PosixFilePermission._
    def add(perm: PosixFilePermission) = {
      val c = PermSet.permToChar(perm)
      if ((PermSet.permToMask(perm) & value) != 0) c else '-'
    }
    new String(
      Array[Char](
        add(OWNER_READ),
        add(OWNER_WRITE),
        add(OWNER_EXECUTE),
        add(GROUP_READ),
        add(GROUP_WRITE),
        add(GROUP_EXECUTE),
        add(OTHERS_READ),
        add(OTHERS_WRITE),
        add(OTHERS_EXECUTE)
      )
    )
  }
}

/**
 * Contains the accumulated output for the invocation of a subprocess command.
 *
 * Apart from the exit code, the primary data-structure is a sequence of byte
 * chunks, tagged with [[Left]] for stdout and [[Right]] for stderr. This is
 * interleaved roughly in the order it was emitted by the subprocess, and
 * reflects what a user would have see if the subprocess was run manually.
 *
 * Derived from that, is the aggregate `out` and `err` [[StreamValue]]s,
 * wrapping stdout/stderr respectively, and providing convenient access to
 * the aggregate output of each stream, as bytes or strings or lines.
 */
case class CommandResult(
    command: Seq[String],
    exitCode: Int,
    chunks: Seq[Either[geny.Bytes, geny.Bytes]]
) {

  /**
   * The standard output and error of the executed command, exposed in a
   * number of ways for convenient access
   */
  val (out, err) = {
    val outChunks = collection.mutable.Buffer.empty[geny.Bytes]
    val errChunks = collection.mutable.Buffer.empty[geny.Bytes]
    chunks.foreach {
      case Left(s) => outChunks.append(s)
      case Right(s) => errChunks.append(s)
    }
    (geny.ByteData.Chunks(outChunks.toSeq), geny.ByteData.Chunks(errChunks.toSeq))
  }

  override def toString() = {
    val denoteMoreCommandChunks = if (command.length == 1) "" else "…"
    s"Result of ${command.head}${denoteMoreCommandChunks}: $exitCode\n" +
      chunks.iterator
        .collect {
          case Left(s) => s
          case Right(s) => s
        }
        .map(x => new String(x.array))
        .mkString
  }
}

/**
 * Thrown when a shellout command results in a non-zero exit code.
 *
 * Doesn't contain any additional information apart from the [[CommandResult]]
 * that is normally returned, but ensures that failures in subprocesses happen
 * loudly and won't get ignored unless intentionally caught
 */
case class SubprocessException(result: CommandResult) extends Exception(result.toString)

/**
 * An implicit wrapper defining the things that can
 * be "interpolated" directly into a subprocess call.
 */
case class Shellable(value: Seq[String])
object Shellable {
  implicit def StringShellable(s: String): Shellable = Shellable(Seq(s))
  implicit def CharSequenceShellable(cs: CharSequence): Shellable = Shellable(Seq(cs.toString))

  implicit def SymbolShellable(s: Symbol): Shellable = Shellable(Seq(s.name))
  implicit def PathShellable(s: Path): Shellable = Shellable(Seq(s.toString))
  implicit def RelPathShellable(s: RelPath): Shellable = Shellable(Seq(s.toString))
  implicit def NumericShellable[T: Numeric](s: T): Shellable = Shellable(Seq(s.toString))

  implicit def OptionShellable[T](s: Option[T])(implicit f: T => Shellable): Shellable =
    Shellable(s.toSeq.flatMap(f(_).value))

  implicit def IterableShellable[T](s: Iterable[T])(implicit f: T => Shellable): Shellable =
    Shellable(s.toSeq.flatMap(f(_).value))

  implicit def ArrayShellable[T](s: Array[T])(implicit f: T => Shellable): Shellable =
    Shellable(s.toIndexedSeq.flatMap(f(_).value))
}

/**
 * The result from doing an system `stat` on a particular path.
 *
 * Note: ctime is not same as ctime (Change Time) in `stat`,
 *       it is creation time maybe fall back to mtime if system not supported it.
 *
 * Created via `stat! filePath`.
 *
 * If you want more information, use `stat.full`
 */
case class StatInfo(
    size: Long,
    mtime: FileTime,
    ctime: FileTime,
    atime: FileTime,
    fileType: FileType
) {
  def isDir = fileType == FileType.Dir
  def isSymLink = fileType == FileType.SymLink
  def isFile = fileType == FileType.File
}
object StatInfo {

  def make(attrs: BasicFileAttributes) = {
    new StatInfo(
      attrs.size(),
      attrs.lastModifiedTime(),
      attrs.creationTime(),
      attrs.lastAccessTime(),
      if (attrs.isRegularFile) FileType.File
      else if (attrs.isDirectory) FileType.Dir
      else if (attrs.isSymbolicLink) FileType.SymLink
      else if (attrs.isOther) FileType.Other
      else ???
    )
  }
}

case class PosixStatInfo(owner: UserPrincipal, permissions: PermSet)
object PosixStatInfo {
  def make(posixAttrs: PosixFileAttributes) = {
    PosixStatInfo(
      posixAttrs.owner,
      PermSet.fromSet(posixAttrs.permissions)
    )
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy