Restructure project directories

Follow the standard of all go projects
This commit is contained in:
Mikołaj Pęczkowski 2024-01-13 15:39:40 +01:00
parent 741f18efd1
commit 5445ce1ccf
20 changed files with 39 additions and 42 deletions

83
internal/config/cmd.go Normal file
View 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
}

View 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())
}
}

View 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
}

View 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
View 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, ","))
}

View 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
}