-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add a higher level API (see command package) and examples
- Loading branch information
Showing
30 changed files
with
1,695 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Copyright 2024 The Kanister Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package command | ||
|
||
import "github.com/kanisterio/safecli" | ||
|
||
// Applier defines the interface for applying arguments to the command. | ||
type Applier interface { | ||
// Apply applies arguments to the command. | ||
Apply(safecli.CommandAppender) error | ||
} | ||
|
||
// apply appends multiple arguments to the command. | ||
// If any of the arguments encounter an error during the apply process, | ||
// the error is returned and no changes are made to the command. | ||
// If no error, the arguments are appended to the command. | ||
func apply(cmd safecli.CommandAppender, args ...Applier) error { | ||
// create a new subcmd builder which will be used to apply the arguments | ||
// to avoid mutating the command if an error is encountered. | ||
subcmd := safecli.NewBuilder() | ||
for _, arg := range args { | ||
if arg == nil { // if the param is nil, skip it | ||
continue | ||
} | ||
if err := arg.Apply(subcmd); err != nil { | ||
return err | ||
} | ||
} | ||
cmd.Append(subcmd) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright 2024 The Kanister Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package command | ||
|
||
import ( | ||
"github.com/kanisterio/safecli" | ||
) | ||
|
||
// errorArgument is a simple implementation of the Applier interface | ||
// that always returns an error when applied. | ||
type errorArgument struct { | ||
err error // error to return when applied | ||
} | ||
|
||
// Apply does nothing except return an error if one is set. | ||
func (e errorArgument) Apply(cmd safecli.CommandAppender) error { | ||
return e.err | ||
} | ||
|
||
// NewErrorArgument creates a new argument with a given error. | ||
// It is useful for creating an argument that always fails when applied. | ||
func NewErrorArgument(err error) Applier { | ||
return errorArgument{err: err} | ||
} | ||
|
||
// noopArgument is a simple implementation of the Applier interface that does nothing. | ||
type noopArgument struct{} | ||
|
||
func (noopArgument) Apply(safecli.CommandAppender) error { | ||
return nil | ||
} | ||
|
||
// NewNoopArgument creates a new argument that does nothing when applied. | ||
func NewNoopArgument() Applier { | ||
return noopArgument{} | ||
} | ||
|
||
// argument defines an argument with the given name. | ||
// If the argument is redacted, it is appended as redacted. | ||
type argument struct { | ||
name string | ||
isRedacted bool | ||
} | ||
|
||
// Apply appends the argument to the command. | ||
func (a argument) Apply(cmd safecli.CommandAppender) error { | ||
append := cmd.AppendLoggable | ||
if a.isRedacted { | ||
append = cmd.AppendRedacted | ||
} | ||
append(a.name) | ||
return nil | ||
} | ||
|
||
// newArgument creates a new argument with a given name and . | ||
func newArgument(name string, isRedacted bool) Applier { | ||
if name == "" { | ||
return NewErrorArgument(ErrInvalidArgumentName) | ||
} | ||
return argument{ | ||
name: name, | ||
isRedacted: isRedacted, | ||
} | ||
} | ||
|
||
// NewArgument creates a new argument with a given name. | ||
func NewArgument(name string) Applier { | ||
return newArgument(name, false) | ||
} | ||
|
||
// NewRedactedArgument creates a new redacted argument with a given name. | ||
func NewRedactedArgument(name string) Applier { | ||
return newArgument(name, true) | ||
} | ||
|
||
// Arguments defines a collection of command arguments. | ||
type Arguments []Applier | ||
|
||
// Apply applies the flags to the CLI. | ||
func (args Arguments) Apply(cli safecli.CommandAppender) error { | ||
return apply(cli, args...) | ||
} | ||
|
||
// NewArguments creates a new collection of arguments. | ||
func NewArguments(args ...Applier) Applier { | ||
return Arguments(args) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright 2024 The Kanister Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package command_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"gopkg.in/check.v1" | ||
|
||
"github.com/pkg/errors" | ||
|
||
"github.com/kanisterio/safecli" | ||
"github.com/kanisterio/safecli/command" | ||
"github.com/kanisterio/safecli/test" | ||
) | ||
|
||
var ( | ||
ErrArgument = errors.New("arg error") | ||
) | ||
|
||
// MockArg is a mock implementation of the Applier interface. | ||
type MockArg struct { | ||
name string | ||
err error | ||
} | ||
|
||
func (m *MockArg) Apply(cli safecli.CommandAppender) error { | ||
cli.AppendLoggable(m.name) | ||
return m.err | ||
} | ||
|
||
func TestArguments(t *testing.T) { check.TestingT(t) } | ||
|
||
var _ = check.Suite(&test.ArgumentSuite{Cmd: "cmd", Arguments: []test.ArgumentTest{ | ||
{ | ||
Name: "NewErrorArgument without error", | ||
Argument: command.NewErrorArgument(nil), | ||
ExpectedCLI: []string{"cmd"}, | ||
}, | ||
{ | ||
Name: "NewErrorArgument with error", | ||
Argument: command.NewErrorArgument(ErrArgument), | ||
ExpectedErr: ErrArgument, | ||
}, | ||
{ | ||
Name: "NewArgument", | ||
Argument: command.NewArgument("arg1"), | ||
ExpectedCLI: []string{"cmd", "arg1"}, | ||
}, | ||
{ | ||
Name: "NewArgument with empty name", | ||
Argument: command.NewArgument(""), | ||
ExpectedErr: command.ErrInvalidArgumentName, | ||
}, | ||
{ | ||
Name: "NewRedactedArgument", | ||
Argument: command.NewRedactedArgument("arg1"), | ||
ExpectedCLI: []string{"cmd", "arg1"}, | ||
ExpectedLog: "cmd <****>", | ||
}, | ||
{ | ||
Name: "NewRedactedArgument with empty name", | ||
Argument: command.NewRedactedArgument(""), | ||
ExpectedErr: command.ErrInvalidArgumentName, | ||
}, | ||
{ | ||
Name: "NewArguments", | ||
Argument: command.NewArguments( | ||
command.NewArgument("arg1"), | ||
nil, // should be skipped | ||
command.NewRedactedArgument("arg2"), | ||
), | ||
ExpectedCLI: []string{"cmd", "arg1", "arg2"}, | ||
ExpectedLog: "cmd arg1 <****>", | ||
}, | ||
{ | ||
Name: "NewArguments without args", | ||
ExpectedCLI: []string{"cmd"}, | ||
}, | ||
}}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright 2024 The Kanister Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package command | ||
|
||
import "github.com/kanisterio/safecli" | ||
|
||
// New creates a new safecli.Builder with the given command name and arguments. | ||
// If the command name is empty, it will be omitted from the output. | ||
func New(cmdName string, args ...Applier) (*safecli.Builder, error) { | ||
cmd := safecli.NewBuilder() | ||
if cmdName != "" { | ||
cmd.AppendLoggable(cmdName) | ||
} | ||
if err := apply(cmd, args...); err != nil { | ||
return nil, err | ||
} | ||
return cmd, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Copyright 2024 The Kanister Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package command_test | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/kanisterio/safecli/command" | ||
"gopkg.in/check.v1" | ||
) | ||
|
||
func TestCommand(t *testing.T) { check.TestingT(t) } | ||
|
||
type CommandSuite struct{} | ||
|
||
var _ = check.Suite(&CommandSuite{}) | ||
|
||
func (s *CommandSuite) TestCommandNewOK(c *check.C) { | ||
cli := []string{ | ||
"cmd", | ||
"--log-level=info", | ||
"--password=secret", | ||
"arg", | ||
"--dest=/tmp/dir", | ||
"--read-only", | ||
} | ||
log := []string{ | ||
"cmd", | ||
"--log-level=info", | ||
"--password=<****>", | ||
"arg", | ||
"--dest=/tmp/dir", | ||
"--read-only", | ||
} | ||
cmd, err := command.New("cmd", []command.Applier{ | ||
command.NewOptionWithArgument("--log-level", "info"), | ||
command.NewOptionWithRedactedArgument("--password", "secret"), | ||
command.NewArgument("arg"), | ||
command.NewOptionWithArgument("--dest", "/tmp/dir"), | ||
command.NewOption("--read-only", true), | ||
}...) | ||
c.Assert(err, check.IsNil) | ||
c.Assert(cmd.Build(), check.DeepEquals, cli) | ||
c.Assert(cmd.String(), check.Equals, strings.Join(log, " ")) | ||
} | ||
|
||
func (s *CommandSuite) TestCommandNewError(c *check.C) { | ||
cmd, err := command.New("cmd", []command.Applier{ | ||
command.NewOptionWithArgument("--log-level", "info"), | ||
command.NewOptionWithRedactedArgument("--password", "secret"), | ||
command.NewArgument(""), // error argument | ||
}...) | ||
c.Assert(cmd, check.IsNil) | ||
c.Assert(err, check.Equals, command.ErrInvalidArgumentName) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package command | ||
|
||
// Copyright 2024 The Kanister Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
// | ||
// The command package is used to define CLI (Command Line Interface) commands along with their arguments. | ||
// | ||
// Command line arguments are the whitespace-separated tokens given in the shell command used to invoke the program. | ||
// | ||
// A token prefixed with a hyphen delimiter (`-`) is known as an *option*. For example, `-o` or `--option`. | ||
// | ||
// An option may or may not have an associated argument. For example, `--option=value`. | ||
// | ||
// A token without a hyphen delimiter (`-`) is considered an *argument*. For example, `arg1` or `arg2`. | ||
// | ||
// The command package provides a set of interfaces and types for defining and applying arguments to commands. | ||
// | ||
// Check safecli/examples/kopia package for usage of the command package. | ||
// |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright 2024 The Kanister Authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package command | ||
|
||
import ( | ||
"github.com/pkg/errors" | ||
) | ||
|
||
var ( | ||
// ErrInvalidArgumentName is returned when the argument name is empty. | ||
ErrInvalidArgumentName = errors.New("argument name is empty") | ||
// ErrInvalidOptionName is returned when the option name is empty or has no hyphen prefix. | ||
ErrInvalidOptionName = errors.New("option name is empty or has no hyphen prefix") | ||
) |
Oops, something went wrong.