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

Conversation

vivasvan1
Copy link

@vivasvan1 vivasvan1 commented Apr 18, 2024

/claim #191

closes #191

Hi @jdegoes,

Hi have attempted to resolve your review comments on the previous PR

Please let me know if more changes are required.

Working Screenshots:
image

image


References:

  1. Old PR: Read args from file, merge args and pass to the cmd. #278

P.S. : I dont have permission to reopen the previous PR so created a fresh one.

@CLAassistant
Copy link

CLAassistant commented Apr 18, 2024

CLA assistant check
All committers have signed the CLA.

@vivasvan1
Copy link
Author

vivasvan1 commented Apr 18, 2024

Also could i please get provide some guidance in fixing CI/CD error. I am new to this ecosystem and unclear on what exactly the errors are.

@jdegoes
Copy link
Member

jdegoes commented Apr 19, 2024

[error] Modules were resolved with conflicting cross-version suffixes in ProjectRef(uri("file:/home/runner/work/zio-cli/zio-cli/"), "zioCliJVM"):
[error]    org.scala-lang.modules:scala-collection-compat _3, _2.13
[error] java.lang.RuntimeException: Conflicting cross-version suffixes in: org.scala-lang.modules:scala-collection-compat

This error is related to Scala JS, to use of a library scala-collection-compat. Probably version numbers for the Scala compiler(s) need updating to be consistent with each other. I would look in the build file.

Copy link
Member

@jdegoes jdegoes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Please just add a test to ensure the behavior is working correctly, and th en we can merge!

As of 19th April zio-nio doesnt support scalaJS so using java to read file and zio core for resource management
@vivasvan1
Copy link
Author

vivasvan1 commented Apr 19, 2024

Update: 22 Apr 2024:
✅ All Green)) - @jdegoes

Notes:

  • JS support can be added later.

Update 20 Apr 2024:
Tests are also initiallized. Let me know if any changes are required.


Looks good! Please just add a test to ensure the behavior is working correctly, and th en we can merge!

@jdegoes
Update 19 Apr 2024:
Hi there,

First, thank you for your review.

I have commited fix for build errors in JVM and Native.

However, errors are now only in scalaJS.
Error are only of this type:
image

I believe it is JS doesnt support java.io
If you know about this could you guide me a little?

Also, I'm working on writing test. should be done soon.

Thank you again :)

@vivasvan1 vivasvan1 requested a review from jdegoes April 22, 2024 08:12
@vivasvan1
Copy link
Author

@jdegoes
Hi there, hope this message finds you well.
Reminder: I have fixed the issues you mentioned.
Could you please let me know how to proceed?
Thanks :)

@pablf
Copy link
Member

pablf commented May 6, 2024

@vivasvan1 Hi! The tests do not cover the case of a real CliApp. Testing locally the following test suite, it seems to be failing. I don't know if there might be some other issues. Method parse of Command is the one that parse the parameters. I think that the easiest way to solve it would be to modify this method so that it accepts separately the user parameters and the file parameters and then process them knowing to which group each one belongs instead of just concatenating them.

If you could add a test suite similar to this testing the whole process of running the CliApp it would be great!

import zio._
import zio.test._
import zio.test.Assertion._
import java.nio.file.{Files, Paths, Path}
import java.io.IOException
import zio.cli._

object AlternativeSpec extends ZIOSpecDefault {

  val configFileOps: ConfigFilePlatformSpecific = ConfigFileArgsPlatformSpecific

  def spec = suite("FileBasedArgs")(
    test("failing test 1") {
      for {
        // Create Sample config files
        cwd     <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.dir")))
        _       <- createSampleConfigFiles2(cwd, "someCommand")
        cliApp = CliApp.make(
          name = "cliApp",
          version = "0",
          summary = HelpDoc.Span.empty,
          command = Command("someCommand", Options.text("opt")),
        ) {
          case text: String => ZIO.succeed(text)
        }
        res <- cliApp.run(List("--opt", "inputText"))

        // Check if the func checkAndGetOptionsFilePaths can

        _ <- cleanUpSampleConfigFiles2(cwd, "someCommand")

      } yield assertTrue(res == Some("inputText"))
    },
    test("failing test 2") {
      for {
        // Create Sample config files
        cwd     <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.dir")))
        command = "someCommandB"
        _       <- createSampleConfigFiles2(cwd, command)
        cliApp = CliApp.make(
          name = "cliApp",
          version = "0",
          summary = HelpDoc.Span.empty,
          command = Command(command, Options.text("opt"), Args.integer("num")),
        ) {
          case text => ZIO.succeed(text)
        }
        res <- cliApp.run(List("--opt", "inputText", "4"))

        // Check if the func checkAndGetOptionsFilePaths can

        _ <- cleanUpSampleConfigFiles2(cwd, command)

      } yield assertTrue(res == Some(("inputText", BigInt(4))))
    },
    test("failing test 3") {
      for {
        // Create Sample config files
        cwd     <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.dir")))
        command = "someCommandCLeft"
        command2 = "someCommandCRight"
        _       <- createSampleConfigFiles2(cwd, command)
        cliApp = CliApp.make(
          name = "cliApp",
          version = "0",
          summary = HelpDoc.Span.empty,
          command = Command(command, Options.text("opt"), Args.integer("num")) | Command(command2, Options.text("opt"), Args.integer("num")),
        ) {
          case text => ZIO.succeed(text)
        }
        res <- cliApp.run(List(command, "4"))

        
        // Check if the func checkAndGetOptionsFilePaths can

        _ <- cleanUpSampleConfigFiles2(cwd, command)

      } yield assertTrue(res == Some(("fileText", BigInt(4))))
    },
    test("failing test 4") {
      for {
        // Create Sample config files
        cwd     <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.dir")))
        command = "someCommandD"
        _       <- createSampleConfigFiles2(cwd, command)
        cliApp = CliApp.make(
          name = "cliApp",
          version = "0",
          summary = HelpDoc.Span.empty,
          command = Command(command, Args.integer("num")),
        ) {
          case text => ZIO.succeed(text)
        }
        res <- cliApp.run(List("4"))

        // Check if the func checkAndGetOptionsFilePaths can

        _ <- cleanUpSampleConfigFiles2(cwd, command)

      } yield assertTrue(res == Some(BigInt(4)))
    },
  )

  def createSampleConfigFiles2(cwd: Path, command: String = "testApp"): IO[IOException, Unit] =
    ZIO.attempt {
      Files.write(Paths.get(cwd.toString(), s".$command"), java.util.Arrays.asList("--opt=fileText"));

      ()
    }.refineToOrDie[IOException]

  def cleanUpSampleConfigFiles2(cwd: Path, command: String = "testApp"): IO[IOException, Unit] =
    ZIO.attempt {
      Files.delete(Paths.get(cwd.toString(), s".$command"));

      ()
    }.refineToOrDie[IOException]
}

@vivasvan1
Copy link
Author

vivasvan1 commented Jun 4, 2024

@pablf Hi there,

I have added the test case you asked for. Where the functionality is being tested on the full wc app.
Let me know if any futher changes are required.

I am not sure why your local is failing. Could you provide more details.
i tried to understand the parse function but as i am new to zio, scala it felt bit overwhelming.

Thanks,
Vivasvan

}
)

def createLineCountTestFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return

ZIO[Scope, IOException, Unit]

and clean itself up


def createLineCountTestFile(
cwd: Path,
file_name: String = "sample_file",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fileName

Comment on lines +95 to +96
content_home: String = "home=true",
content_cwd: String = "dir=true\nhome=false"
Copy link
Contributor

@Kalin-Rudnicki Kalin-Rudnicki Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

camel

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is it possible to remove the default arguments?
I find reading through the tests very hard to follow, and easily determine what output I would expect.

Also, I imagine that there should be multiple tests asserting expected behavior for different permutations of hierarchy overrides.

command: String = "testApp",
content_home: String = "home=true",
content_cwd: String = "dir=true\nhome=false"
): IO[IOException, Unit] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing as above, writing to file should return zio Scope, and clean itself up, doing so for a single file, and be called twice

Comment on lines +11 to +12
val cwd = java.lang.System.getProperty("user.dir")
val homeDirOpt = java.lang.System.getProperty("user.home")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See: zio.System

Comment on lines +35 to +45
lines <- ZIO
.foreach(filePaths) { filePath =>
ZIO.acquireReleaseWith(
ZIO.attempt(
Source.fromFile(Paths.get(filePath, "." + topLevelCommand).toFile)
)
)(source => ZIO.attempt(source.close()).orDie) { source =>
ZIO.attempt(source.getLines().toList.filter(_.trim.nonEmpty))
}
}
.map(_.flatten)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zio.ZIO.readFile

// Create Sample config files
cwd <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.dir")))
homeDir <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.home")))
command <- ZIO.succeed("testApp")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

command = "testApp"

// Create Sample config files
cwd <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.dir")))
homeDir <- ZIO.succeed(Paths.get(java.lang.System.getProperty("user.home")))
command <- ZIO.succeed("wc")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

command = "wc"

_ <- cliApp.run(args = List("sample_file")).either
output <- TestConsole.output

correctOutput <- ZIO.succeed(" 3 4 20 sample_file\n")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

=

Comment on lines +135 to 137
.flatMap { configArgs =>
ZIO.succeed(configArgs ++ args)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.map

import java.nio.file.{Files, Path, Paths}
import scala.io.Source

object ConfigFileArgsPlatformSpecific extends ConfigFilePlatformSpecific {
Copy link
Contributor

@Kalin-Rudnicki Kalin-Rudnicki Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be copy-pasted, if so, move into ./zio-cli/jvm-native

content: String = "asdf\nqweqwer\njdsafn"
): IO[IOException, Unit] =
ZIO.attempt {
Files.write(Paths.get(cwd.toString(), s"$file_name"), java.util.Arrays.asList(content));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

content.getBytes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s"$file_name" = fileName

Comment on lines +14 to +19
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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work for Windows/Linux/Mac?
I have a sneaking suspicion it might not work correctly for /home/kalin/dev/a/b/c/.cmd
Might be safer to implement via Path#getParent?

Comment on lines +6 to +9
trait ConfigFilePlatformSpecific {
def findPathsOfCliConfigFiles(topLevelCommand: String): Task[List[String]]
def loadOptionsFromConfigFiles(topLevelCommand: String): ZIO[Any, IOException, List[String]]
}
Copy link
Contributor

@Kalin-Rudnicki Kalin-Rudnicki Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal opinion:
I think it would be nicer if this passed via something ~like

trait ConfigFileArgs {
  // ...
}
object ConfigFileArgs {
  val default: ConfigFileArgs // platform specific
}
sealed trait CliApp[-R, +E, +A] { self =>
  def runWithFileArgs(args: List[String]): ZIO[R & ConfigFileArgs, CliError[E], Option[A]]
  final def run(args: List[String]): ZIO[R, CliError[E], Option[A]] = 
    runWithFileArgs(args).provideSomeLayer(ZLayer.succeed(ConfigFileArgs.default))
}

Copy link
Contributor

@Kalin-Rudnicki Kalin-Rudnicki Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I suggest this is because it would allow for more separation of concerns, and make much more testable.
You could independently test a Live impl, verify that is doing what it should, and provide a Mock layer to the actual cliApp.run (or in this example cliApp.runWithFileArgs), so that you arent doing all this file standup/teardown in order to test the internals of the parsing is working correctly

Again, very much personal opinion 😅

@vivasvan1
Copy link
Author

@Kalin-Rudnicki
Wow, thank you so much for all the comments, I really appreciate your help 🙏.

I will look into this asap. This seems really helpful.

@pablf
Copy link
Member

pablf commented Jun 4, 2024

@vivasvan1 If I remember correctly, the tests contained in the code of my last comment should all pass if the implementation of the feature were correct, so if you copy that code into zio-cli/jvm/src/test/scala/zio/cli/AlternativeSpec.scala it should be green. It's great testing now on CliApp as you have changed, but I only see one use case tested. I think this is the problem. My sample test should be working (correct me if I'm wrong please) so, would you mind adding it exactly the same to your PR? If it passed, maybe there would be some other test that should be working and isn't, but I think this would be a good start.

If you have any doubt, ask me please! Or maybe you have already tried adding the AlternativeSpec to your build and was passing? Given that it's already written and has tests that should be passing, it should be included in the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for reading command-line options from file(s)
5 participants