Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
gopkg.in/ukautz/clif.v1
Go framework for rapid command line application development.
package main
import "gopkg.in/ukautz/clif.v1"
func main() {
clif.New("My App", "1.0.0", "An example application").
New("hello", "The obligatory hello world", func(out clif.Output) {
out.Printf("Hello World\n")
}).
Run()
}
$ go get gopkg.in/ukautz/clif.v1
On the one side, CLIF's builder-like API can be easily used for rapid development of small, single purpose tools. On the other side, CLIF is designed with complex console applications in mind.
Commands must have a unique name and can have additional arguments and options.
cmd1 := clif.NewCommand("name", "A description", callBackFunction)
cmd2 := clif.NewCommand("other", "Another description", callBackFunction2)
The name
is used from the command line to call the command:
$ ./app name
$ ./app other
Callback functions can have arbitrary parameters. CLIF uses a small, built-in (signatur) injection container which allows you to register any kind of object (struct
or interface
) beforehand.
So you can register any object (interface{}, struct{} .. and anything else, see below) in your bootstrap and then "require" those instances by simply putting them in the command callback signature:
// Some type definition
type MyFoo struct {
X int
}
func main() {
// init cli
cli := clif.New("My App", "1.0.0", "An example application")
// register object instance with container
foo := &MyFoo{X: 123}
cli.Register(foo)
// Create command with callback using the peviously registered instance
cli.NewCommand("foo", "Call foo", func (foo *MyFoo) {
// do something with foo
})
cli.Run()
}
Using interfaces is possible as well, but a bit less elegant:
// Some interface
type MyBar interface {
Bar() string
}
// Some type
type MyFoo struct {
}
// implement interface
func (m *MyFoo) Bar() string {
return "bar"
}
func main() {
// init cli
cli := clif.New("My App", "1.0.0", "An example application")
// create object, which implements MyBar:
foo := &MyFoo{}
t := reflect.TypeOf((*MyBar)(nil)).Elem()
cli.RegisterAs(t.String(), foo)
// Register command with callback using the type
cli.NewCommand("bar", "Call bar", func (bar MyBar) {
// do something with bar
})
cli.Run()
}
Everything works great if you only have a single instance of any object of a specific type.
However, if you need more than one instance (which might often be the case for primitive
types, such as int
or string
) you can use named registering:
// Register abitrary objects under unique name
cli.RegisterNamed("foo", new(MyFoo)).
RegisterNamed("bar", 123).
RegisterNamed("baz", "bla")
// Register command with callback named container
cli.NewCommand("bar", "Call bar", func (named clif.NamedParameters) {
asMap := map[string]interface{}(named)
fmt.Println(asMap["baz"].(string))
})
Note: If you want to use the named feature, you cannot Register()
any NamedParameters
instance yourself, since "normally" registered objects are evaluated before named.
CLIF pre-populates the dependency container with a couple of built-in objects:
Output
(formatted output helper, see below), eg func (out clif.Output) { .. }
Input
(input helper, see below), eg func (in clif.Input) { .. }
*Cli
instance itself, eg func (c *clif.Cli) { .. }
*Command
instance, eg func (o *clif.Command) { .. }
CLIF can deal with arguments and options. The difference being:
--opt-name
(or alias, eg -O
)Of course you can use arguments and options at the same time..
Arguments are additional command line parameters which come after the command name itself.
cmd := clif.NewCommand("hello", "A description", callBackFunction)
.NewArgument("name", "Name for greeting", "", true, false)
arg := cmd.NewAgument("other", "Something ..", "default", false, true)
cmd.AddArgument(arg)
Arguments consist of a name, a description, an optional default value a required flag and a multiple flag.
$ ./my-app hello the-name other1 other2 other3
# ^ ^ ^ ^ ^
# | | | | |
# | | | | third "other" arg
# | | | second "other" arg
# | | first "other" arg
# | the "name" arg
# command name
Position of arguments matters. Make sure you add them in the right order. And: required arguments must come before optional arguments (makes sense, right?). There can be only one multiple argument at all and, of course, it must be the last (think: variadic).
You can access the arguments by injecting the command instance *clif.Command
into the callback and calling the Argument()
method. You can choose to interpret the argument as String()
, Int()
, Float()
, Bool()
, Time()
or Json()
. Multiple arguments can be accessed with Strings()
, Ints()
.. and so on. Count()
gives the amount of (provided) multiple arguments and Provided()
returns bool for optional arguments. Please see parameter.go for more.
func callbackFunctionI(c *clif.Command) {
// a single
name := c.Argument("name").String()
// a multiple
others := c.Argument("other").Strings()
// .. do something ..
}
Options have no fixed position, meaning ./app --foo --bar
and ./app --bar --foo
are equivalent. Options are referenced by their name (eg --name
) or alias (eg -n
). Unless the option is a flag (see below) it must have a value. The value must immediately follow the option. Valid forms are: --name value
, --name=value
, -n value
and -n=value
.
Options must come before the command, unless they use the =
separator. For example: ./app command --opt value
is valid, ./app --opt=value command
is valid but ./app --opt value command
is not valid (since it becomes impossible to distinguish between command and value).
cmd := clif.NewCommand("hello", "A description", callBackFunction)
.NewOption("name", "n", "Name for greeting", "", true, false)
arg := cmd.NewOption("other", "O", "Something ..", "default", false, true)
cmd.AddOption(arg)
Now:
$ ./my-app hello --other bar -n Me -O foo
# ^ ^ ^
# | | |
# | | second other opt with value
# | name opt with value
# first other opt with value
You can access options the same way as arguments, just use Option()
instead.
func callbackFunctionI(c *clif.Command) {
name := c.Option("name").String()
others := c.Option("other").Strings()
// .. do something ..
}
There is a special kind of option, which does not expect a parameter: the flag. As options, their position is arbitrary.
// shorthand
flag := clif.NewFlag("my-flag", "f", "Something ..", false)
// which would just do:
flag = clif.NewOption("my-flag", "f", "Something ..", "", false, false).IsFlag()
cmd := clif.NewCommand("hello", "A description", callBackFunction).AddOption(flag)
When using the option, you dont need to (nor can you) provide an argument:
$ ./my-app hello --my-flag
You want to use Bool()
to check if a flag is provided:
func callbackFunctionI(c *clif.Command) {
if c.Option("my-flag").Bool() {
// ..
}
}
You can validate/parse/transform the input using the Parse
attribute of options or arguments. It can be (later on)
set using the SetParse()
method:
// Validation example
arg := clif.NewArgument("my-int", "An integer", "", true, false).
SetParse(func(name, value string) (string, error) {
if _, err := strconv.Atoi(value); err != nil {
return "", fmt.Errorf("Oops: %s is not an integer: %s", name, err)
} else {
return value, nil
}
})
// Transformation example
opt := clif.NewOption("client-id", "c", "The client ID", "", true, false).
SetParse(func(name, value string) (string, error) {
if strings.Index(value, "#") != 0 {
return fmt.Sprintf("#%s", value), nil
} else {
return value, nil
}
})
There are a few built-in validators you can use out of the box:
clif.IsInt
- Checks for integer, eg clif.NewOption(..).SetParse(clif.IsInt)
clif.IsFloat
- Checks for float, eg clif.NewOption(..).SetParse(clif.IsFloat)
See validators.go.
The argument and option constructors (NewArgument
, NewOption
) already allow you to set a default. In addition you can set
the name of an environment variable, which will be used, if the parameter is not provided.
opt := clif.NewOption("client-id", "c", "The client ID", "", true, false).SetEnv("CLIENT_ID")
The order is:
--config /path/to/config
CONFIG_FILE
SetDefault()
Note: A required parameter must have a value, but it does not care whether it came from input, via environment variable or as a default value.
Often you need one or multiple options on every or most commands. The usual --verbose
or --config /path..
are common examples.
CLIF provides two ways to deal with those.
clif.DefaultOptions
(it's pre-filled with the --help
option, which is clif.DefaultHelpOption
)AddDefaultOptions()
or NewDefaultOption()
on an instance of clif.Cli
The former is global (for any instance of clif.Cli
) and assigned to any new command (created by the NewCommand
constructor). The latter is applied when Run()
is called and is in the scope of a single clif.Cli
instance.
Note: A helpful patterns is combining default options and the injection container/registry. Following an example parsing a config file, which can be set on any command with --config /path..
or as an environment variable and has a default path.
type Conf struct {
Foo string
Bar string
}
func() main {
// init new cli app
cli := clif.New("my-app", "1.2.3", "My app that does something")
// register default option, which fills injection container with config instance
configOpt := clif.NewOption("config", "c", "Path to config file", "/default/config/path.json", true, false).
SetEnv("MY_APP_CONFIG").
SetParse(function(name, value string) (string, error) {
conf := new(Conf)
if raw, err := ioutil.ReadFile(value); err != nil {
return "", fmt.Errorf("Could not read config file %s: %s", value, err)
} else if err = json.Unmarshal(raw, conf); err != nil {
return "", fmt.Errorf("Could not unmarshal config file %s: %s", value, err)
} else if conf.Foo == "" {
return "", fmt.Errorf("Config %s is missing \"foo\"", value)
} else {
// register *Conf
cli.Register(conf)
return value, nil
}
})
cli.AddDefaultOptions(configOpt)
// Since *Conf was registered it can be used in any callback
cli.New("anything", "Does anything", func(conf *Conf) {
// do something with conf
})
cli.Run()
}
Of course, you can just use fmt
and os.Stdin
, but for convenience (and fancy output) there are clif.Output
and clif.Input
.
You can inject an instance of the clif.Input
interface into your command callback. It provides small set of often used tools.
Just ask the user a question then read & check the input. The question will be asked until the check/requirement is satisfied (or the user exits out with ctrl+c
):
func callbackFunctionI(in clif.Input) {
// Any input is OK
foo := in.Ask("What is a foo", nil)
// Validate input
name := in.Ask("Who are you? ", func(v string) error {
if len(v) > 0 {
return nil
} else {
return fmt.Errorf("Didn't catch that")
}
})
// Shorthand for regex validation
count := in.AskRegex("How many? ", regexp.MustCompile(`^[0-9]+$`))
// ..
}
See clif.RenderAskQuestion
for customization.
Confirm()
ask the user a question until it is answered with yes
(or y
) or no
(or n
) and returns the response as bool
.
func callbackFunctionI(in clif.Input) {
if in.Confirm("Let's do it?") {
// ..
}
}
See clif.ConfirmRejection
, clif.ConfirmYesRegex
and clif.ConfirmNoRegex
for customization.
Choose()
is like a select in HTML and provides a list of options with descriptions to the user. The user then must choose (type in) one of the options. The choices will be presented to the user until a valid choice (one of the options) is provided.
func callbackFunctionI(in clif.Input) {
father := in.Choose("Who is your father?", map[string]string{
"yoda": "The small, green guy",
"darth": "The one with the smoker voice and the dark cape!",
"obi": "The old man with the light thingy",
})
if father == "darth" {
// ..
}
}
See clif.RenderChooseQuestion
, clif.RenderChooseOption
and clif.RenderChooseQuery
for customization.
The clif.Output
interface can be injected into any callback. It relies on a clif.Formatter
, which does the actual formatting (eg colorizing) of the text.
Per default, the clif.DefaultInput
via clif.NewColorOutput()
is used. It uses clif.DefaultStyles
, which look like the screenshots you are seeing in this readme.
You can change the output like so:
cli := clif.New(..)
cli.SetOutput(clif.NewColorOutput().
SetFormatter(clif.NewDefaultFormatter(clif.SunburnStyles))
Styles are applied by parsing (replacing) tokens like <error>
, which would be substitude with \033[31;1m
(using the default styles) resulting in a red coloring. Another example is <reset>
, which is replaced with \033[0m
leading to reset all colorings & styles.
There three built-in color styles (of course, you can extend them or add your own):
DefaultStyles
- as you can see on this pageSunburnStyles
- more yellow'ishWinterStyles
- more blue'ishTable rendering is a neat tool for CLIs. CLIF supports tables out of the box using the Output
interface.
Features:
Example:
var (
headers := []string{"Name", "Age", "Force"}
rows = [][]string{
{
"<important>Yoda<reset>",
"Very, very old",
"Like the uber guy",
},
{
"<important>Luke Skywalker<reset>",
"Not that old",
"A bit, but not that much",
},
{
"<important>Anakin Skywalker<reset>",
"Old dude",
"He is Lukes father! Was kind of stronger in 1-3, but still failed to" +
" kill Jar Jar Binks. Not even tried, though. What's with that?",
},
}
)
func callbackFunction(out clif.Output) {
table := out.Table(headers)
table.AddRows(rows)
fmt.Println(table.Render())
}
Would print the following:
There are currently to styles available: ClosedTableStyle
(above), ClosedTableStyleLight
, OpenTableStyle
(below) and OpenTableStyleLight
:
func callbackFunction(out clif.Output) {
table := out.Table(headers, clif.OpenTableStyle)
table.AddRows(rows)
fmt.Println(table.Render())
}
Would print the following:
Another often required tool is the progress bar. Hence CLIF provides one out of the box:
func cmdProgress(out clif.Output) error {
pbs := out.ProgressBars()
pb, _ := pbs.Init("default", 200)
pbs.Start()
var wg sync.WaitGroup
wg.Add(1)
go func(b clif.ProgressBar) {
defer wg.Done()
for i := 0; i < 200; i++ {
b.Increment()
<-time.After(time.Millisecond * 100)
}
}(pb)
wg.Wait()
<-pbs.Finish()
}
Would output this:
Multiple bars are also possible, thanks to Greg Osuri's library:
func cmdProgress(out clif.Output) error {
pbs := out.ProgressBars()
pbs.Start()
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
pb, _ := pbs.Init(fmt.Sprintf("bar-%d", i+1), 200)
go func(b clif.ProgressBar, ii int) {
defer wg.Done()
for i := 0; i < 200; i++ {
b.Increment()
<-time.After(time.Millisecond * time.Duration(100 * ii))
}
}(pb, i)
}
wg.Wait()
<-pbs.Finish()
}
Would output this:
You prefer those ASCII arrows? Just set pbs.SetStyle(clif.ProgressBarStyleAscii)
and:
To provide you a usful'ish example, I've written a small CLI application called repos.
There are a lot of other approaches you should have a look at.
FAQs
Unknown package
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.