Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Reopen] Read args from file, merge args and pass to the cmd. #308

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ lazy val zioCli = crossProject(JSPlatform, JVMPlatform, NativePlatform)
"dev.zio" %%% "zio-json" % "0.6.2",
"dev.zio" %%% "zio-streams" % zioVersion,
"dev.zio" %%% "zio-test" % zioVersion % Test,
"dev.zio" %%% "zio-test-sbt" % zioVersion % Test
"dev.zio" %%% "zio-test-sbt" % zioVersion % Test,
"dev.zio" %% "zio-nio" % "2.0.0"
)
)
.jvmSettings(
Expand Down
89 changes: 76 additions & 13 deletions zio-cli/shared/src/main/scala/zio/cli/CliApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import zio.cli.completion.{Completion, CompletionScript}
import zio.cli.figlet.FigFont

import scala.annotation.tailrec
import zio.nio.file.{Files, Path}
import java.io.IOException

/**
* A `CliApp[R, E]` is a complete description of a command-line application, which requires environment `R`, and may
Expand Down Expand Up @@ -66,6 +68,59 @@ object CliApp {
def printDocs(helpDoc: HelpDoc): UIO[Unit] =
printLine(helpDoc.toPlaintext(80)).!

def checkAndGetOptionsFilePaths(topLevelCommand: String): Task[List[String]] = {
val filename = s".$topLevelCommand"
val cwd = java.lang.System.getProperty("user.dir")
val homeDirOpt = java.lang.System.getProperty("user.home")

def parentPaths(path: String): List[String] = {
val parts = path.split(java.io.File.separatorChar).filterNot(_.isEmpty)
(0 to parts.length)
.map(i => s"${java.io.File.separatorChar}${parts.take(i).mkString(java.io.File.separator)}")
.toList
}

val paths = parentPaths(cwd)
val pathsToCheck = homeDirOpt :: paths

// Use ZIO to filter the paths
ZIO
.foreach(pathsToCheck) { path =>
Files.exists(Path(path, filename))
}
.map(_.zip(pathsToCheck).collect { case (exists, path) if exists => path })
}

// Merges a list of options, removing any duplicate keys.
// If there are options with the same keys but different values, it will use the value from the last option in the
// list.
def mergeOptionsBasedOnPriority(options: List[String]): List[String] = {
val mergedOptions = options.flatMap { opt =>
opt.split('=') match {
case Array(key) => Some(key -> None)
case Array(key, value) => Some(key -> value)
case _ =>
None // handles the case when there isn't exactly one '=' in the string
}
}.toMap.toList.map {
case (key, None) => key
case (key, value) => s"$key=$value"
}

mergedOptions
}

def loadOptionsFromFile(topLevelCommand: String): ZIO[Any, IOException, List[String]] =
checkAndGetOptionsFilePaths(topLevelCommand).flatMap { filePaths =>
ZIO.foreach(filePaths) { filePath =>
readFileAsString(Path(filePath, s".$topLevelCommand"))
}
}.map(_.flatten).refineToOrDie[IOException]

def readFileAsString(path: zio.nio.file.Path): Task[List[String]] =
Files
.readAllLines(path)

def run(args: List[String]): ZIO[R, CliError[E], Option[A]] = {
def executeBuiltIn(builtInOption: BuiltInOption): ZIO[R, CliError[E], Option[A]] =
builtInOption match {
Expand Down Expand Up @@ -128,19 +183,27 @@ object CliApp {
case Command.Subcommands(parent, _) => prefix(parent)
}

self.command
.parse(prefix(self.command) ++ args, self.config)
.foldZIO(
e => printDocs(e.error) *> ZIO.fail(CliError.Parsing(e)),
{
case CommandDirective.UserDefined(_, value) =>
self.execute(value).map(Some(_)).mapError(CliError.Execution(_))
case CommandDirective.BuiltIn(x) =>
executeBuiltIn(x).catchSome { case err @ CliError.Parsing(e) =>
printDocs(e.error) *> ZIO.fail(err)
}
}
)
// Reading args from config files and combining with provided args
val combinedArgs: ZIO[R, CliError[E], List[String]] =
loadOptionsFromFile(self.command.names.head).flatMap { configArgs =>
ZIO.succeed(configArgs ++ args)
}.mapError(e => CliError.IO(e)) // Convert any IO errors into CliError.IO

combinedArgs.flatMap { allArgs =>
self.command
.parse(prefix(self.command) ++ allArgs, self.config)
.foldZIO(
e => printDocs(e.error) *> ZIO.fail(CliError.Parsing(e)),
{
case CommandDirective.UserDefined(_, value) =>
self.execute(value).map(Some(_)).mapError(CliError.Execution(_))
case CommandDirective.BuiltIn(x) =>
executeBuiltIn(x).catchSome { case err @ CliError.Parsing(e) =>
printDocs(e.error) *> ZIO.fail(err)
}
}
)
}
}

override def flatMap[R1 <: R, E1 >: E, B](f: A => ZIO[R1, E1, B]): CliApp[R1, E1, B] =
Expand Down
Loading