GithubHelp home page GithubHelp logo

hartbit / yaap Goto Github PK

View Code? Open in Web Editor NEW
118.0 3.0 0.0 108 KB

Yet Another (Swift) Argument Parser

License: MIT License

Swift 100.00%
swift cli argument-parser argument-parsing argument-parsers argparse argparser macos linux yet-another

yaap's Introduction

Yaap version 1.0.0 swift 5.1 license MIT

Yaap is Yet Another (Swift) Argument Parser that represents executable commands as types, and arguments as properties of those types. It supports:

  • Strongly-typed argument and option parsing
  • Automatic help and usage message generation
  • Multiple command routing
  • Smart error messages with suggestion on typos

Here's a self-contained example of a rand executable that generates random numbers in a configurable interval to standard output, with everything from --help documentation, usage generation, and --version printing.

class RandomCommand: Command {
    let name = "rand"
    let documentation = "Generates a random number that lies in an interval."

    @Argument(documentation: "Exclusive maximum value")
    var maximum: Int

    @Option(shorthand: "m", documentation: "Inclusive minimum value")
    var minimum: Int = 0

    let help = Help()
    let version = Version("0.1.0")

    func run(outputStream: inout TextOutputStream, errorStream: inout TextOutputStream) throws {
        guard maximum > minimum else {
            throw InvalidIntervalError(minimum: minimum, maximum: maximum)
        }

        outputStream.write(Int.random(in: minimum..<maximum).description)
        outputStream.write("\n")
    }
}

struct InvalidIntervalError: LocalizedError {
    let minimum: Int
    let maximum: Int

    var errorDescription: String? {
        return "invalid interval [\(minimum), \(maximum))"
    }
}

RandomCommand().parseAndRun()

Installation

Yaap can be installed as a Swift Package Manager dependency. Here's the declaration for depending on the latest stable version:

let package = Package(
    dependencies: [
        .package(url: "https://github.com/hartbit/Yaap.git", from: "1.0.0")
    ]
)

Usage

Commands

In Yaap, a command is a self-contained operation defined as a class conforming to the Command protocol: arguments (if any) are defined as properties and execution logic is defined in a run(outputStream:errorStream) function. Simple programs only need one command but can grow more as necessary.

A command must also define a name (the executable name), that will appear in the usage description, and an optional documentation property, that will appear in the help output.

class HelloWorldCommand: Command {
    let name = "hello-world"
    let description = "My first command"

    func run(outputStream: inout TextOutputStream, errorStream: inout TextOutputStream) throws {
        outputStream.write("Hello World")
    }
}

Commands can parse command-line arguments and run themselves with the parseAndRun function:

let command = HelloWorldCommand()
command.parseAndRun()

Any errors thrown by run(outputStream:errorStream) will be caught and reported to the standard error stream.

Arguments

Mandatory arguments are defined using the generic Argument type and are parsed in the order they are declared in the command. They can also be configured with an optional name and documentation that will show in the help output:

class SplitCommand: Command {
    let name = "split"

    @Argument(documentation: "The string to split.")
    var string: String

    @Argument(name: "separator", documentation: "The seperator to split the string with.")
    var sep: Character

    func run(outputStream: inout TextOutputStream, errorStream: inout TextOutputStream) throws {
        outputStream.write(string.split(separator: sep).joined(separator: "\n"))
        outputStream.write("\n")
    }
}
$ split "The Swift Programming Language" " "
The
Swift
Programming
Language

Options

Optional arguments are defined using the generic Option type and must provide a defaultValue. The are parsed using the --option value or --option=value syntax where option is the name of the property, which can be customized with an optional name parameter. There is also an optional shorthand parameter to allow parsing them with a single character syntax of -o value or -o=value. Again, documentation can be provided for the help output:

class SplitCommand: Command {
    let name = "split"

    @Argument
    var string: String

    @Option(name: "separator", shorthand: "s", documentation: "The seperator to split the string with.")
    var sep: Character = " "

    func run(outputStream: inout TextOutputStream, errorStream: inout TextOutputStream) throws {
        outputStream.write(string.split(separator: sep).joined(separator: "\n"))
        outputStream.write("\n")
    }
}
$ split a,b,c,d --separator ,
a
b
c
d

Sub-commands

Help

Yaap comes with a built-in Help property that parses --help/-h arguments and prints the command's detailed documentation to standard output. It can be configured with a different name and shorthand syntax. Using the RandomCommand example from above:

class RandomCommand: Command {
    let name = "rand"
    let documentation = "Generates a random number that lies in an interval."
    let help = Help()

    @Argument(documentation: "Exclusive maximum value")
    var maximum: Int

    @Option(shorthand: "m", documentation: "Inclusive minimum value")
    var minimum: Int = 0

    func run(outputStream: inout TextOutputStream, errorStream: inout TextOutputStream) throws {
        // ...
    }
}
$ rand --help
OVERVIEW: Generates a random number that lies in an interval.

USAGE: rand [options] <maximum>

ARGUMENTS:
  maximum          Exclusive maximum value

OPTIONS:
  --help, -h       Display available options [default: false]
  --minimum, -m    Inclusive minimum value [default: 0]

Version

Yaap also comes with a built-in Version property that allows commands to respond to a --version/-v argument by printing their version number to standard output. The property can be customized to respond to a different argument name and optional shorthand syntax:

class MyCommand: Command {
    let name = "program"
    let version = Version("4.2", name: "ver", shorthand: nil)

    func run(outputStream: inout TextOutputStream, errorStream: inout TextOutputStream) throws {
        // ...
    }
}
$ program --ver
4.2

Thanks

I'd like to thank SwiftCLI for being a major influence in designing Yaap.

yaap's People

Contributors

hartbit avatar

Stargazers

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

Watchers

 avatar  avatar  avatar

yaap's Issues

Version tag is missing

The installation guideline is currently not working because of the missing version tag.

I get the following error:

error: dependency graph is unresolvable; found these conflicting requirements:

Dependencies: 
    https://github.com/hartbit/Yaap.git @ 0.0.0..<1.0.0

Add subcommand examples to README

Hi, thanks for putting all this together! Additionally, that's a great use case for Property Wrappers.

Could you please add subcommand examples to the README?

Thanks!

PS: I would have liked to contribute this, but I find it hard to enter the codebase because of the "meta" nature of most names in this domain: argument, option, argumenttype, etc, and I lack time to do so.

Example in README needs updating

‪Nice idea and API, nice user case for property wrappers 👍👌

I just saw that the example in the README is not up to date, still uses minimum.value and maximum.value instead of using directly minimum and maximum‬ now that they are properties annotated by a property wrapper.

The example in YaapExample/main.swift in the repo doesn’t have that issue and seem up to date so it’s just a matter of copy/pasting that back in the README (I’d have done it myself via a PR but I’m writing from my phone so not so convenient to open PRs 😅)

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.