template / commit
Added more tests.
Makefile | 9 +++++++-- config.go | 10 ++++------ config_test.go | 46 ++++++++++++++++++++++++++++++++------------- extras_test.go | 9 +++++++++ file.go | 13 ++++--------- main.go | 11 +++++++++-- name_pattern.go | 9 +++++---- profile.go | 39 +++++++++++++++++--------------------- profile_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++------ utils.go | 8 ++------ utils_test.go | 11 ++++------- 11 files changed, 146 insertions(+), 77 deletions(-) diff --git a/Makefile b/Makefile index abda531..28a2431 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,9 @@ -SHELL := pwsh +ifeq ($(OS),Windows_NT) + SHELL := pwsh.exe +else + SHELL := pwsh +endif + .SHELLFLAGS := -Command BINARY_NAME := template @@ -12,7 +17,7 @@ install: build ./scripts/clean-install.ps1 test: build - @New-Item -ItemType Directory -Force -Path ./test | out-null + New-Item -ItemType Directory -Force -Path ./test | out-null go test -v ./... -coverprofile="./test/coverage.out" test_coverage: test diff --git a/config.go b/config.go index c8a6526..dab0302 100644 --- a/config.go +++ b/config.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "os/user" "gopkg.in/yaml.v3" ) @@ -178,7 +179,7 @@ func validateConfig(config Config) error { } error := validateProfile(profile) if error != nil { - return error + return fmt.Errorf("error validating profile %s: %s", profile.Name, error) } } return nil @@ -214,13 +215,10 @@ func saveConfig(path string, config Config) error { return nil } -func getOrCreateConfig(configFilePath string) (Config, error) { +func getOrCreateConfig(configFilePath string, usr user.User) (Config, error) { emptyConfig := Config{} - fullConfigPath, err := getFullPath(configFilePath) - if err != nil { - return emptyConfig, fmt.Errorf("failed to get full path for config file %v: %v", configFilePath, err) - } + fullConfigPath := getFullPath(configFilePath, usr) // try and load config config, err := loadConfig(fullConfigPath) diff --git a/config_test.go b/config_test.go index eca3067..59a30ff 100644 --- a/config_test.go +++ b/config_test.go @@ -3,17 +3,21 @@ package main import ( "errors" "os" + "os/user" "testing" "github.com/stretchr/testify/assert" ) func TestSaveLoadConfig(t *testing.T) { + usr, err := user.Current() + assert.Nil(t, err) + config := defaultConfig() configPath := "./test/test-config.yaml" // save config - err := saveConfig(configPath, config) + err = saveConfig(configPath, config) assert.Nil(t, err, "Error saving config") // load config @@ -44,7 +48,7 @@ func TestSaveLoadConfig(t *testing.T) { assert.True(t, errors.Is(err, os.ErrNotExist), "Expected config file to not exist, but it does") // get and create config - loadedConfig, err = getOrCreateConfig(configPath) + loadedConfig, err = getOrCreateConfig(configPath, *usr) assert.Nil(t, err, "Error loading config") // test config @@ -55,7 +59,7 @@ func TestSaveLoadConfig(t *testing.T) { assert.Nil(t, err, "Expected config file to exist, but it does not") // get and create config - loadedConfig, err = getOrCreateConfig(configPath) + loadedConfig, err = getOrCreateConfig(configPath, *usr) assert.Nil(t, err, "Error loading config") // test config @@ -68,14 +72,15 @@ func TestSaveLoadConfig(t *testing.T) { } func TestGetOrCreateConfig(t *testing.T) { + usr, err := user.Current() + assert.Nil(t, err) testFailure := func(configPath string) { - _, err := getOrCreateConfig(configPath) + _, err := getOrCreateConfig(configPath, *usr) if err == nil { t.Errorf("Expected to fail to get or create config, but succeeded") } } - testFailure("~~~") testFailure("invalid/path/invalid-config.yaml") } @@ -88,23 +93,38 @@ func TestValidateDefaultConfig(t *testing.T) { } func TestValidateConfig(t *testing.T) { - testFailure := func(config Config) { + testFailure := func(config Config, errorText string) { err := validateConfig(config) if err == nil { t.Errorf("Expected to fail to validate config, but succeeded") } + assert.Contains(t, err.Error(), errorText) } - testFailure(Config{}) - testFailure(Config{Profiles: []Profile{}}) + testFailure(Config{}, "no profiles found") + testFailure(Config{Profiles: []Profile{}}, "no profiles found") + testFailure(Config{Profiles: []Profile{ + { + Name: "daily", + IsDefault: true, + NamePatterns: []string{"daily"}, + FileTemplate: "foo", + }, + }}, "error validating profile") testFailure(Config{Profiles: []Profile{ { - Name: "daily", - IsDefault: true, + Name: "daily", + IsDefault: true, + NamePatterns: []string{"daily"}, + Type: FileType, + FileTemplate: "foo", }, { - Name: "daily2", - IsDefault: true, + Name: "daily2", + IsDefault: true, + NamePatterns: []string{"daily"}, + Type: FileType, + FileTemplate: "foo", }, - }}) + }}, "multiple default profiles found") } diff --git a/extras_test.go b/extras_test.go index 9a72542..5719ab0 100644 --- a/extras_test.go +++ b/extras_test.go @@ -49,4 +49,13 @@ func TestExtras(t *testing.T) { t.Error("Error loading extras: ", error) } assert.Equal(t, extras, loadedExtras) + + // test path errors + _, error = loadExtras("non-existent-file.yaml") + assert.NotNil(t, error, "Expected to fail to load extras, but succeeded") + + os.WriteFile(file.Name(), []byte("invalid yaml"), 0644) + + _, error = loadExtras(fileName) + assert.NotNil(t, error, "Expected to fail to parse extras, but succeeded") } diff --git a/file.go b/file.go index f604e55..e364f77 100644 --- a/file.go +++ b/file.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "os/user" "path/filepath" "strings" "text/template" @@ -29,19 +30,13 @@ func createFilePatternData(now time.Time, projectName string, name string, extra } } -func createTemplateFile(srcPath string, destPath string, projectName string, name string, extras map[string]string) error { +func createTemplateFile(srcPath string, destPath string, projectName string, name string, extras map[string]string, usr user.User) error { now := time.Now() filePatternData := createFilePatternData(now, projectName, name, extras) // standardize paths - srcPath, err := getFullPath(srcPath) - if err != nil { - return err - } - destPath, err = getFullPath(destPath) - if err != nil { - return err - } + srcPath = getFullPath(srcPath, usr) + destPath = getFullPath(destPath, usr) // parse template srcName := filepath.Base(srcPath) // srcName is required because we are sending files to ParseFiles diff --git a/main.go b/main.go index 028a555..c0ea6ab 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "os/user" "strings" "github.com/urfave/cli/v2" // imports as package "cli" @@ -17,6 +18,7 @@ func main() { var extrasFilePath string var useDefaultConfig bool var err error + var usr *user.User app := &cli.App{ Name: "template", @@ -65,12 +67,17 @@ func main() { }, Action: func(context *cli.Context) error { + usr, err = user.Current() + if err != nil { + log.Fatal(err) + } + // get config var config Config if useDefaultConfig { config = defaultConfig() } else { - config, err = getOrCreateConfig(configFilePath) + config, err = getOrCreateConfig(configFilePath, *usr) if err != nil { return err } @@ -136,7 +143,7 @@ func main() { } // process profile - err = processProfile(profile, projectName, extras) + err = processProfile(profile, projectName, extras, *usr) if err != nil { return fmt.Errorf("error processing profile: %v", err) } diff --git a/name_pattern.go b/name_pattern.go index c383b63..32fb4d3 100644 --- a/name_pattern.go +++ b/name_pattern.go @@ -2,6 +2,7 @@ package main import ( "errors" + "fmt" "strings" "text/template" "time" @@ -42,13 +43,13 @@ func createNewNameForProfile(now time.Time, projectName string, profile Profile, template, err := template.Parse(namePattern) if err != nil { - return "", err + return "", fmt.Errorf("error parsing name pattern %d: %w", index, err) } sb := new(strings.Builder) err = template.Execute(sb, patternData) if err != nil { - return "", err + return "", fmt.Errorf("error executing name pattern %d: %w", index, err) } newName := sb.String() @@ -68,11 +69,11 @@ func createNewNameForProfile(now time.Time, projectName string, profile Profile, // if getVersion was used, but this isn't the last pattern, report an error if useIndex && !isLastPattern { - return "", errors.New("Cannot use function getVersion in a pattern that is not the last pattern") + return "", errors.New("cannot use function getVersion in a pattern that is not the last pattern") } startingVersion++ } } - return "", errors.New("Couldn't find a new name") + return "", errors.New("couldn't find a new name") } diff --git a/profile.go b/profile.go index fd19ac1..888ee70 100644 --- a/profile.go +++ b/profile.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "os/exec" + "os/user" "path/filepath" "runtime" "strings" @@ -59,24 +60,18 @@ func findProfile(config Config, profileName string) (Profile, error) { return p, nil } } - } else { - // find profile by name - for _, p := range config.Profiles { - if p.Name == profileName { - return p, nil - } + return Profile{}, errors.New("no default profile found") + } + // find profile by name + for _, p := range config.Profiles { + if p.Name == profileName { + return p, nil } - return Profile{}, fmt.Errorf("unable to find profile '%v'", profileName) } - return Profile{}, errors.New("couldn't find profile") + return Profile{}, fmt.Errorf("unable to find profile '%v'", profileName) } -func processProfile(profile Profile, projectName string, extras map[string]string) error { - // validate profile - if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 { - return errors.New("no name patterns specified") - } - +func processProfile(profile Profile, projectName string, extras map[string]string, usr user.User) error { // decide if the profile should run on this OS profileOs := profile.OS isWindows := runtime.GOOS == "windows" @@ -95,10 +90,7 @@ func processProfile(profile Profile, projectName string, extras map[string]strin } if profile.Path != "" { // change cwd to the profile's path, if one is specified - path, err := getFullPath(profile.Path) - if err != nil { - return fmt.Errorf("unable to get full path for '%v': %v", profile.Path, err) - } + path := getFullPath(profile.Path, usr) err = os.Chdir(path) if err != nil { return fmt.Errorf("unable to change directory to %v: %v", path, err) @@ -153,7 +145,7 @@ func processProfile(profile Profile, projectName string, extras map[string]strin useTemplate := profile.FileTemplate != "" if useTemplateFile { fmt.Printf("Creating file '%v' from template: %v\n", fullPath, profile.FileTemplatePath) - err := createTemplateFile(profile.FileTemplatePath, fullPath, projectName, newName, extras) + err := createTemplateFile(profile.FileTemplatePath, fullPath, projectName, newName, extras, usr) if err != nil { return fmt.Errorf("error creating template file: %v", err) } @@ -203,15 +195,18 @@ func validateProfile(profile Profile) error { if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 { return fmt.Errorf("no name patterns found for profile %s", profile.Name) } - if profile.Path != "" && strings.Contains(profile.Path, "\\") { - return fmt.Errorf("invalid path for profile %s: %s", profile.Name, profile.Path) - } if profile.Type == "" || (profile.Type != FileType && profile.Type != FolderType && profile.Type != ScriptType) { return fmt.Errorf("invalid profile type %s for profile %s", profile.Type, profile.Name) } if profile.Type != FileType && profile.FileTemplatePath != "" { return fmt.Errorf("file template path is not supported for profile %s, which has Type=%s", profile.Name, profile.Type) } + if profile.Type != FileType && profile.FileTemplate != "" { + return fmt.Errorf("file template is not supported for profile %s, which has Type=%s", profile.Name, profile.Type) + } + if profile.Type == FileType && profile.FileTemplatePath != "" && profile.FileTemplate != "" { + return fmt.Errorf("both file template path and file template are specified for profile %s", profile.Name) + } if profile.Type == ScriptType && profile.NamePatterns != nil && len(profile.NamePatterns) > 1 { return fmt.Errorf("multiple name patterns are not supported for profile %s, which has Type=%s", profile.Name, profile.Type) } diff --git a/profile_test.go b/profile_test.go index 2eb60aa..f2c747f 100644 --- a/profile_test.go +++ b/profile_test.go @@ -23,6 +23,18 @@ func TestValidateProfile(t *testing.T) { Name: "daily", NamePatterns: []string{"{{.Date}}"}, }) + testSuccess(Profile{ + Type: FileType, + Name: "daily", + NamePatterns: []string{"{{.Date}}"}, + FileTemplate: "foo", + }) + testSuccess(Profile{ + Type: FileType, + Name: "daily", + NamePatterns: []string{"{{.Date}}"}, + FileTemplatePath: "foo", + }) // empty name testFailure(Profile{ @@ -51,12 +63,42 @@ func TestValidateProfile(t *testing.T) { Name: "daily", NamePatterns: []string{}, }) - // testFailure(Profile{ - // Type: FileType, - // Name: "daily", - // NamePatterns: []string{"test"}, - // FileTemplatePath: "", - // }) + // file template and file template path are set + testFailure(Profile{ + Type: FileType, + Name: "daily", + NamePatterns: []string{"test"}, + FileTemplatePath: "foo", + FileTemplate: "bar", + }) + // file template set with wrong type + testFailure(Profile{ + Type: FolderType, + Name: "daily", + NamePatterns: []string{"test"}, + FileTemplate: "bar", + }) + // file template path set with wrong type + testFailure(Profile{ + Type: FolderType, + Name: "daily", + NamePatterns: []string{"test"}, + FileTemplatePath: "bar", + }) + // script type with multiple name patterns + testFailure(Profile{ + Type: ScriptType, + Name: "daily", + NamePatterns: []string{"test", "test2"}, + }) + // postscripts with no shell + testFailure(Profile{ + Type: ScriptType, + Name: "daily", + NamePatterns: []string{"test"}, + PostScripts: []string{"test"}, + ScriptShell: "", + }) for _, profile := range defaultConfig().Profiles { testSuccess(profile) @@ -96,4 +138,8 @@ func TestFindProfile(t *testing.T) { testSuccess("notes", "notes") testFailure("non-existent") + + config.Profiles[1].IsDefault = false + testFailure("") + testFailure("non-default") } diff --git a/utils.go b/utils.go index 4cda077..7a794bb 100644 --- a/utils.go +++ b/utils.go @@ -6,11 +6,7 @@ import ( "strings" ) -func getFullPath(path string) (string, error) { - usr, error := user.Current() - if error != nil { - return "", error - } +func getFullPath(path string, usr user.User) string { dir := usr.HomeDir var newPath string if path == "~" { @@ -20,5 +16,5 @@ func getFullPath(path string) (string, error) { } else { newPath = path } - return newPath, nil + return newPath } diff --git a/utils_test.go b/utils_test.go index 1cd946f..851bdca 100644 --- a/utils_test.go +++ b/utils_test.go @@ -8,11 +8,11 @@ import ( ) func TestGetFullPath(t *testing.T) { + usr, err := user.Current() + assert.Nil(t, err) + testSuccess := func(path string, expected string) { - fullPath, err := getFullPath(path) - if err != nil { - t.Errorf("Expected to get full path for '%v', but failed: %v", path, err) - } + fullPath := getFullPath(path, *usr) if fullPath != expected { t.Errorf("Expected to get full path '%v' for '%v', but got '%v'", expected, path, fullPath) } @@ -29,9 +29,6 @@ func TestGetFullPath(t *testing.T) { testSuccess("C:\\test\\", "C:\\test\\") testSuccess("C:\\test\\..", "C:\\test\\..") // WRONG - usr, err := user.Current() - assert.NoError(t, err, "Failed to get current user") - testSuccess("~/", usr.HomeDir) testSuccess("~", usr.HomeDir) }