Giter VIP home page Giter VIP logo

Comments (5)

ajalt avatar ajalt commented on September 25, 2024 1

That's one way to do it, although you don't necessarily need the group. You can test each command individually if you use findOrSetObject instead.

data class User(val login: String, val name: String?)

class UserCommand : CliktCommand() {
    val login by option().required()
    val name by option()

    val users by findOrSetObject { mutableMapOf<String, User>() }

    override fun run() {
        users[login] = User(login, name)
    }
}

class ReplaceCommand : CliktCommand() {
    val users by findOrSetObject { mutableMapOf<String, User>() }
    val args: Set<String> by argument().multiple().unique().help("files to do replacement(s) in")

    override fun run() {
        echo("users: $users")
        echo("args: $args")
    }
}

class Tool : CliktCommand(allowMultipleSubcommands = true) {
    override fun run() {
        // set the context object for subcommands
        currentContext.obj = mutableMapOf<String, User>()
    }
}

test works with subcommands, you just have to register them like you normally would:

val command = Tool().subcommands(UserCommand(), ReplaceCommand())
val result = command.test(
    "user --login a --name b user --login c replace file1 file2"
)
assertEquals(
    result.output,
    """
    users: {a=User(login=a, name=b), c=User(login=c, name=null)}
    args: [file1, file2]
    """.trimIndent()
)

from clikt.

ajalt avatar ajalt commented on September 25, 2024

Groups can't be repeated, but subcommands can. Use a subcommand instead of a group and you can have something like this:

$ ./tool --unrelatedOption person --name hoffi --age=30  person --name both --age=77 --opt=78  person --name jesus --opt=42

from clikt.

HoffiMuc avatar HoffiMuc commented on September 25, 2024

My goal is NOT to run() a command consecutively, as I am transforming very big files,
I want to have some opts that are "globally" for "how" (e.g. case insensitive) to transform all the files,
PLUS multiple groups of options which tell "what" to do with different(!) parts/regions in each file.
So: for a region identified by A do X, for a region identified by B do Y, ...

As the files are very big, I don't want to run the subcommand x times for achieving all the x transformations,
but give a set of transformations (plus the global options once) so I can do a "transform once"-run on each file,
doing ALL the transformations to it which in turn are defined by the combined information in each group of options.

any way on how to achieve something like that?

addendum: how could I pass trailing arguments (the files to operate on) to ALL subcommand's execution??
Because if my subcommand has arguments val args: Set<String> by argument().multiple().unique()
It will "eat up" anything after the last option of the first subcommand as arguments (and not calling the subcommand a second time, but passing the 2nd 'person' as first arg to the subcommand)

example of what I have in mind:

$ kscript replaceInFiles \
    --verbose --ignore-nonexisting --backup \
    \
    --region-start '^# START REPLACE Region 1' \
    --region-end '^# END REPLACE Region 1' \
    --replace '\d' "X"   --replace 'ri' 'ir'  \
    \
    --region-start '^# START REPLACE Region 2' \
    --region-end '^# END REPLACE Region 2' \
    --replace '^(\w+) ([A-Z]+)(.*)$' 'CHANGED $2'   --replace 'regex' 'replaced by $2' \
    \
    ~/tmp/original.txt ~/tmp/nonex.txt

I know, a bit more "invasive" ... but also would be way cool! :)

from clikt.

ajalt avatar ajalt commented on September 25, 2024

You don't have to do any processing in your run; you can collect the info into your context object and process everything at the end.

But if you're fixed on not using subcommands, you'll have to collect the repeated options as arguments and group them yourself. Global options can be declared as usual.

You can't have arguments after a subcommand with multiple arguments. There would be no way to know whether a token should belong to the parent or subcommand.

from clikt.

HoffiMuc avatar HoffiMuc commented on September 25, 2024

just to confirm if I correctly understood: (and if so, for others who come acoss this as reference)

for re-cooccuring OptionGroups you'd need

  • a CliktCommand that has a single OptionGroup in it
    and puts them in parents currentContext (or its own context to be fetched by subcommand name)
  • the parent CliktCommand has to CliktCommand(allowMultipleSubcommands = true)
  • the CliktCommand that actually uses the multiple gatherd OptionGroups
    (which each of aboves CliktCommand puts in the context)
    by fetching them from the Clikt context (List or Map)

so e.g.:

the gathering one OptionGroup CliktCommand:

class UserOptGroup() : CliktCommand(name = "user") {
    class UserOptions : OptionGroup() {
        override fun toString(): String = "user($login, ${name.singleQuote()}, $age, $opt)"
        val login by option().required()
        val name by option()
        val age by option().int()
        val opt by option().int()
    }
    data class User(val login: String, val name: String?, val age: Int?, val opt: Int?) {
        override fun toString(): String = "User('$login', ${name.singleQuote()}, $age, $opt)"
    }
    val userOptions by UserOptions().cooccurring()
    val unrelatedOption by option("--unrelatedOption").flag()

    override fun run() {
        val parentCtxUsers: MutableMap<String, User> = currentContext.parent!!.findOrSetObject { mutableMapOf() }
        userOptions?.let { parentCtxUsers[it.login] = User(it.login, it.name, it.age, it.opt) }
        // debug output
        echo("UserOptGroup run():")
        echo("  unrelatedOption: '${unrelatedOption}'")
        userOptions?.let { echo("  $it") } ?: echo(" no userOptions")
    }
}

the actual CliktCommand using all of above's OptionGroups

class DoWithUsers() : CliktCommand(name = "doWithUsers") {
    val users: MutableMap<String, UserOptGroup.User> = mutableMapOf() // set/filled in run() from parent CliktCommand context
    val args: Set<String> by argument().multiple().unique().help("files to do replacement(s) in")

    override fun run() {
        val parentUsers = currentContext.parent!!.findObject<MutableMap<String, UserOptGroup.User>>()
        parentUsers?.let { users.putAll(it) }
        // debug output
        if (users.isEmpty()) echo("no users!")
        echo("DoWithUsers: ${users.size} users: ${users.entries.joinToStringSingleQuoted()}")
        echo("DoWithUsers: args: ${args.joinToStringSingleQuoted()}")
    }
}

btw: doing it this way the actual CliktCommand cannot be unit tested without both CliktCommands
or if connected via a parent CliktComand all three of them,
as fun CliktCommand.test(...) only tests a CliktCommand in isolation of all other CliktCommands
(at least not without some dirty hardcoded inject the List/Map of OptionGroup's into it.)
or do you have a fancy idea for testing things like above's class DoWithUsers() : CliktCommand(name = "doWithUsers") ? :)

from clikt.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.