Skip to content

Lightweight, non intrusive Command Line Argument Parser

License

Notifications You must be signed in to change notification settings

fred1268/go-clap

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Release Go Report Codecov Go Reference License: MIT


clap 👏

The lightweight, non-intrusive Command Line Argument Parser


Why?

The most famous command line parser is undoubtly Cobra. Cobra is very nice and has tons of features, however, I was not quite happy with it. First, it does way too many things for my liking, and also, it is pretty intrusive, i.e. you need to build your code around Cobra, rather than having Cobra silently collaborating with your code.


Enters clap 👏

clap is a non intrusive Command Line Argument Parser, that will use struct tags to understand what you want to parse. You don't have to implement interface or use a CLI to scaffold your project: you just call clap.Parse(args, &YourConfig) and you are done. You are nonetheless responsible for handling potential commands and subcommands, but clap will fill up your CLI configuration struct with the values passed on the command line for those commands / subcommands.


Benefits of clap 👏

  • lightweight
  • non intrusive
  • obvious defaults
  • no dependencies
  • easy to configure
  • no CLI nor scaffolding

As a bonus, clap is about 350 LoC (excluding comments and tests)


Installation

go get github.com/fred1268/go-clap

Quick start

Very easy to start with:

  1. declare a struct containing your configuration
  2. add struct tags as hints for clap
  3. call clap.Parse(args, &config)
    type config struct {
    	Cookie      string   `clap:"--cookie"`
    	HTTPOnly    bool     `clap:"--httpOnly"`
    	Secure      bool     `clap:"--secure"`
    	Origins     [4]string `clap:"--origins,-O,mandatory"`
    	Port        int      `clap:",-P,mandatory"`
    	ConfigFiles []string `clap:"trailing"`
    }

Please note that this automatically generates a --no-secure and --no-httpOnly flags that you can use on the command line to set the corresponding booleans to the false value. This is useful when you want to give a boolean a true default value.

A clap struct tag has the following structure:

    Name        Type    `clap:"longName[,shortName][,mandatory]"`

longName is a... well... long name, like --recursive or --credentials

shortName is a single letter name, like -R or -c

mandatory can be added to make the non-optional parameters

In your main, just make a call to clap.Parse():

    func main() {
        var err error
        var results *clap.Results
        // define your defaults
    	cfg := &config{Secure: true}
        // note you may want to skip the first few
        // parameters (like command and subcommand)
        // by passing args[2:] instead of args
        if results, err = clap.Parse(args, cfg); err != nil {
            // results contains a list of arguments in error
            // can be used for user friendly error handling
            return err
        }
        // results contains the list of arguments being ignored
        // can be used for user friendly error handling
    }

Assuming the command line looks like:

    -P 8080 --cookie clapcookie --httpOnly --origins http://localhost:5137 \
    https://localhost:5173 http://localhost:3000 https://localhost:3000 \
    config-db.json config-log.json

You will get the following struct:

    config{
    	Cookie:   "clapcookie",
    	HTTPOnly: true,
    	Secure:   true, // comes from your default (not in the command line)
    	Origins: []string{
    		"http://localhost:5137", "https://localhost:5173",
    		"http://localhost:3000", "https://localhost:3000",
    	},
    	Port:        8080,
    	// trailing parameters
    	ConfigFiles: []string{"config-db.json", "config-log.json"},
    }

Note that it is important to use arrays rather than slices when you can, since arrays will consume, at maximum the requested number of parameters, whereas slices will consume all possible parameters. Thus, a slice parameter should not be used just before the trailing otherwise it will consume the trailing parameters.


Supported parameter types

The following parameter types are supported by clap:

  • bool: --param or --no-param
  • string: --param test
  • int: --param 10
  • float: --param 12.3
  • string array of any size (here 3): --param a b c
  • int array of any size (here 2): --param 80 443
  • string slice: --param a b c
  • int slice: --param 80 443

Handling commands and subcommands

clap doesn't have explicit support for commands and subcommands because it doesn't really need it. The way to have commands is simple:

  • create a parameter struct per command
  • switch on os.Args[1] (the command)
  • call clap.Parse(os.Args[2:])

Here is an example:

type RunParams struct {
	ForceRebuild bool `clap:",-a"`
	PrintOnly    bool `clap:",-n"`
    // ...
}

type TestParams struct {
	Binary      string `clap:",-o"`
	CompileOnly bool   `clap:",-c"`
    // ...
}

func main() {
	if len(os.Args) < 2 {
		os.Exit(1)
	}
	var runParams RunParams
	var testParams TestParams
	switch os.Args[1] {
	case "run":
		clap.Parse(os.Args[2:], &runParams)
	case "test":
		clap.Parse(os.Args[2:], &testParams)
	}
	fmt.Printf("%v\n%v\n", runParams, testParams)
}

Working with subcommands involves exactly the same steps, except that you will consume the first two args and give os.Args[3:] to clap.Parse().


License & contribution

clap is licensed under the MIT license (see LICENSE).

Issues or PR are welcome.