Skip to content

Commit

Permalink
support for file based config
Browse files Browse the repository at this point in the history
  • Loading branch information
vivasvan1 committed Apr 18, 2024
1 parent 8b82983 commit ca2471a
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 15 deletions.
5 changes: 3 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ 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(
libraryDependencies += "dev.zio" %% "zio-process" % "0.7.1"
libraryDependencies += "dev.zio" %% "zio-process" % "0.7.1",
)
.nativeSettings(Test / fork := false)
.nativeSettings(
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

0 comments on commit ca2471a

Please sign in to comment.