Skip to content

Commit

Permalink
Add the include command and some tests, support to include other conf…
Browse files Browse the repository at this point in the history
…ig files (#103)
  • Loading branch information
mstmdev authored Feb 28, 2023
1 parent 2e08f7f commit 0645cab
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 106 deletions.
54 changes: 0 additions & 54 deletions command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,60 +36,6 @@ func Exec(conf string) error {
return commands.Exec()
}

// ParseConfigFile parse the config file to a command list
func ParseConfigFile(path string) (commands *Commands, err error) {
data, err := os.ReadFile(path)
if err != nil {
return
}
var c Config
err = yaml.Unmarshal(data, &c)
if err != nil {
return
}
return ParseConfig(c)
}

// ParseConfig parse the config to a command list
func ParseConfig(conf Config) (commands *Commands, err error) {
commands = &Commands{
Name: conf.Name,
}
commands.Init, err = parseCommands(conf.Init)
if err != nil {
return nil, err
}
commands.Actions, err = parseCommands(conf.Actions)
if err != nil {
return nil, err
}
commands.Clear, err = parseCommands(conf.Clear)
if err != nil {
return nil, err
}
return commands, nil
}

func parseCommands(actions []Action) (commands []Command, err error) {
for _, action := range actions {
var c Command
for name, fn := range allCommands {
if _, ok := action[name]; ok {
c, err = fn(action)
break
}
}
if err != nil {
return nil, err
}
if c == nil {
return nil, errUnsupportedCommand
}
commands = append(commands, c)
}
return commands, nil
}

func parse[T Command](a Action) (c T, err error) {
out, err := yaml.Marshal(a)
if err != nil {
Expand Down
48 changes: 0 additions & 48 deletions command/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,54 +49,6 @@ func TestExec_ConfigFileNotExist(t *testing.T) {
}
}

func TestParseConfigFile_ConfigFileNotExist(t *testing.T) {
_, err := ParseConfigFile("./example/notexist.yaml")
if !os.IsNotExist(err) {
t.Errorf("ParseConfigFile expect to get a not exist error, but get %v", err)
}
}

func TestParseConfigFile_InvalidConfigFile(t *testing.T) {
_, err := ParseConfigFile("./command_test.go")
if err == nil {
t.Errorf("ParseConfigFile expect get an error, but get nil")
}
}

func TestParseConfig_UnsupportedCommand(t *testing.T) {
testCases := []struct {
name string
conf Config
}{
{"unsupported command in init", Config{Init: []Action{{"unsupported-command": ""}}}},
{"unsupported command in actions", Config{Actions: []Action{{"unsupported-command": ""}}}},
{"unsupported command in clear", Config{Clear: []Action{{"unsupported-command": ""}}}},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := ParseConfig(tc.conf)
if !errors.Is(err, errUnsupportedCommand) {
t.Errorf("ParseConfig expect get error => %v, but get %v", errUnsupportedCommand, err)
}
})
}
}

func TestParseConfig_WithIllegalField(t *testing.T) {
conf := Config{
Name: "invalid command",
}
action := make(Action)
action["cp"] = ""
action["source"] = errMarshaler{}
conf.Actions = append(conf.Actions, action)
_, err := ParseConfig(conf)
if !errors.Is(err, errMarshalYamlMock) {
t.Errorf("ParseConfig expect get error => %v, but get %v", errMarshalYamlMock, err)
}
}

var (
errMarshalYamlMock = errors.New("marshal yaml error mock")

Expand Down
11 changes: 7 additions & 4 deletions command/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package command

// Config the config structure defined a series of commands
type Config struct {
Name string `yaml:"name"`
Init []Action `yaml:"init"`
Actions []Action `yaml:"actions"`
Clear []Action `yaml:"clear"`
Name string `yaml:"name"`
// IncludePath the root directory of the include files, include path is the directory of current config file by default
IncludePath string `yaml:"include_path"`
Include []string `yaml:"include"`
Init []Action `yaml:"init"`
Actions []Action `yaml:"actions"`
Clear []Action `yaml:"clear"`
}

// Action contain the command action name and some parameters that current command needed
Expand Down
13 changes: 13 additions & 0 deletions command/example/include/actions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: actions step
include:
- actions_step1.yaml
- actions_step2.yaml
init:
- print:
input: call init from actions.yaml
actions:
- print:
input: call actions from actions.yaml
clear:
- print:
input: call clear from actions.yaml
10 changes: 10 additions & 0 deletions command/example/include/actions_step1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: actions step 1
init:
- print:
input: call init from actions_step1.yaml
actions:
- print:
input: call actions from actions_step1.yaml
clear:
- print:
input: call clear from actions_step1.yaml
10 changes: 10 additions & 0 deletions command/example/include/actions_step2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: actions step 2
init:
- print:
input: call init from actions_step2.yaml
actions:
- print:
input: call actions from actions_step2.yaml
clear:
- print:
input: call clear from actions_step2.yaml
17 changes: 17 additions & 0 deletions command/example/include/clear.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: clear step
include:
- clear_step1.yaml
- clear_step2.yaml
init:
- print:
input: call init from clear.yaml
actions:
- print:
input: call actions from clear.yaml
clear:
- print:
input: call clear from clear.yaml
- rm:
source: ./source
- rm:
source: ./dest
10 changes: 10 additions & 0 deletions command/example/include/clear_step1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: clear step 1
init:
- print:
input: call init from clear_step1.yaml
actions:
- print:
input: call actions from clear_step1.yaml
clear:
- print:
input: call clear from clear_step1.yaml
10 changes: 10 additions & 0 deletions command/example/include/clear_step2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: clear step 2
init:
- print:
input: call init from clear_step2.yaml
actions:
- print:
input: call actions from clear_step2.yaml
clear:
- print:
input: call clear from clear_step2.yaml
15 changes: 15 additions & 0 deletions command/example/include/include.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: command examples with include
include:
- init_source.yaml
- init_dest.yaml
- actions.yaml
- clear.yaml
init:
- print:
input: call init from include.yaml
actions:
- print:
input: call actions from include.yaml
clear:
- print:
input: call clear from include.yaml
3 changes: 3 additions & 0 deletions command/example/include/infinite_recursion.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: command examples of infinite recursion
include:
- infinite_recursion.yaml
6 changes: 6 additions & 0 deletions command/example/include/init_dest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: init dest path
init:
- print:
input: call init from init_dest.yaml
- mkdir:
source: ./dest
6 changes: 6 additions & 0 deletions command/example/include/init_source.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: init source path
init:
- print:
input: call init from init_source.yaml
- mkdir:
source: ./source
121 changes: 121 additions & 0 deletions command/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package command

import (
"errors"
"os"
"path/filepath"

"gopkg.in/yaml.v3"
)

var (
errInfiniteRecursion = errors.New("find an infinite recursion")
)

const (
maxRecursionDepth = 10000
)

type parser struct {
c int // check infinite recursion
}

func newParser() *parser {
return &parser{}
}

func (p *parser) ParseConfigFile(path string) (commands *Commands, err error) {
data, err := os.ReadFile(path)
if err != nil {
return
}
var c Config
err = yaml.Unmarshal(data, &c)
if err != nil {
return
}
if len(c.IncludePath) == 0 {
c.IncludePath = filepath.Dir(path)
}
return p.ParseConfig(c)
}

func (p *parser) ParseConfig(conf Config) (commands *Commands, err error) {
commands, err = p.parseConfig(conf)
if err != nil {
return nil, err
}

var (
init []Command
actions []Command
clear []Command
)
for _, f := range conf.Include {
baseCommands, err := p.ParseConfigFile(filepath.Join(conf.IncludePath, f))
if err != nil {
return nil, err
}
init = append(init, baseCommands.Init...)
actions = append(actions, baseCommands.Actions...)
clear = append(clear, baseCommands.Clear...)
}

commands.Init = append(init, commands.Init...)
commands.Actions = append(actions, commands.Actions...)
commands.Clear = append(clear, commands.Clear...)
return commands, nil
}

func (p *parser) parseConfig(conf Config) (commands *Commands, err error) {
p.c++
if p.c > maxRecursionDepth {
return nil, errInfiniteRecursion
}
commands = &Commands{
Name: conf.Name,
}
commands.Init, err = p.parseCommands(conf.Init)
if err != nil {
return nil, err
}
commands.Actions, err = p.parseCommands(conf.Actions)
if err != nil {
return nil, err
}
commands.Clear, err = p.parseCommands(conf.Clear)
if err != nil {
return nil, err
}
return commands, nil
}

func (p *parser) parseCommands(actions []Action) (commands []Command, err error) {
for _, action := range actions {
var c Command
for name, fn := range allCommands {
if _, ok := action[name]; ok {
c, err = fn(action)
break
}
}
if err != nil {
return nil, err
}
if c == nil {
return nil, errUnsupportedCommand
}
commands = append(commands, c)
}
return commands, nil
}

// ParseConfigFile parse the config file to a command list
func ParseConfigFile(path string) (commands *Commands, err error) {
return newParser().ParseConfigFile(path)
}

// ParseConfig parse the config to a command list
func ParseConfig(conf Config) (commands *Commands, err error) {
return newParser().ParseConfig(conf)
}
Loading

0 comments on commit 0645cab

Please sign in to comment.