Restructure project directories
Follow the standard of all go projects
This commit is contained in:
parent
741f18efd1
commit
5445ce1ccf
20 changed files with 39 additions and 42 deletions
83
internal/config/cmd.go
Normal file
83
internal/config/cmd.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/akamensky/argparse"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfigPath = ".config/grm/config.yaml"
|
||||
)
|
||||
|
||||
func getDefaultConfigDir() string {
|
||||
// Only systems like Unix, Linux, and Windows systems are supported
|
||||
userHomeDir, _ := os.UserHomeDir()
|
||||
return fmt.Sprintf("%v/%v", userHomeDir, defaultConfigPath)
|
||||
}
|
||||
|
||||
func ParseCliArguments(name, description string, arguments []string) (CliArguments, error) {
|
||||
|
||||
parser := argparse.NewParser(name, description)
|
||||
|
||||
syncCMD := parser.NewCommand("sync", "Synchronize repositories with remote branches, if the repository does not exist, clone it. (If pulling is not possible, the repository will be fetched)")
|
||||
statusCMD := parser.NewCommand("status", "Get information about repositories")
|
||||
|
||||
configFile := parser.String("c", "config-file", &argparse.Options{
|
||||
Default: getDefaultConfigDir(),
|
||||
Help: "Path to the configuration file",
|
||||
})
|
||||
|
||||
version := parser.Flag("v", "version", &argparse.Options{
|
||||
Default: false,
|
||||
Help: "Print version",
|
||||
})
|
||||
|
||||
color := parser.Flag("", "no-color", &argparse.Options{
|
||||
Default: false,
|
||||
Help: "Turn off color printing",
|
||||
})
|
||||
|
||||
limitName := parser.String("n", "name", &argparse.Options{
|
||||
Help: "Limit action to the specified repository name",
|
||||
})
|
||||
|
||||
limitTags := parser.StringList("t", "tag", &argparse.Options{
|
||||
Help: "Limit actions to repositories that contain specific tags",
|
||||
})
|
||||
|
||||
limitRoutines := parser.Int("", "max-concurrent-process", &argparse.Options{
|
||||
Default: 10,
|
||||
Help: "Determine how many tasks can run simultaneously",
|
||||
})
|
||||
|
||||
ignoreSkipped := parser.Flag("", "ignore-skip-flag", &argparse.Options{
|
||||
Help: "Run selected command for all repositories with ignoring the skip flag",
|
||||
})
|
||||
|
||||
if err := parser.Parse(arguments); err != nil {
|
||||
return CliArguments{}, errors.New(parser.Usage("Please follow this help"))
|
||||
}
|
||||
|
||||
if !syncCMD.Happened() && !(*version) && !statusCMD.Happened() {
|
||||
return CliArguments{}, errors.New(errNoCommand)
|
||||
}
|
||||
|
||||
if *limitName != "" && len(*limitTags) != 0 {
|
||||
return CliArguments{}, errors.New(errNameAndTagsTogether)
|
||||
}
|
||||
|
||||
return CliArguments{
|
||||
ConfigurationFile: *configFile,
|
||||
Sync: syncCMD.Happened(),
|
||||
Status: statusCMD.Happened(),
|
||||
Version: *version,
|
||||
Color: !(*color),
|
||||
LimitToName: *limitName,
|
||||
LimitToTags: *limitTags,
|
||||
Routines: *limitRoutines,
|
||||
IgnoreSkipped: *ignoreSkipped,
|
||||
}, nil
|
||||
}
|
||||
83
internal/config/cmd_test.go
Normal file
83
internal/config/cmd_test.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetDefaultConfigDir(t *testing.T) {
|
||||
ud, _ := os.UserHomeDir()
|
||||
|
||||
desiredPath := fmt.Sprintf("%v/%v", ud, defaultConfigPath)
|
||||
|
||||
if getDefaultConfigDir() != desiredPath {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", desiredPath, getDefaultConfigDir())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParsingDefaultArguments(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName", "sync"}
|
||||
|
||||
parseResult, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err.Error())
|
||||
}
|
||||
|
||||
if parseResult.ConfigurationFile != getDefaultConfigDir() {
|
||||
t.Errorf("Default value for configurationFile should be %v, instead of this got %v", getDefaultConfigDir(), parseResult.ConfigurationFile)
|
||||
}
|
||||
|
||||
if parseResult.Sync != true {
|
||||
t.Errorf("Default value for configurationFile should be %v, instead of this got %v", true, parseResult.Sync)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsingWithoutCommand(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName", "--name", "test"}
|
||||
|
||||
results, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, not results: %v", results)
|
||||
}
|
||||
if err.Error() != errNoCommand {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", errNoCommand, err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestParsingNothingProvided(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName"}
|
||||
|
||||
results, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, not results: %v", results)
|
||||
}
|
||||
|
||||
}
|
||||
func TestParsingNameAndTags(t *testing.T) {
|
||||
|
||||
// First item in os.Args is appPath, this have to be mocked
|
||||
fakeOSArgs := []string{"appName", "status", "--tag", "example", "--name", "example"}
|
||||
|
||||
results, err := ParseCliArguments("", "", fakeOSArgs)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, not results: %v", results)
|
||||
}
|
||||
|
||||
if err.Error() != errNameAndTagsTogether {
|
||||
t.Errorf("Expected to get \"%v\", instead of this got \"%v\"", errNameAndTagsTogether, err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
69
internal/config/config_file_parse.go
Normal file
69
internal/config/config_file_parse.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func GetRepositoryConfig(data []byte, fileExtension string) (Configuration, error) {
|
||||
|
||||
var config Configuration
|
||||
var err error
|
||||
|
||||
data = []byte(os.ExpandEnv(string(data)))
|
||||
|
||||
switch fileExtension {
|
||||
case "yaml":
|
||||
err = yaml.Unmarshal(data, &config)
|
||||
default:
|
||||
return Configuration{}, errors.New(errNotSupportedType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return Configuration{}, err
|
||||
}
|
||||
|
||||
if config.Workspace == "" {
|
||||
|
||||
return Configuration{}, errors.New(errMissingWorkspaceField)
|
||||
}
|
||||
// Get counters to check if name or dest values are not duplicated
|
||||
nameFieldCounter := make(map[string][]int)
|
||||
destFieldCounter := make(map[string][]int)
|
||||
|
||||
for index, repo := range config.Repositories {
|
||||
if repo.Src == "" {
|
||||
errorMessage := fmt.Sprintf(errMissingSrcField, index)
|
||||
return Configuration{}, errors.New(errorMessage)
|
||||
}
|
||||
if repo.Name == "" {
|
||||
splittedGit := strings.Split(repo.Src, "/")
|
||||
nameWithExcention := splittedGit[len(splittedGit)-1]
|
||||
name := strings.Split(nameWithExcention, ".")[0]
|
||||
config.Repositories[index].Name = name
|
||||
}
|
||||
if repo.Dest == "" {
|
||||
config.Repositories[index].Dest = config.Repositories[index].Name
|
||||
}
|
||||
nameFieldCounter[config.Repositories[index].Name] = append(nameFieldCounter[config.Repositories[index].Name], index)
|
||||
destFieldCounter[config.Repositories[index].Dest] = append(destFieldCounter[config.Repositories[index].Dest], index)
|
||||
}
|
||||
|
||||
for rowId, items := range nameFieldCounter {
|
||||
if len(items) != 1 {
|
||||
return Configuration{}, getDuplicateFieldError("name", rowId, items)
|
||||
}
|
||||
}
|
||||
|
||||
for rowId, items := range destFieldCounter {
|
||||
if len(items) != 1 {
|
||||
return Configuration{}, getDuplicateFieldError("dest", rowId, items)
|
||||
}
|
||||
}
|
||||
|
||||
return config, err
|
||||
}
|
||||
157
internal/config/config_file_parse_test.go
Normal file
157
internal/config/config_file_parse_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotSupportedFileExcension(t *testing.T) {
|
||||
|
||||
_, err := GetRepositoryConfig([]byte("test"), "custom")
|
||||
if err == nil {
|
||||
t.Error("Expected to get error")
|
||||
}
|
||||
if err.Error() != errNotSupportedType {
|
||||
t.Errorf("Expected to get %v, instead of this got %v", errNotSupportedType, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRepositoryConfigFromYaml(t *testing.T) {
|
||||
|
||||
var exampleYamlConfig = []byte(`
|
||||
workspace: ${HOME}
|
||||
repositories:
|
||||
- src: "https://github.com/example/example.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
- src: https://github.com/example/example2.git
|
||||
tags:
|
||||
- "example"
|
||||
`)
|
||||
|
||||
homedir, _ := os.UserHomeDir()
|
||||
|
||||
var destinationConfiguration = Configuration{
|
||||
Workspace: homedir,
|
||||
Repositories: []RepositoryConfig{
|
||||
{
|
||||
Name: "custom_example",
|
||||
Dest: "example/path",
|
||||
Src: "https://github.com/example/example.git",
|
||||
},
|
||||
{
|
||||
Name: "example2",
|
||||
Src: "https://github.com/example/example2.git",
|
||||
Dest: "example2",
|
||||
Tags: []string{"example"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, err := GetRepositoryConfig(exampleYamlConfig, "yaml")
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err.Error())
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(result.Repositories, destinationConfiguration.Repositories) {
|
||||
t.Errorf("Default value for configurationFile should be:\n %v \ninstead of this got:\n %v", result, destinationConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrongYamlFormat(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`---
|
||||
workspace: "/test"
|
||||
repositories:
|
||||
- src: "https://github.com/example/example.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
`)
|
||||
|
||||
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
expectedError := "yaml: line 2: found character that cannot start any token"
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingWorkspaceRequiredField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`---
|
||||
repositories:
|
||||
- src: "https://github.com/example/example.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
`)
|
||||
|
||||
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
if err.Error() != errMissingWorkspaceField {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", errMissingWorkspaceField, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingSourceRequiredField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`---
|
||||
workspace: /tmp
|
||||
repositories:
|
||||
- dest: "example/path"
|
||||
name: "custom_example"
|
||||
`)
|
||||
_, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
expectedError := fmt.Sprintf(errMissingSrcField, 0)
|
||||
|
||||
if err.Error() != expectedError {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicatedNameField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`
|
||||
workspace: "/tmp"
|
||||
repositories:
|
||||
- src: "https://github.com/example/example1.git"
|
||||
dest: "example/path"
|
||||
name: "custom_example"
|
||||
- src: "https://github.com/example/example2.git"
|
||||
name: "example2"
|
||||
- src: "https://github.com/example/example2.git"
|
||||
`)
|
||||
result, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected result: %v", result)
|
||||
|
||||
}
|
||||
expectedError := getDuplicateFieldError("name", "example2", []int{1, 2})
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Errorf("Expected to get error with value %v, instead of this got: %v", expectedError.Error(), err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicatedDestField(t *testing.T) {
|
||||
exampleWrongYamlConfig := []byte(`
|
||||
workspace: "/tmp"
|
||||
repositories:
|
||||
- src: "https://github.com/example/example1.git"
|
||||
dest: "example/path"
|
||||
- src: "https://github.com/example/example2.git"
|
||||
dest: "example"
|
||||
- src: "https://github.com/example/example3.git"
|
||||
dest: "example"
|
||||
`)
|
||||
result, err := GetRepositoryConfig(exampleWrongYamlConfig, "yaml")
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected result: %v", result)
|
||||
}
|
||||
|
||||
expectedError := getDuplicateFieldError("dest", "example", []int{1, 2})
|
||||
|
||||
if err.Error() != expectedError.Error() {
|
||||
t.Errorf("Expected to get error with value \"%v\", instead of this got: \"%v\"", expectedError, err)
|
||||
}
|
||||
}
|
||||
25
internal/config/errors.go
Normal file
25
internal/config/errors.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
errNoCommand = "at least one command must be specified, use help to get commands"
|
||||
errNotSupportedType = "not supported configuration type"
|
||||
errMissingWorkspaceField = "missing required \"workspace\" field"
|
||||
errMissingSrcField = "missing required field the \"src\" in row %v"
|
||||
errNameAndTagsTogether = "name and tags arguments connot be used together"
|
||||
)
|
||||
|
||||
func getDuplicateFieldError(field string, name string, rows []int) error {
|
||||
|
||||
var rowsInString []string
|
||||
for _, row := range rows {
|
||||
rowsInString = append(rowsInString, strconv.Itoa(row))
|
||||
}
|
||||
|
||||
return fmt.Errorf("the %v \"%v\" is duplicated in rows: %v", field, name, strings.Join(rowsInString, ","))
|
||||
}
|
||||
26
internal/config/structures.go
Normal file
26
internal/config/structures.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package config
|
||||
|
||||
type Configuration struct {
|
||||
Workspace string
|
||||
Repositories []RepositoryConfig
|
||||
}
|
||||
|
||||
type RepositoryConfig struct {
|
||||
Name string `yaml:",omitempty"`
|
||||
Src string
|
||||
Dest string `yaml:",omitempty"`
|
||||
Tags []string `yaml:",omitempty"`
|
||||
Skip bool
|
||||
}
|
||||
|
||||
type CliArguments struct {
|
||||
ConfigurationFile string
|
||||
Sync bool
|
||||
Status bool
|
||||
Version bool
|
||||
Color bool
|
||||
LimitToName string
|
||||
LimitToTags []string
|
||||
Routines int
|
||||
IgnoreSkipped bool
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue