scli

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 21, 2025 License: MIT Imports: 15 Imported by: 0

README

scli

Build a command line argument parser by defining a struct

Example

This example is in example/simple/main.go

// define flag, default and usage in struct field's tags
type Arg struct {
    // Flags(no value) are defined by bool type field
    Help bool `flag:"h" default:"false" usage:"print help"`

    // Arguments(with value) are defined by field of their types
    // define flag, default and usage in struct field's tags
    // flag, default and usage in tag are all optional
    Name  string `flag:"name" default:"you" usage:"your name"`
    Email string `flag:"email" default:"you@example.com" usage:"your email"`

    // subcommands are defined by *struct{...} field
    Add *struct {
        All  bool   `flag:"a" default:"false" usage:"Add all files"`
        File string `flag:"f" usage:"file to be added"`
    } `flag:"add" usage:"Add file contents to the index"`

    Commit *struct{} `flag:"commit" usage:"Record changes to the repository"`

    // subcommands can be nested inside of subcommands!
    Push *struct {
        Origin *struct{} `flag:"origin" usage:"Push to origin"`
    } `flag:"push" usage:"Push to remote repository"`
}

func main() {
    var arg Arg // init a empty argument struct

    parser := scli.BuildParser(&arg)
    parser.Parse()
    fmt.Printf("%v\n", prettyPrint(arg))
}

Below are some examples of input CLI arguments and parsed values of arg.

$ ./mygit
{
  "Help": false,
  "Name": "you",
  "Email": "you@example.com",
  "Add": null,
  "Commit": null,
  "Push": null
}

$ ./mygit --name canoriz
{
  "Help": false,
  "Name": "canoriz",
  "Email": "you@example.com",
  "Add": null,
  "Commit": null,
  "Push": null
}

$ ./mygit --name canoriz add -a -f source-code
{
  "Help": false,
  "Name": "canoriz",
  "Email": "you@example.com",
  "Add": {
    "All": true,
    "File": "source-code"
  },
  "Commit": null,
  "Push": null
}

$ ./mygit --name canoriz commit
{
  "Help": false,
  "Name": "canoriz",
  "Email": "you@example.com",
  "Add": null,
  "Commit": {},
  "Push": null
}

$ ./mygit --name canoriz push origin
{
  "Help": false,
  "Name": "canoriz",
  "Email": "you@example.com",
  "Add": null,
  "Commit": null,
  "Push": {
    "Origin": {}
  }
}

$ ./mygit --name canoriz push
{
  "Help": false,
  "Name": "canoriz",
  "Email": "you@example.com",
  "Add": null,
  "Commit": null,
  "Push": {
    "Origin": null
  }
}

$ ./mygit --name canoriz add
error: argument "--f" is required in "./mygit add" but not provided

The usage is:
Usage: ./mygit add [OPTIONS]

Options:
    -help, --help           print this message
    --a, --/a               set [Add all files] to true / false  [default: false]
    --f <file to be added>

$ ./mygit --help
Usage: ./mygit [OPTIONS] [COMMAND]

Commands:
    add     Add file contents to the index
    commit  Record changes to the repository
    push    Push to remote repository

Options:
    -help, --help         print this message
    --email <your email>  [default: "you@example.com"]
    --h, --/h             set [print help] to true / false  [default: false]
    --name <your name>    [default: "you"]

Run `./mygit [COMMAND] -help` to print the help message of COMMAND

$ ./mygit add --help
Usage: ./mygit add [OPTIONS]

Options:
    -help, --help           print this message
    --a, --/a               set [Add all files] to true / false  [default: false]
    --f <file to be added>

Feature

Any type of argument

Supports arguments of type int, float64, bool, string, []int, []float, []bool, []string, or any type T that *T implemented scli.Parse.

For example, if Addr {string; string} struct defines a (*Addr).FromString(string) error method parsing 127.0.0.1:80 to Addr {"127.0.0.1", "80"}, and a (*Addr).Example() string returns "1.2.3.4:5678", method.CLI can have a type Addr argument.

  • ./my-program -addr 127.0.0.1:80

Arbitrary nested layer of subcommands

Supports defining CLI program like below, where remote, local are layer 1 subcommands, add, remove are layer 2 subcommands.

  • ./my-program remote add -name Alice
  • ./my-program local add -name Bob
  • ./my-program remote remove -name Cindy

Any argument/subcommand can have a configurable CLI argument name and usage message.

Default values

Supports default value for any type of arguments by simply add a default tag, including custom types.

  • type Arg struct {
        addr Addr `default:"127.0.0.1:80"`
    }
    

Notes for panic

To parse arguments, there are 2 stages.

  1. BuildParser(*struct{...}) error) to build a parser
  2. Parse(), ParseArgs(...)to parse arguments

BuildParser will check struct field's type and tags, if error found in type and tags, BuildParser panics and tells the detailed error. You can think BuildParser compile struct{...} to corresponding Parser, and compile may error.

Parse(), ParseArgs(...) parse input arguments from CLI or []string, they never panics, if parse fails, error is returned.

Parse() do a bit more than ParseArgs(...). If error occurred in Parse(), show parse errors and program USAGE, then program exits.

If a custom type Custom is in arguments struct, and the custom type's Custom.FromString(string) method may panic. If FromString panic, Parse() and ParseArg() will panic.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrIsHelp = errors.New("argument is help message")
)

Functions

This section is empty.

Types

type DisableLiveUpdate added in v0.3.1

type DisableLiveUpdate struct{}

DisableLiveUpdate implements LiveUpdateOpt

type EnableLiveUpdate added in v0.3.1

type EnableLiveUpdate struct{}

EnableLiveUpdate implements LiveUpdateOpt

type File added in v0.3.1

type File[T any, L LiveUpdateOpt] struct {
	// contains filtered or unexported fields
}

Load T from configuration file File[T, L] can be used with scli argument parser, or separately To enable live update, use File[T, scli.EnableLiveUpdate] To disable live update, use File[T, scli.DisableLiveUpdate]

func (*File[T, L]) Example added in v0.3.1

func (f *File[T, L]) Example() string

Example returns an example of config-file input Marker for Parse

func (*File[T, L]) FromString added in v0.3.1

func (f *File[T, L]) FromString(source string) error

Parse data from file

func (*File[T, L]) Get added in v0.3.1

func (f *File[T, L]) Get() *T

Get() returns the inner T instance

func (*File[T, L]) UpdateEvents added in v0.3.1

func (f *File[T, L]) UpdateEvents() <-chan fsnotify.Event

UpdateEvents() returns a channel of fsnotify.Events. An event will be send to this channel once the file changes.

type LiveUpdateOpt added in v0.3.1

type LiveUpdateOpt interface {
	// contains filtered or unexported methods
}

LiveUpdateOpt is type restriction of L in File[T, L]. EnableLiveUpdate and DisableLiveUpdate are two types implemented LiveUpdateOpt. This interface SHOULD NOT be implemented by users.

type MarkOnce added in v0.3.1

type MarkOnce[T Parse] struct {
	// contains filtered or unexported fields
}

EXPERIMENTAL: MarkOnce[T]: convert Parse to ParseOnce for type T that *T is Parse

struct {
	t T
}

should now define:

type TOnce = scli.MarkOnce[*T]

struct {
		tOnce TOnce
}

and get T by call tOnce.Get()

func (*MarkOnce[T]) Example added in v0.3.1

func (m *MarkOnce[T]) Example() string

func (*MarkOnce[T]) FromString added in v0.3.1

func (m *MarkOnce[T]) FromString(s string) error

func (*MarkOnce[T]) Get added in v0.3.1

func (m *MarkOnce[T]) Get() *T

type Parse

type Parse interface {
	FromString(s string) error
	Example() string
}

Parse is the interface for custom type. Any type implemented this interface can be used as an argument/option in scli's definition struct.

type ParseOnce added in v0.3.1

type ParseOnce interface {
	Parse
	// contains filtered or unexported methods
}

ParseOnce is an interface for types that are Parse, but the FromString() of the type should be called at most once. This restriction is vital for implementing types like ConfigFile. Lets see why. First, some definitions.

Definitions:

1. Stateful Assuming all the outside running environment is unchanged, for instance t, calling t.FromString() once and calling t.FromString(..) for the second time will result in different behavior. One example is FromString contains logics based on a global variable which is inherited next time, then the second call will not trigger the same logic.

2. Impure For instance t, the result of t.FromString(..) not only dependent on the code of FromString it self, and may produce different outcome based on the running environment. One example is FromString read a file, and file may or may not exist on different environment.

For ConfigFile type, apparently, it's Impure because it relies on files which is not part of the code. For ConfigFile that can live update it's value when file is changed, the type is Stateful. A watcher is created for the first time a FromString() success and when file changes, FromString is called again to load new data, but not creating new watchers.

For ParseOnce, scli will not check default value nor example value, and Parser.Parse()FromString is called only once at parsing. If you think your type T is ParseOnce, use MarkOnce[T] to convert your Parse type to ParseOnce

type Parser

type Parser[T any] struct {
	// contains filtered or unexported fields
}

func BuildParser

func BuildParser[T any](u *T) Parser[T]

func (Parser[T]) Checker

func (p Parser[T]) Checker(checkFn func(T) error) Parser[T]

func (Parser[T]) Help

func (p Parser[T]) Help() string

func (Parser[T]) Parse

func (p Parser[T]) Parse() T

func (Parser[T]) ParseArg

func (p Parser[T]) ParseArg(s string) (zero T, err error)

Directories

Path Synopsis
example
custom_type command
simple command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL