-
-
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.
- Loading branch information
Showing
8 changed files
with
325 additions
and
312 deletions.
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,114 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/erikgeiser/promptkit/selection" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
type Command struct { | ||
Name string `yaml:"-"` | ||
Description string | ||
Run string | ||
Env map[string]string | ||
Inputs Inputs | ||
Commands Commands | ||
} | ||
|
||
func (command *Command) HasSubCommands() bool { | ||
return len(command.Commands) > 0 | ||
} | ||
|
||
type Commands []Command | ||
|
||
func (commands *Commands) Get(name string) *Command { | ||
for _, command := range *commands { | ||
if command.Name == name { | ||
return &command | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (commands *Commands) Inputs() Inputs { | ||
inputs := make(Inputs, 0) | ||
for _, command := range *commands { | ||
inputs = append(inputs, command.Inputs...) | ||
} | ||
return inputs | ||
} | ||
|
||
func (initCommands Commands) Select() (Commands, error) { | ||
askedCommands := make(Commands, 0) | ||
commands := initCommands | ||
|
||
for numCommands := len(commands); numCommands > 0; numCommands = len(commands) { | ||
choices := make([]*selection.Choice, numCommands) | ||
|
||
for i, command := range commands { | ||
choices[i] = &selection.Choice{String: command.Name, Value: command} | ||
} | ||
|
||
prompt := promptStyle.Render("Choose command") | ||
sp := selection.New(prompt, choices) | ||
|
||
if numCommands <= minChoiceFiltering { | ||
sp.Filter = nil | ||
} | ||
|
||
choice, err := sp.RunPrompt() | ||
if err != nil { | ||
return askedCommands, err | ||
} | ||
|
||
command := choice.Value.(Command) | ||
askedCommands = append(askedCommands, command) | ||
commands = command.Commands | ||
} | ||
|
||
return askedCommands, nil | ||
} | ||
|
||
func (x *Commands) UnmarshalYAML(value *yaml.Node) error { | ||
if value.Kind != yaml.MappingNode || len(value.Content)%2 != 0 { | ||
return fmt.Errorf("line %d: cannot unmarshal commands into map", value.Line) | ||
} | ||
|
||
commands := Commands{} | ||
content := value.Content | ||
|
||
for len(content) > 0 { | ||
keyNode := content[0] | ||
valueNode := content[1] | ||
|
||
if keyNode.Kind != yaml.ScalarNode || !(valueNode.Kind == yaml.MappingNode || valueNode.Kind == yaml.ScalarNode) { | ||
return fmt.Errorf("line %d: unexpected node type", keyNode.Line) | ||
} | ||
|
||
var command Command | ||
if valueNode.Kind == yaml.ScalarNode { | ||
command = Command{ | ||
Name: keyNode.Value, | ||
Run: valueNode.Value, | ||
} | ||
} else { | ||
if err := valueNode.Decode(&command); err != nil { | ||
return err | ||
} | ||
if numCommands := len(command.Commands); command.Run == "" && numCommands == 0 { | ||
return fmt.Errorf("line %d: '%s' command missing run or commands attribute", keyNode.Line, keyNode.Value) | ||
} else if command.Run != "" && numCommands > 0 { | ||
return fmt.Errorf("line %d: '%s' command cannot have both run and commands attribute", keyNode.Line, keyNode.Value) | ||
} | ||
command.Name = keyNode.Value | ||
} | ||
commands = append(commands, command) | ||
|
||
content = content[2:] | ||
} | ||
|
||
*x = commands | ||
|
||
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,198 @@ | ||
package main | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
func TestCommandHasSubCommands(t *testing.T) { | ||
command := Command{ | ||
Name: "decepticons", | ||
Commands: Commands{ | ||
Command{Name: "punish"}, | ||
Command{Name: "enslave"}, | ||
}, | ||
} | ||
if !command.HasSubCommands() { | ||
t.Errorf("Expected to have sub commands") | ||
} | ||
} | ||
|
||
func TestCommandsUnmarshalYAML_WithRunAndCommands(t *testing.T) { | ||
var commands Commands | ||
content := ` | ||
protect: | ||
run: echo fail | ||
commands: | ||
foobar: | ||
run: echo foobar | ||
punish: | ||
run: echo Punish and enslave | ||
` | ||
expected := "line 2: 'protect' command cannot have both run and commands attribute" | ||
actual := yaml.Unmarshal([]byte(content), &commands) | ||
|
||
if expected != actual.Error() { | ||
t.Errorf("Expected error, but received: %s", actual) | ||
} | ||
} | ||
|
||
func TestCommandsUnmarshalYAML_WithoutRunAndCommands(t *testing.T) { | ||
var commands Commands | ||
content := ` | ||
protect: {} | ||
punish: | ||
run: echo Punish and enslave | ||
` | ||
expected := "line 2: 'protect' command missing run or commands attribute" | ||
actual := yaml.Unmarshal([]byte(content), &commands) | ||
|
||
if expected != actual.Error() { | ||
t.Errorf("Expected error, but received: %s", actual) | ||
} | ||
} | ||
|
||
func TestCommandsUnmarshalYAML_WithCommands(t *testing.T) { | ||
var actual Commands | ||
content := ` | ||
protect: | ||
commands: | ||
foobar: | ||
run: echo foobar | ||
punish: | ||
commands: | ||
foobaz: | ||
run: echo foobaz | ||
` | ||
expected := Commands{ | ||
Command{ | ||
Name: "protect", | ||
Commands: Commands{ | ||
Command{ | ||
Name: "foobar", | ||
Run: "echo foobar", | ||
}, | ||
}, | ||
}, | ||
Command{ | ||
Name: "punish", | ||
Commands: Commands{ | ||
Command{ | ||
Name: "foobaz", | ||
Run: "echo foobaz", | ||
}, | ||
}, | ||
}, | ||
} | ||
err := yaml.Unmarshal([]byte(content), &actual) | ||
|
||
if err != nil { | ||
t.Errorf("Received error from parser: %s", err) | ||
} | ||
|
||
if !reflect.DeepEqual(actual, expected) { | ||
fatalDiff(t, expected, actual) | ||
} | ||
} | ||
|
||
func TestCommandsUnmarshalYAML_WithRun(t *testing.T) { | ||
var actual Commands | ||
content := ` | ||
protect: | ||
run: echo Protect all sentient life forms | ||
punish: | ||
run: echo Punish and enslave | ||
` | ||
expected := Commands{ | ||
Command{ | ||
Name: "protect", | ||
Run: "echo Protect all sentient life forms", | ||
}, | ||
Command{ | ||
Name: "punish", | ||
Run: "echo Punish and enslave", | ||
}, | ||
} | ||
err := yaml.Unmarshal([]byte(content), &actual) | ||
|
||
if err != nil { | ||
t.Errorf("Received error from parser: %s", err) | ||
} | ||
|
||
if !reflect.DeepEqual(actual, expected) { | ||
fatalDiff(t, expected, actual) | ||
} | ||
} | ||
|
||
func TestCommandsUnmarshalYAML_Shorthand(t *testing.T) { | ||
var actual Commands | ||
content := ` | ||
protect: echo Protect all sentient life forms | ||
punish: echo Punish and enslave | ||
` | ||
expected := Commands{ | ||
Command{ | ||
Name: "protect", | ||
Run: "echo Protect all sentient life forms", | ||
}, | ||
Command{ | ||
Name: "punish", | ||
Run: "echo Punish and enslave", | ||
}, | ||
} | ||
err := yaml.Unmarshal([]byte(content), &actual) | ||
|
||
if err != nil { | ||
t.Errorf("Received error from parser: %s", err) | ||
} | ||
|
||
if !reflect.DeepEqual(actual, expected) { | ||
fatalDiff(t, expected, actual) | ||
} | ||
} | ||
|
||
func TestCommandsGet(t *testing.T) { | ||
commands := Commands{ | ||
Command{ | ||
Name: "a", | ||
}, | ||
Command{ | ||
Name: "b", | ||
}, | ||
} | ||
|
||
expected := &commands[1] | ||
actual := commands.Get("b") | ||
if !reflect.DeepEqual(actual, expected) { | ||
fatalDiff(t, expected, actual) | ||
} | ||
|
||
expected = nil | ||
actual = commands.Get("c") | ||
if !reflect.DeepEqual(actual, expected) { | ||
fatalDiff(t, expected, actual) | ||
} | ||
} | ||
|
||
func TestCommandsInputs(t *testing.T) { | ||
input1 := Input{} | ||
input2 := Input{} | ||
commands := Commands{ | ||
Command{ | ||
Name: "a", | ||
Inputs: Inputs{input1}, | ||
}, | ||
Command{ | ||
Name: "b", | ||
Inputs: Inputs{input2}, | ||
}, | ||
} | ||
|
||
expected := Inputs{input1, input2} | ||
actual := commands.Inputs() | ||
if !reflect.DeepEqual(actual, expected) { | ||
fatalDiff(t, expected, actual) | ||
} | ||
} |
Oops, something went wrong.