Git Product home page Git Product logo

kyper's Introduction

kyper License codecov

Functional Kotlin friendly way to create command line applications.

It comes from the basic need to have a function like:

fun main(
    content: String,
    target: File,
    mode: Mode = Mode.APPEND,
) {
    TODO()
}

enum class Mode { APPEND, OVERWRITE }

with all the behaviors we expect when using it in our code:

  • supports more data types than just String, i.e. File/Path/Enum/etc
  • mode being an optional parameter

and turns it into a command line application powered by Kotlin script.

This library is solving this need ๐Ÿช„

Kyper? Is it a name?

This library is hugely inspired by the wonderful typer from the Python ecosystem.

Also naming is hard ๐Ÿ˜‡

But we already have clikt? (or any alternative)

Correct, but I try to keep my Kotlin scripts as small as possible, and having to deal with classes is not what I would describe as simple.

Also chained property delegates are great, but I always need to read the documentation to figure out all the options.

So let's migrate everything to this wonderful library?

For simple usecases like Kotlin scripts, feel free.

For more complex applications, where readability is important, I would stick with clikt or any alternative not relying on magic reflection and/or implicit behaviors like this library is doing.

Installation central

repositories {
    mavenCentral()
}

dependencies {
    // Check the ๐Ÿ” maven central badge ๐Ÿ” for the latest $kyperVersion
    implementation("com.github.pgreze:kyper:$kyperVersion")
}

Or in your kotlin script:

@file:DependsOn("com.github.pgreze:kyper:$kyperVersion")

Usage with function(s)

Start with a single function

We can start by defining a simple function handling our logic:

#!/usr/bin/env kotlinc -script

import com.github.pgreze.kyper.Command
import com.github.pgreze.kyper.Parameter
import com.github.pgreze.kyper.kyper

@Commmand("function help message")
fun main(
    @Parameter("the name to greet")
    name: String
) {
    println("hello $name")
}

kyper().invoke(args)

And run it with the name parameter:

$ ./script.main.kts there
hello there

Notice we also defined help messages for both the command and its parameter:

$ # Use `--` to indicate that the following arguments are for the script, not kotlinc itself
$ ./script.main.kts -- --help
Usage: main NAME

  function help message

Options:
  -h, --help      Show this message and exit

Arguments:
  NAME  the name to greet

Only @Command annotated methods are exported

Our script can declare more methods, without exposing them as command:

#!/usr/bin/env kotlinc -script

import com.github.pgreze.kyper.Command
import com.github.pgreze.kyper.kyper

@Command
fun main(name: String) {
    greet(name)
}

fun greet(name: String) {
    println("Hello $name")
}

kyper().invoke(args)

Usage is the same:

$ ./script.main.kts there
hello there

But having several @Command functions will turn our application into a multi-command mode:

#!/usr/bin/env kotlinc -script

import com.github.pgreze.kyper.Command
import com.github.pgreze.kyper.kyper

@Command("Say hello in English")
fun hello(name: String) {
    println("hello $name")
}

@Command("Say hello in French")
fun bonjour(name: String) {
    println("bonjour $name")
}

kyper(help = "Say hello to your user").invoke(args)

We can now notice several commands are available by calling --help:

$ ./kyper/hellos.main.kts -- --help
Usage: [OPTIONS] COMMAND [ARGS]...

  Say hello to your user

Options:
  -h, --help      Show this message and exit

Commands:
  BONJOUR  Say hello in French
  HELLO    Say hello in English

Each command can be requested for --help:

$ ./kyper/hellos.main.kts -- --help bonjour
Usage: bonjour NAME

  Say hello in French

Options:
  -h, --help      Show this message and exit

Arguments:
  NAME

Handle more than strings as arguments

The following types are supported:

fun main(
    string: String = "arg",
    int: Int = 1,
    float: Float = 1.2f,
    double: Double = 3.14,
    long: Long = Long.MAX_VALUE,
    boolean: Boolean = true,
    bigInteger: BigInteger = BigInteger.valueOf(12),
    bigDecimal: BigDecimal = BigDecimal.valueOf(12.3),
    file: File = File("file"),
    path: Path = Path.of("path"),
    choice: Choice = Choice.NO,
    vararg strings: String, // For vararg, only String/File are supported.
) {
    TODO()
}

enum class Choice { YES, NO }

Default values (unstable)

As shown in the last code block, default values are supported as long as they're at the end of the method.

๐Ÿšจ๏ธ WIP: the current implementation is quite simple; it is just based on parameter positioning, and does not allow any --flag logic.

Usage with lambda(s) (experimental)

If the Kotlin DSL syntax is something you're looking for in your Kotlin script, this library also provides a similar syntax based on lambdas:

#!/usr/bin/env kotlinc -script

@file:Suppress("OPT_IN_USAGE")

import com.github.pgreze.kyper.kyper

kyper(help = "Run multiple commands from Kotlin script with ease") {
    register(name = "time", help = "Display current timestamp") { ->
        println(System.currentTimeMillis())
    }

    register(name = "greet") { name ->
        println("Hello $name")
    }
}.invoke(args)

But this comes with restrictions:

  • no default argument(s),
  • up to 4 arguments,
  • only String type is supported.

This may be dropped in the future if we cannot reach the same support level as the functions-based usage.

kyper's People

Contributors

emmacaunter avatar pgreze avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

emmacaunter

kyper's Issues

Better error handling

Define all possible errors with this library, and returns an helpful error instead of "ArrayIndexOutOfBoundsException" when failing on parsing args.

Support --flag

Current argument parsing strategy is really naive.
We should allow to specify any argument and/or at least optional arguments with --flag.

Also this should solve the vararg usage with optional arguments.

Support suspend functions

We're depending on coroutines but it's not used...
Let's add support for suspend functions!

This should be as easy as using runBlocking when using a suspend function.

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.