fuzzyworld

template / commit

Refactored a bit:

8c15a714adc179eccdd78dbf3c88e5a6a261d000 · Pavel Safronov · 2022-11-03 23:10:05 -0700 · parents e86c26ae8eaa

Refactored a bit:
- made list/get/reset into actual commands
- reworked how command settings are parsed, so this can happen (mostly) in the same spot
- added a few notes to the readme
- test updates
- added a new profile "therapy-open" that opens the therapy folder
 README.md             |   7 ++-
 config_test.go        |   2 +
 default_config.go     |  14 ++++-
 defaultconfig_test.go |  14 +++++
 main.go               | 168 +++++++++++++++++++++++++++++++++++---------------
 name_pattern.go       |  23 +++----
 name_pattern_test.go  |   9 ++-
 profile.go            |  95 +++++++++++++++++-----------
 profile_test.go       |  39 +++++-------
 9 files changed, 236 insertions(+), 135 deletions(-)

diff --git a/README.md b/README.md
index b6e09c9..5310c5e 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ template
 
 ## Description
 
-This CLI creates files and folders with generated names, for example `2022-09-26-fhl-2023.md` (generator on September 26, 2023, with the project name "fhl-2023").
+This CLI creates files and folders with generated templated names, for example `2022-09-26-fhl-2023.md` (generator on September 26, 2023, with the project name "fhl-2023").
 It's also possible to control the contents of the files: you can specify a template file or an inline template to set the file contents.
 
 This started as a hackathon project that I used to learn Go (golang) and to automate a repetitive task: creation of time-stamped files and folders.
@@ -151,7 +151,10 @@ The CLI should also not be too opinionated. Aside from `go`, there are no other
 
 Here is the future feature list:
 
-1. Support for passing in a list of template values for custom values in the template. Use might look like this: `template work form123 --id=456 --corpOffice=SEA`. The passed-in extra values will be placed somewhere inside the template. Some value keys will not be allowed, like "time".
+1. Remove dependency on pwsh - is there a better approach to making `template` a cross-platform CLI?
+2. Publish Homebrew version of app
+3. Publish Chocolatey version of app
+4. Support for passing in a list of template values for custom values in the template. Use might look like this: `template work form123 --id=456 --corpOffice=SEA`. The passed-in extra values will be placed somewhere inside the template. Some value keys will not be allowed, like "time".
 
 ## Contributing
 
diff --git a/config_test.go b/config_test.go
index 4163c89..46103ae 100644
--- a/config_test.go
+++ b/config_test.go
@@ -116,6 +116,7 @@ func TestValidateConfig(t *testing.T) {
 			IsDefault:    true,
 			NamePatterns: []ParameterizedText{"daily"},
 			Type:         FileType,
+			Path:         "foo",
 			File: File{
 				Template: "foo",
 			},
@@ -125,6 +126,7 @@ func TestValidateConfig(t *testing.T) {
 			IsDefault:    true,
 			NamePatterns: []ParameterizedText{"daily"},
 			Type:         FileType,
+			Path:         "foo",
 			File: File{
 				Template: "foo",
 			},
diff --git a/default_config.go b/default_config.go
index aa6d0de..cc5e942 100644
--- a/default_config.go
+++ b/default_config.go
@@ -53,6 +53,7 @@ Today's notes for {{.Year}}-{{.Month}}-{{.Day}}.
 				OS:          "All",
 				Type:        FileType,
 				IsDefault:   true,
+				Path:        ".", // current folder
 				NamePatterns: []ParameterizedText{
 					"{{$version := getVersion}}" +
 						"{{.Year}}-{{.Month}}-{{.Day}}" +
@@ -130,7 +131,7 @@ cspell:words nanite
 				OS:          "All",
 				Path:        "~/Therapy",
 				File: File{
-					Template: `# {{.FilenameWithoutExtension}}
+					Template: `# {{if .ProjectName}}{{.ProjectName}}{{else}}Therapy notes{{end}}
 
 {{if .ProjectName}}Therapy notes on {{.ProjectName}}{{else}}Therapy notes{{end}}
 
@@ -150,9 +151,16 @@ Sincerely, me
 						"{{if (gt $version 1)}}v{{$version}}{{end}}" +
 						".md",
 				},
+			},
+			{
+				Name:        "therapy-open",
+				Description: "Open the ~/Therapy folder in VS Code",
+				OS:          "All",
+				Path:        "~/Therapy",
+				Type:        ScriptType,
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
-					"code . \"{{.FileName}}\"",
+					"code .",
 				},
 			},
 			{
@@ -195,6 +203,7 @@ echo "Let's test {{.ProjectName}}!"`,
 				Name:        "folder",
 				Description: "Creates a folder in the current folder in the format YYYY-MM-DD[-ProjectName][-vVersion]",
 				Type:        FolderType,
+				Path:        ".", // current folder
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{},
 				NamePatterns: []ParameterizedText{
@@ -209,6 +218,7 @@ echo "Let's test {{.ProjectName}}!"`,
 				Type:        FolderType,
 				Description: "Creates a folder in the current folder in the format create-react-app[-ProjectName][-vVersion], runs `npx create-react-app` to initialize the folder, and opens the folder in VSCode",
 				ScriptShell: "pwsh",
+				Path:        ".", // current folder
 				NamePatterns: []ParameterizedText{
 					"{{$version := getVersion}}" +
 						"create-react-app" +
diff --git a/defaultconfig_test.go b/defaultconfig_test.go
new file mode 100644
index 0000000..b06ada8
--- /dev/null
+++ b/defaultconfig_test.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDefaultConfig(t *testing.T) {
+
+	config := defaultConfig()
+	err := validateConfig(&config)
+	assert.Nil(t, err, "Error validating default config")
+}
diff --git a/main.go b/main.go
index 51a8044..3f56d33 100644
--- a/main.go
+++ b/main.go
@@ -25,7 +25,10 @@ type ArgumentValues struct {
 }
 type Settings struct {
 	ArgumentValues
-	extras map[string]string
+	extras         map[string]string
+	fullConfigPath string
+	config         Config
+	usr            *user.User
 }
 
 func main() {
@@ -85,11 +88,92 @@ func main() {
 				Destination: &argumentValues.logLevel,
 			},
 		},
+		Commands: []*cli.Command{
+			{
+				Name:  "list",
+				Usage: "list available profiles",
+				Action: func(c *cli.Context) error {
+					settings, err := createSettings(argumentValues, c)
+					if err != nil {
+						return fmt.Errorf("error creating settings: %v", err)
+					}
+					err = setConfig(&settings)
+					if err != nil {
+						return fmt.Errorf("error setting config: %w", err)
+					}
+
+					config := settings.config
+					// list profiles
+					fmt.Println("Available profiles:")
+					for _, p := range config.Profiles {
+						fmt.Printf("  %v: %v\n", p.Name, p.Description)
+					}
+					return nil
+				},
+			},
+			{
+				Name:  "get",
+				Usage: "get a profile",
+				Action: func(c *cli.Context) error {
+					settings, err := createSettings(argumentValues, c)
+					if err != nil {
+						return fmt.Errorf("error creating settings: %v", err)
+					}
+					err = setConfig(&settings)
+					if err != nil {
+						return fmt.Errorf("error setting config: %w", err)
+					}
+
+					config := settings.config
+
+					// get profile information
+					index, err := findProfileIndex(config.Profiles, settings.profile)
+					if err != nil {
+						return fmt.Errorf("profile '%v' not found", settings.profile)
+					}
+					profile := config.Profiles[index]
+
+					writer := strings.Builder{}
+					encoder := yaml.NewEncoder(&writer)
+					err = encoder.Encode(profile)
+					if err != nil {
+						return fmt.Errorf("error encoding profile: %v", err)
+					}
+
+					yamlText := writer.String()
+					fmt.Printf("Profile '%v':\n", settings.projectName)
+					fmt.Println(yamlText)
+					return nil
+				},
+			},
+			{
+				Name:  "reset",
+				Usage: "reset the global template config to default profiles",
+				Action: func(c *cli.Context) error {
+					settings, err := createSettings(argumentValues, c)
+					if err != nil {
+						return fmt.Errorf("error creating settings: %v", err)
+					}
+					fullConfigPath := settings.fullConfigPath
+
+					fmt.Printf("Resetting config at '%v' to default\n", fullConfigPath)
+					err = saveConfig(fullConfigPath, defaultConfig())
+					if err != nil {
+						return fmt.Errorf("error saving default config: %v", err)
+					}
+					return nil
+				},
+			},
+		},
 		Action: func(context *cli.Context) error {
-			settings, err := parseArguments(argumentValues, context)
+			settings, err := createSettings(argumentValues, context)
 			if err != nil {
 				return fmt.Errorf("error parsing arguments: %w", err)
 			}
+			err = setConfig(&settings)
+			if err != nil {
+				return fmt.Errorf("error setting config: %w", err)
+			}
 			err = mainLogic(settings)
 			if err != nil {
 				return fmt.Errorf("error in main logic: %w", err)
@@ -103,7 +187,7 @@ func main() {
 	}
 }
 
-func parseArguments(
+func createSettings(
 	argumentValues ArgumentValues,
 	context *cli.Context,
 ) (Settings, error) {
@@ -161,17 +245,24 @@ func parseArguments(
 		settings.extras = extras
 	}
 
-	return settings, nil
-}
-
-func mainLogic(settings Settings) error {
+	// get user
 	usr, err := user.Current()
 	if err != nil {
 		log.Fatal(err)
 	}
+	settings.usr = usr
 
 	// get config from default path
 	fullConfigPath := getFullPath(settings.configFilePath, *usr)
+	settings.fullConfigPath = fullConfigPath
+
+	return settings, nil
+}
+
+func setConfig(settings *Settings) error {
+	fullConfigPath := settings.fullConfigPath
+	usr := settings.usr
+
 	config, err := loadConfig(fullConfigPath)
 	if err != nil {
 		return fmt.Errorf("error loading config from %v: %w", fullConfigPath, err)
@@ -197,63 +288,38 @@ func mainLogic(settings Settings) error {
 		}
 	}
 
-	// list profiles
-	if settings.profile == "list" {
-		fmt.Println("Available profiles:")
-		for _, p := range config.Profiles {
-			fmt.Printf("  %v: %v\n", p.Name, p.Description)
-		}
-		return nil
-	}
-
-	// get profile information
-	if settings.profile == "get" {
-		index, err := findProfileIndex(config.Profiles, settings.projectName)
-		if err != nil {
-			return fmt.Errorf("profile '%v' not found", settings.projectName)
-		}
-		profile := config.Profiles[index]
-
-		writer := strings.Builder{}
-		encoder := yaml.NewEncoder(&writer)
-		err = encoder.Encode(profile)
-		if err != nil {
-			return fmt.Errorf("error encoding profile: %v", err)
-		}
-
-		yamlText := writer.String()
-		fmt.Printf("Profile '%v':\n", settings.projectName)
-		fmt.Println(yamlText)
-		return nil
-	}
-
-	// reset config to the default
-	if settings.profile == "reset" {
-		fmt.Printf("Resetting config at '%v' to default\n", fullConfigPath)
-		err = saveConfig(fullConfigPath, defaultConfig())
-		if err != nil {
-			return fmt.Errorf("error saving default config: %v", err)
-		}
-		return nil
-	}
+	settings.config = config
+	return nil
+}
 
-	// find profile
-	var profile Profile
+func findProfile(settings Settings) (Profile, error) {
+	var profile Profile = Profile{}
+	var err error
+	config := settings.config
 	if settings.profile == "" {
 		profile, err = findDefaultProfile(config.Profiles)
 		if err != nil {
-			return fmt.Errorf("no profile specified and no default profile found")
+			return profile, fmt.Errorf("no profile specified and no default profile found")
 		}
 	} else {
 		index, err := findProfileIndex(config.Profiles, settings.profile)
 		if err != nil {
-			return fmt.Errorf("error finding profile: %v", err)
+			return profile, fmt.Errorf("error finding profile: %v", err)
 		}
 		profile = config.Profiles[index]
 	}
+	return profile, nil
+}
+
+func mainLogic(settings Settings) error {
+	// find profile
+	profile, err := findProfile(settings)
+	if err != nil {
+		return fmt.Errorf("error finding profile: %v", err)
+	}
 
 	// process profile
-	errors := processProfile(profile, settings.projectName, settings.extras, *usr)
+	errors := processProfile(profile, settings.projectName, settings.extras, *settings.usr)
 	if len(errors) > 0 {
 		return fmt.Errorf("error processing profile: %v", errors)
 	}
diff --git a/name_pattern.go b/name_pattern.go
index 9e60573..2f30d6f 100644
--- a/name_pattern.go
+++ b/name_pattern.go
@@ -12,9 +12,10 @@ import (
 
 func createNewNameForPatterns(
 	now time.Time,
+	patternData PrePatternData,
 	projectName string,
 	namePatterns []ParameterizedText,
-	extras map[string]string, existingEntities []string) (string, PrePatternData, error) {
+	extras map[string]string, existingEntities []string) (string, error) {
 	for index, namePattern := range namePatterns {
 
 		startingVersion := 1
@@ -26,28 +27,18 @@ func createNewNameForPatterns(
 			},
 		}
 		template := template.New("template-name").Funcs(funcMap)
-		patternData := PrePatternData{
-			Year:        now.Format("2006"),
-			Month:       now.Format("01"),
-			Day:         now.Format("02"),
-			Hour:        now.Format("15"),
-			Minute:      now.Format("04"),
-			Second:      now.Format("05"),
-			ProjectName: projectName,
-			Extras:      extras,
-		}
 
 		for {
 
 			template, err := template.Parse(string(namePattern))
 			if err != nil {
-				return "", patternData, fmt.Errorf("error parsing name pattern %d: %w", index, 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 "", patternData, fmt.Errorf("error executing name pattern %d: %w", index, err)
+				return "", fmt.Errorf("error executing name pattern %d: %w", index, err)
 			}
 
 			newName := sb.String()
@@ -57,7 +48,7 @@ func createNewNameForPatterns(
 
 			// if no conflict, use this name
 			if !alreadyExists {
-				return newName, patternData, nil
+				return newName, nil
 			}
 			// if index is not being used (template did not invoke getVersion)
 			// then we're done
@@ -67,11 +58,11 @@ func createNewNameForPatterns(
 
 			// if getVersion was used, but this isn't the last pattern, report an error
 			if useIndex && !isLastPattern {
-				return "", patternData, 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 "", PrePatternData{}, errors.New("couldn't find a new name")
+	return "", errors.New("couldn't find a new name")
 }
diff --git a/name_pattern_test.go b/name_pattern_test.go
index 4cf5db5..9168364 100644
--- a/name_pattern_test.go
+++ b/name_pattern_test.go
@@ -29,7 +29,8 @@ func TestCreateNewName(t *testing.T) {
 
 	testHappyPath := func(projectName string, expectedName string, existingEntities []string) func(t *testing.T) {
 		return func(t *testing.T) {
-			newName, prePatternData, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, existingEntities)
+			prePatternData := createPrePatternData(now, projectName, extras)
+			newName, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, existingEntities)
 			if err != nil {
 				t.Error("Error creating new name: ", err)
 			}
@@ -62,7 +63,8 @@ func TestCreateNewName(t *testing.T) {
 	}
 	testErrorPath := func(projectName string, existingEntities []string) func(t *testing.T) {
 		return func(t *testing.T) {
-			_, _, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, existingEntities)
+			prePatternData := createPrePatternData(now, projectName, extras)
+			_, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, existingEntities)
 			if err == nil {
 				t.Error("Expected error but got none")
 			}
@@ -100,7 +102,8 @@ func TestCreateNewName_Index(t *testing.T) {
 
 	testHappyPath := func(expectedName string, existingEntities []string) func(t *testing.T) {
 		return func(t *testing.T) {
-			newName, prePatternData, err := createNewNameForPatterns(now, "test-project", profile.NamePatterns, extras, existingEntities)
+			prePatternData := createPrePatternData(now, "test-project", extras)
+			newName, err := createNewNameForPatterns(now, prePatternData, "test-project", profile.NamePatterns, extras, existingEntities)
 			if err != nil {
 				t.Error("Error creating new name: ", err)
 			}
diff --git a/profile.go b/profile.go
index 55b70a4..edede6c 100644
--- a/profile.go
+++ b/profile.go
@@ -90,51 +90,54 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 		cwd = path
 	}
 
-	// get all entities in current directory
-	files, err := monkey.OSReadDir(cwd)
-	if err != nil {
-		return []error{err}
-	}
-
 	// determine if we're dealing with files or directories
 	profileType := profile.Type
 
-	// filter entities to just files or directories
-	var entities []string
-	for _, file := range files {
-		isDir := file.IsDir()
-		if isDir == (profileType == FolderType) {
-			entities = append(entities, file.Name())
-		}
-	}
-
-	// generate a new name based on time, profile, and existing entities
-	now := time.Now()
 	errors := []error{}
-	newName, prePatternData, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, entities)
-	swallowPrePostScriptErrors := profile.RunPostscriptOnFailure && profile.PostScripts != nil && len(profile.PostScripts) > 0
+	fullPath := ""
+	now := time.Now()
+	prePatternData := createPrePatternData(now, projectName, extras)
+	if profileType == FileType || profileType == FolderType {
+		// get all entities in current directory
+		files, err := monkey.OSReadDir(cwd)
+		if err != nil {
+			return []error{err}
+		}
 
-	// check newName is created
-	if newName == "" {
-		if swallowPrePostScriptErrors {
-			fmt.Println("No new name was created, but will attempt to run post scripts")
-		} else {
-			errors = append(errors, fmt.Errorf("couldn't find a good new name"))
-			return errors
+		// filter entities to just files or directories
+		var entities []string
+		for _, file := range files {
+			isDir := file.IsDir()
+			if isDir == (profileType == FolderType) {
+				entities = append(entities, file.Name())
+			}
 		}
-	}
 
-	fullPath := ""
-	if err == nil && newName != "" {
-		fullPath, err = createEntity(now, profile, cwd, newName, projectName, extras, prePatternData)
-		if err != nil {
+		// generate a new name based on time, profile, and existing entities
+		newName, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, entities)
+		swallowPrePostScriptErrors := profile.RunPostscriptOnFailure && profile.PostScripts != nil && len(profile.PostScripts) > 0
+
+		// check newName is created
+		if newName == "" {
 			if swallowPrePostScriptErrors {
-				fmt.Printf("Error creating entity, but will attempt to run post scripts: %v", err)
+				fmt.Println("No new name was created, but will attempt to run post scripts")
 			} else {
-				errors = append(errors, fmt.Errorf("unable to create entity: %v", err))
+				errors = append(errors, fmt.Errorf("couldn't find a good new name"))
 				return errors
 			}
 		}
+
+		if err == nil && newName != "" {
+			fullPath, err = createEntity(now, profile, cwd, newName, projectName, extras, prePatternData)
+			if err != nil {
+				if swallowPrePostScriptErrors {
+					fmt.Printf("Error creating entity, but will attempt to run post scripts: %v", err)
+				} else {
+					errors = append(errors, fmt.Errorf("unable to create entity: %v", err))
+					return errors
+				}
+			}
+		}
 	}
 
 	// execute PostScript, if present
@@ -170,6 +173,20 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 	return nil
 }
 
+func createPrePatternData(now time.Time, projectName string, extras map[string]string) PrePatternData {
+	prePatternData := PrePatternData{
+		Year:        now.Format("2006"),
+		Month:       now.Format("01"),
+		Day:         now.Format("02"),
+		Hour:        now.Format("15"),
+		Minute:      now.Format("04"),
+		Second:      now.Format("05"),
+		ProjectName: projectName,
+		Extras:      extras,
+	}
+	return prePatternData
+}
+
 func createEntity(
 	now time.Time,
 	profile Profile,
@@ -196,7 +213,8 @@ func createEntity(
 		if len(profile.Files) > 0 {
 			fmt.Printf("Creating %v files in folder: %v\n", len(profile.Files), fullPath)
 			for index, file := range profile.Files {
-				fileName, filePrePatternData, err := createNewNameForPatterns(now, projectName, []ParameterizedText{file.Name}, extras, []string{})
+				filePrePatternData := createPrePatternData(now, projectName, extras)
+				fileName, err := createNewNameForPatterns(now, filePrePatternData, projectName, []ParameterizedText{file.Name}, extras, []string{})
 				if err != nil {
 					return "", fmt.Errorf("error creating file name for file index %v : %v", index, err)
 				}
@@ -227,15 +245,18 @@ func validateProfile(profile *Profile) error {
 	if slices.Contains(forbiddenProfileNames, profile.Name) {
 		return fmt.Errorf("forbidden profile name '%s'", profile.Name)
 	}
-	if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 {
-		return fmt.Errorf("no name patterns found for profile %s", profile.Name)
-	}
 	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.Template != "" {
 		return fmt.Errorf("file template is not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
 	}
+	if profile.Type == FileType && profile.Path == "" {
+		return fmt.Errorf("path is not specified for profile %s, which has Type=%s", profile.Name, profile.Type)
+	}
+	if profile.Type == FolderType && profile.Path == "" {
+		return fmt.Errorf("path is required for profile %s, which has Type=%s", profile.Name, profile.Type)
+	}
 	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 c70ed53..fa6d41b 100644
--- a/profile_test.go
+++ b/profile_test.go
@@ -18,10 +18,10 @@ func TestValidateProfile(t *testing.T) {
 			t.Errorf("Expected to validate profile '%v', but failed: %v", profile.Name, err)
 		}
 	}
-	testFailure := func(profile Profile) {
+	testFailure := func(profile Profile, testName string) {
 		err := validateProfile(&profile)
 		if err == nil {
-			t.Errorf("Expected to fail to validate profile '%v', but succeeded", profile.Name)
+			t.Errorf("%v - Expected to fail to validate profile '%v', but succeeded", testName, profile.Name)
 		}
 	}
 
@@ -29,6 +29,7 @@ func TestValidateProfile(t *testing.T) {
 		Type:         FileType,
 		Name:         "daily",
 		NamePatterns: []ParameterizedText{"{{.Date}}"},
+		Path:         "~/test/dir",
 	})
 	testSuccess(Profile{
 		Type:         FileType,
@@ -37,36 +38,27 @@ func TestValidateProfile(t *testing.T) {
 		File: File{
 			Template: "foo",
 		},
+		Path: "~/test/dir",
 	})
 
-	// empty name
 	testFailure(Profile{
 		Name: "",
-	})
-	// illegal name
+	}, "empty name")
 	testFailure(Profile{
 		Name: "list",
-	})
+	}, "illegal name")
 	testFailure(Profile{
 		Name: "get",
-	})
-	// file type with no path
+	}, "illegal name")
 	testFailure(Profile{
 		Type: FileType,
 		Name: "daily",
-	})
+	}, "missing path")
 	testFailure(Profile{
 		Type: FileType,
 		Name: "daily",
-		Path: "\\",
-	})
-	// no name patterns
-	testFailure(Profile{
-		Type:         FileType,
-		Name:         "daily",
-		NamePatterns: []ParameterizedText{},
-	})
-	// file template set with wrong type
+	}, "invalid path")
+
 	testFailure(Profile{
 		Type:         FolderType,
 		Name:         "daily",
@@ -74,21 +66,19 @@ func TestValidateProfile(t *testing.T) {
 		File: File{
 			Template: "bar",
 		},
-	})
-	// script type with multiple name patterns
+	}, "file in folder")
 	testFailure(Profile{
 		Type:         ScriptType,
 		Name:         "daily",
 		NamePatterns: []ParameterizedText{"test", "test2"},
-	})
-	// postscripts with no shell
+	}, "multiple name patterns")
 	testFailure(Profile{
 		Type:         ScriptType,
 		Name:         "daily",
 		NamePatterns: []ParameterizedText{"test"},
 		PostScripts:  []ParameterizedText{"test"},
 		ScriptShell:  "",
-	})
+	}, "missing script shell")
 
 	for _, profile := range defaultConfig().Profiles {
 		testSuccess(profile)
@@ -281,7 +271,7 @@ func TestProcessProfile(t *testing.T) {
 		assert.Equal(t, 1, len(monkeyPatchCalls.ExecCommandCalls))
 		assert.Equal(t, 1, len(monkeyPatchCalls.OSChdirCalls))
 		assert.Equal(t, 1, len(monkeyPatchCalls.OSGetwdCalls))
-		assert.Equal(t, 1, len(monkeyPatchCalls.OSReadDirCalls))
+		assert.Equal(t, 0, len(monkeyPatchCalls.OSReadDirCalls))
 	}
 	testFailure := func() {
 		profile := Profile{
@@ -294,6 +284,7 @@ func TestProcessProfile(t *testing.T) {
 				"echo 'hi'",
 			},
 			ScriptShell: shell,
+			Type:        FileType,
 		}
 
 		usr, err := user.Current()