fuzzyworld

template / commit

Added support for explicit file and folder templating. Updated tests and readme.

8fc7c4526316f49530cc37195e400f940bf39b52 · Pavel Safronov · 2023-03-29 00:16:43 -0700 · parents d3c36d207776

Added support for explicit file and folder templating. Updated tests and readme.
 .vscode/settings.json |  33 ++---
 README.md             |  41 +++++-
 main.go               |  97 ++++++++++----
 main_test.go          |  74 +++++++----
 name_pattern_test.go  | 340 +++++++++++++++++++++++++-------------------------
 5 files changed, 347 insertions(+), 238 deletions(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 851e655..773e7a8 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,15 +1,20 @@
-{
-  "cSpell.words": [
-    "bashdb",
-    "filetemplate",
-    "logrus",
-    "pwsh",
-    "runpostscriptonfailure",
-    "scriptshell",
-    "sirupsen",
-    "snakecase",
-    "stretchr",
-    "targetdirectory",
-    "urfave"
-  ]
+{

+  "cSpell.words": [

+    "bashdb",

+    "filetemplate",

+    "hackathon",

+    "logrus",

+    "Pavel",

+    "pwsh",

+    "runpostscriptonfailure",

+    "Safronov",

+    "scriptshell",

+    "sirupsen",

+    "snakecase",

+    "stretchr",

+    "targetdirectory",

+    "templating",

+    "untap",

+    "urfave"

+  ]

 }
\ No newline at end of file
diff --git a/README.md b/README.md
index 8042d1a..c9500cd 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,22 @@ Post script had no output
 Note that user profiles are combined with the default ones.
 This is so you can run `template reset` to reset `~/.template.yaml`, but keep your local profiles/changes as-is.
 
+### Using a file or folder template directly

+

+You can also specify a file or folder template during execution, without changing any configs.

+

+Creating a file:

+

+```sh

+template -f "{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt" "project name"

+```

+

+Creating a folder:

+

+```sh

+template -d "{{.Year}}-{{.Month}}-{{.Day}}-{{.ProjectName}}.txt" Test123

+```

+

 ## Examples
 
 ### Use `daily` profile to create a new file
@@ -126,6 +142,24 @@ template work "fix-bug-123"
 curl -X GET "https://httpbin.org/delay/3" -H  "accept: application/json" 2>&1 | template log
 ```
 
+### Specify file pattern and project name

+

+```sh

+template -f "{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}-{{.ProjectName}}.txt" test

+```

+

+### Specify file pattern, project name, and piped input

+

+```sh

+history | template -f "{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}-{{.ProjectName}}.txt" "Hi all!" 

+```

+

+### Specify folder pattern and project name

+

+```sh

+template -d "{{.Year}}-{{.Month}}-{{.Day}}-{{.ProjectName}}" test123

+```

+

 ## Requirements
 
 The application is a single executable, there are no special requirements.
@@ -226,13 +260,13 @@ The following command will build the package, test it, and then install it:
 make install
 ```
 
-I use this approach, but that's because I use `template xyz` multiple times a day, and find it helpful to test out a work in progress, and to be using the latest version.

+I use this approach, but that's because I use `template` multiple times a day, and find it helpful be using the latest version.

 
 ## Support
 
 For anything, open an issue in this repo.
 
-## Roadmap

+## Road map

 
 I'm going to use this tool daily to figure out how it can be improved.
 
@@ -248,7 +282,7 @@ Once there are enough other users and enough new feature requirements are added,
 
 Here are the basic features of the CLI that I want to keep:
 
-1. Cross-platform

+1. Cross-platform support

 2. CLI is implemented in Go
 3. Fast (under 1s) basic operation of templating file and folder names
    1. Postscripts can take longer, as long as the "basic operations" finish quickly enough.
@@ -275,7 +309,6 @@ Here is the future feature list:
 
 1. Publish Chocolatey version of app, possibly using [this GoReleaser Chocolatey documentation](https://goreleaser.com/customization/chocolatey/)
 2. 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".
-3. Support specifying a template directly to the CLI: `template --file "{{.Year}}-{{.Month}}-{{.Day}}.woot.txt"` will produce file `2023-02-18.woot.txt`

 
 ## Contributing
 
diff --git a/main.go b/main.go
index 9931c07..5392e2c 100644
--- a/main.go
+++ b/main.go
@@ -25,6 +25,8 @@ const defaultLogLevel = "info"
 type ArgumentValues struct {
 	profile        string
 	projectName    string
+	fileTemplate   string
+	dirTemplate    string
 	configFilePath string
 	userConfigPath string
 	extrasFilePath string
@@ -43,8 +45,6 @@ type Settings struct {
 }
 
 func main() {
-	// var err error
-	// var usr *user.User
 	argumentValues := ArgumentValues{}
 
 	app := &cli.App{
@@ -63,6 +63,20 @@ func main() {
 				// Required:    true,
 				// EnvVars: []string{"TEMPLATE_PROFILE"},
 			},
+			&cli.StringFlag{
+				Name:        "file",
+				Aliases:     []string{"f"},
+				Value:       "",
+				Usage:       "file template to use to create a file",
+				Destination: &argumentValues.fileTemplate,
+			},
+			&cli.StringFlag{
+				Name:        "dir",
+				Aliases:     []string{"d"},
+				Value:       "",
+				Usage:       "folder pattern to use to create a directory",
+				Destination: &argumentValues.dirTemplate,
+			},
 			&cli.StringFlag{
 				Name:        "name",
 				Aliases:     []string{"n"},
@@ -216,38 +230,46 @@ func createSettings(
 	args := context.Args()
 
 	// configure logging
-	switch settings.logLevel {
+	var level log.Level
+	switch strings.ToLower(settings.logLevel) {
 	case "debug":
-		log.SetLevel(log.DebugLevel)
+		level = log.DebugLevel
 	case "info":
-		log.SetLevel(log.InfoLevel)
+		level = log.InfoLevel
 	case "warn":
-		log.SetLevel(log.WarnLevel)
+		level = log.WarnLevel
 	case "error":
-		log.SetLevel(log.ErrorLevel)
+		level = log.ErrorLevel
 	case "fatal":
-		log.SetLevel(log.FatalLevel)
+		level = log.FatalLevel
 	case "panic":
-		log.SetLevel(log.PanicLevel)
+		level = log.PanicLevel
 	default:
-		return Settings{}, fmt.Errorf("invalid log level specified %v, using info", settings.logLevel)
+		return Settings{}, fmt.Errorf("invalid log level specified %v", settings.logLevel)
 	}
+	log.SetLevel(level)
 	log.SetOutput(os.Stdout)
-	// log.SetFormatter(&log.JSONFormatter{})
 
 	log.WithFields(log.Fields{"args": args}).Debug("Args parsed")
 
-	// get profile name from args
-	// if not set explicitly, use first arg, if present
-	if settings.profile == "" && args.Len() > 0 {
-		settings.profile = args.Get(0)
+	skipProfile := len(settings.fileTemplate) > 0 || len(settings.dirTemplate) > 0
+	currentParamIndex := 0
+
+	if !skipProfile {
+		// get profile name from args
+		// if not set explicitly, use first arg, if present
+		if settings.profile == "" && args.Len() > currentParamIndex {
+			settings.profile = args.Get(currentParamIndex)
+		}
+		fmt.Println("Profile:", settings.profile)
+		currentParamIndex++
 	}
-	fmt.Println("Profile:", settings.profile)
 
 	// get project name from args
-	// if not set explicitly, use second arg, if present
-	if settings.projectName == "" && args.Len() > 1 {
-		settings.projectName = args.Get(1)
+	// if not set explicitly, use next arg (after profile), if present
+	if settings.projectName == "" && args.Len() > currentParamIndex {
+		settings.projectName = args.Get(currentParamIndex)
+		// currentParamIndex++
 	}
 	fmt.Println("Project name:", settings.projectName)
 
@@ -332,17 +354,38 @@ 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 profile, fmt.Errorf("no profile specified and no default profile found")
+	createTempProfile := len(settings.fileTemplate) > 0 || len(settings.dirTemplate) > 0
+
+	if createTempProfile {
+		profileType := FileType
+		if len(settings.dirTemplate) > 0 {
+			profileType = FolderType
+		}
+		patterns := []ParameterizedText{}
+		if len(settings.fileTemplate) > 0 {
+			patterns = append(patterns, ParameterizedText(settings.fileTemplate))
+		} else if len(settings.dirTemplate) > 0 {
+			patterns = append(patterns, ParameterizedText(settings.dirTemplate))
+		}
+		profile = Profile{
+			Name:         "temp",
+			Description:  "temp",
+			Type:         profileType,
+			NamePatterns: patterns,
 		}
 	} else {
-		index, err := findProfileIndex(config.Profiles, settings.profile)
-		if err != nil {
-			return profile, fmt.Errorf("error finding profile: %v", err)
+		if settings.profile == "" {
+			profile, err = findDefaultProfile(config.Profiles)
+			if err != nil {
+				return profile, fmt.Errorf("no profile specified and no default profile found")
+			}
+		} else {
+			index, err := findProfileIndex(config.Profiles, settings.profile)
+			if err != nil {
+				return profile, fmt.Errorf("error finding profile: %v", err)
+			}
+			profile = config.Profiles[index]
 		}
-		profile = config.Profiles[index]
 	}
 	return profile, nil
 }
diff --git a/main_test.go b/main_test.go
index 2a1ab5a..fa619e2 100644
--- a/main_test.go
+++ b/main_test.go
@@ -1,23 +1,51 @@
-package main
-
-import (
-	"os"
-	"testing"
-)
-
-func TestMain(m *testing.M) {
-	setup()
-	code := m.Run()
-	shutdown()
-	os.Exit(code)
-}
-
-func setup() {
-	// setup code
-}
-func shutdown() {
-	// shutdown code
-
-	// not really necessary, but why not?
-	undoMonkeyPatch()
-}
+package main

+

+import (

+	"os"

+	"testing"

+

+	"github.com/stretchr/testify/require"

+)

+

+func TestMain(m *testing.M) {

+	setup()

+	code := m.Run()

+	shutdown()

+	os.Exit(code)

+}

+

+func setup() {

+	// setup code

+}

+func shutdown() {

+	// shutdown code

+

+	// not really necessary, but why not?

+	undoMonkeyPatch()

+}

+

+func TestFindProfile(t *testing.T) {

+	fileTemplate := "{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt"

+	profile, err := findProfile(Settings{

+		ArgumentValues: ArgumentValues{

+			fileTemplate: fileTemplate,

+		},

+	})

+	require.NoError(t, err)

+	require.Equal(t, "temp", profile.Name)

+	require.Equal(t, FileType, profile.Type)

+	require.Equal(t, 1, len(profile.NamePatterns))

+	require.Equal(t, ParameterizedText(fileTemplate), profile.NamePatterns[0])

+

+	dirTemplate := "{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}"

+	profile, err = findProfile(Settings{

+		ArgumentValues: ArgumentValues{

+			dirTemplate: dirTemplate,

+		},

+	})

+	require.NoError(t, err)

+	require.Equal(t, "temp", profile.Name)

+	require.Equal(t, FolderType, profile.Type)

+	require.Equal(t, 1, len(profile.NamePatterns))

+	require.Equal(t, ParameterizedText(dirTemplate), profile.NamePatterns[0])

+}

diff --git a/name_pattern_test.go b/name_pattern_test.go
index 60fce98..3a6ef6f 100644
--- a/name_pattern_test.go
+++ b/name_pattern_test.go
@@ -1,170 +1,170 @@
-package main
-
-import (
-	"testing"
-	"time"
-
-	"github.com/stretchr/testify/assert"
-	"golang.org/x/exp/slices"
-)
-
-// Mon Jan 2 15:04:05 MST 2006
-var now = time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)
-
-func TestCreateNewName(t *testing.T) {
-	// setup code
-
-	extras := make(map[string]string)
-	extras["test1"] = "test1"
-
-	profile := Profile{
-		IsDefault: true,
-		Type:      FileType,
-		NamePatterns: []ParameterizedText{
-			"{{.Extras.test1}}-{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt",
-			"{{.Extras.test1}}-{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt",
-			"{{.Extras.test1}}-{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}-{{.ProjectName}}.txt",
-		},
-	}
-	newNameNoProject := "test1-2006-01-02.txt"
-	newName1 := "test1-2006-01-02-test-project.txt"
-	newName2 := "test1-2006-01-02-15-test-project.txt"
-	newName3 := "test1-2006-01-02-15-04-05-test-project.txt"
-
-	testHappyPath := func(projectName string, expectedName string, existingEntities []string) func(t *testing.T) {
-		return func(t *testing.T) {
-			prePatternData := createPrePatternData(now, projectName, extras)
-			validator := func(newName string) bool { return !slices.Contains(existingEntities, newName) }
-			newName, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, validator)
-			if err != nil {
-				t.Error("Error creating new name: ", err)
-			}
-			if newName != expectedName {
-				t.Error("Expected name to be ", expectedName, " but got ", newName)
-			}
-			if prePatternData.ProjectName != projectName {
-				t.Error("Expected prePatternData.ProjectName to be ", projectName, " but got ", prePatternData.ProjectName)
-			}
-			if prePatternData.Year != "2006" {
-				t.Error("Expected prePatternData.Year to be 2006 but got ", prePatternData.Year)
-			}
-			if prePatternData.Month != "01" {
-				t.Error("Expected prePatternData.Month to be 01 but got ", prePatternData.Month)
-			}
-			if prePatternData.Day != "02" {
-				t.Error("Expected prePatternData.Day to be 02 but got ", prePatternData.Day)
-			}
-			if prePatternData.Hour != "15" {
-				t.Error("Expected prePatternData.Hour to be 15 but got ", prePatternData.Hour)
-			}
-			if prePatternData.Minute != "04" {
-				t.Error("Expected prePatternData.Minute to be 04 but got ", prePatternData.Minute)
-			}
-			if prePatternData.Second != "05" {
-				t.Error("Expected prePatternData.Second to be 05 but got ", prePatternData.Second)
-			}
-
-		}
-	}
-	testErrorPath := func(projectName string, existingEntities []string) func(t *testing.T) {
-		return func(t *testing.T) {
-			prePatternData := createPrePatternData(now, projectName, extras)
-			validator := func(newName string) bool { return !slices.Contains(existingEntities, newName) }
-			_, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, validator)
-			if err == nil {
-				t.Error("Expected error but got none")
-			}
-		}
-	}
-
-	// test code
-
-	// happy path
-	t.Run("createNewName - happy path", testHappyPath("test-project", newName1, []string{}))
-	t.Run("createNewName - use second pattern", testHappyPath("test-project", newName2, []string{newName1}))
-	t.Run("createNewName - use third pattern", testHappyPath("test-project", newName3, []string{newName1, newName2}))
-	t.Run("createNewName - happy path", testHappyPath("", newNameNoProject, []string{}))
-
-	// error path
-	t.Run("createNewName - error path - collision", testErrorPath("test-project", []string{newName1, newName2, newName3}))
-}
-
-func TestCreateNewName_Index(t *testing.T) {
-	extras := make(map[string]string)
-	extras["test1"] = "test1"
-
-	profile := Profile{
-		IsDefault: true,
-		Type:      FileType,
-		NamePatterns: []ParameterizedText{
-			"{{.Extras.test1}}-{{$version := getVersion}}{{.Year}}-{{.Month}}-{{.Day}}v{{$version}}.txt",
-		},
-	}
-	newName1 := "test1-2006-01-02v1.txt"
-	newName2 := "test1-2006-01-02v2.txt"
-	newName3 := "test1-2006-01-02v3.txt"
-
-	testHappyPath := func(expectedName string, existingEntities []string) func(t *testing.T) {
-		return func(t *testing.T) {
-			prePatternData := createPrePatternData(now, "test-project", extras)
-			validator := func(newName string) bool { return !slices.Contains(existingEntities, newName) }
-			newName, err := createNewNameForPatterns(now, prePatternData, "test-project", profile.NamePatterns, extras, validator)
-			if err != nil {
-				t.Error("Error creating new name: ", err)
-			}
-			if newName != expectedName {
-				t.Error("Expected name to be ", expectedName, " but got ", newName)
-			}
-			if prePatternData.ProjectName != "test-project" {
-				t.Error("Expected prePatternData.ProjectName to be test-project but got ", prePatternData.ProjectName)
-			}
-			if prePatternData.Year != "2006" {
-				t.Error("Expected prePatternData.Year to be 2006 but got ", prePatternData.Year)
-			}
-			if prePatternData.Month != "01" {
-				t.Error("Expected prePatternData.Month to be 01 but got ", prePatternData.Month)
-			}
-			if prePatternData.Day != "02" {
-				t.Error("Expected prePatternData.Day to be 02 but got ", prePatternData.Day)
-			}
-			if prePatternData.Hour != "15" {
-				t.Error("Expected prePatternData.Hour to be 15 but got ", prePatternData.Hour)
-			}
-			if prePatternData.Minute != "04" {
-				t.Error("Expected prePatternData.Minute to be 04 but got ", prePatternData.Minute)
-			}
-			if prePatternData.Second != "05" {
-				t.Error("Expected prePatternData.Second to be 05 but got ", prePatternData.Second)
-			}
-		}
-	}
-
-	// test code
-
-	// happy path
-	t.Run("createNewName - v1", testHappyPath(newName1, []string{}))
-	t.Run("createNewName - v2", testHappyPath(newName2, []string{newName1}))
-	t.Run("createNewName - v3", testHappyPath(newName3, []string{newName1, newName2}))
-	t.Run("createNewName - v2", testHappyPath(newName2, []string{newName1, newName3}))
-}
-
-func TestCreateNewName_sprig(t *testing.T) {
-	profile := Profile{
-		IsDefault:    true,
-		Type:         FileType,
-		NamePatterns: []ParameterizedText{`{{"HELLO" | lower}}`},
-	}
-	projectName := "test-project"
-	prePatternData := createPrePatternData(now, projectName, nil)
-	newName, err := createNewNameForPatterns(
-		now,
-		prePatternData,
-		projectName,
-		profile.NamePatterns,
-		nil,
-		func(newName string) bool {
-			return true
-		})
-	assert.Nil(t, err)
-	assert.Equal(t, "hello", newName)
-}
+package main

+

+import (

+	"testing"

+	"time"

+

+	"github.com/stretchr/testify/assert"

+	"golang.org/x/exp/slices"

+)

+

+// Mon Jan 2 15:04:05 MST 2006

+var now = time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC)

+

+func TestCreateNewName(t *testing.T) {

+	// setup code

+

+	extras := make(map[string]string)

+	extras["test1"] = "test1"

+

+	profile := Profile{

+		IsDefault: true,

+		Type:      FileType,

+		NamePatterns: []ParameterizedText{

+			"{{.Extras.test1}}-{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt",

+			"{{.Extras.test1}}-{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt",

+			"{{.Extras.test1}}-{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.txt",

+		},

+	}

+	newNameNoProject := "test1-2006-01-02.txt"

+	newName1 := "test1-2006-01-02-test-project.txt"

+	newName2 := "test1-2006-01-02-15-test-project.txt"

+	newName3 := "test1-2006-01-02-15-04-05-test-project.txt"

+

+	testHappyPath := func(projectName string, expectedName string, existingEntities []string) func(t *testing.T) {

+		return func(t *testing.T) {

+			prePatternData := createPrePatternData(now, projectName, extras)

+			validator := func(newName string) bool { return !slices.Contains(existingEntities, newName) }

+			newName, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, validator)

+			if err != nil {

+				t.Error("Error creating new name: ", err)

+			}

+			if newName != expectedName {

+				t.Error("Expected name to be ", expectedName, " but got ", newName)

+			}

+			if prePatternData.ProjectName != projectName {

+				t.Error("Expected prePatternData.ProjectName to be ", projectName, " but got ", prePatternData.ProjectName)

+			}

+			if prePatternData.Year != "2006" {

+				t.Error("Expected prePatternData.Year to be 2006 but got ", prePatternData.Year)

+			}

+			if prePatternData.Month != "01" {

+				t.Error("Expected prePatternData.Month to be 01 but got ", prePatternData.Month)

+			}

+			if prePatternData.Day != "02" {

+				t.Error("Expected prePatternData.Day to be 02 but got ", prePatternData.Day)

+			}

+			if prePatternData.Hour != "15" {

+				t.Error("Expected prePatternData.Hour to be 15 but got ", prePatternData.Hour)

+			}

+			if prePatternData.Minute != "04" {

+				t.Error("Expected prePatternData.Minute to be 04 but got ", prePatternData.Minute)

+			}

+			if prePatternData.Second != "05" {

+				t.Error("Expected prePatternData.Second to be 05 but got ", prePatternData.Second)

+			}

+

+		}

+	}

+	testErrorPath := func(projectName string, existingEntities []string) func(t *testing.T) {

+		return func(t *testing.T) {

+			prePatternData := createPrePatternData(now, projectName, extras)

+			validator := func(newName string) bool { return !slices.Contains(existingEntities, newName) }

+			_, err := createNewNameForPatterns(now, prePatternData, projectName, profile.NamePatterns, extras, validator)

+			if err == nil {

+				t.Error("Expected error but got none")

+			}

+		}

+	}

+

+	// test code

+

+	// happy path

+	t.Run("createNewName - happy path", testHappyPath("test-project", newName1, []string{}))

+	t.Run("createNewName - use second pattern", testHappyPath("test-project", newName2, []string{newName1}))

+	t.Run("createNewName - use third pattern", testHappyPath("test-project", newName3, []string{newName1, newName2}))

+	t.Run("createNewName - happy path", testHappyPath("", newNameNoProject, []string{}))

+

+	// error path

+	t.Run("createNewName - error path - collision", testErrorPath("test-project", []string{newName1, newName2, newName3}))

+}

+

+func TestCreateNewName_Index(t *testing.T) {

+	extras := make(map[string]string)

+	extras["test1"] = "test1"

+

+	profile := Profile{

+		IsDefault: true,

+		Type:      FileType,

+		NamePatterns: []ParameterizedText{

+			"{{.Extras.test1}}-{{$version := getVersion}}{{.Year}}-{{.Month}}-{{.Day}}v{{$version}}.txt",

+		},

+	}

+	newName1 := "test1-2006-01-02v1.txt"

+	newName2 := "test1-2006-01-02v2.txt"

+	newName3 := "test1-2006-01-02v3.txt"

+

+	testHappyPath := func(expectedName string, existingEntities []string) func(t *testing.T) {

+		return func(t *testing.T) {

+			prePatternData := createPrePatternData(now, "test-project", extras)

+			validator := func(newName string) bool { return !slices.Contains(existingEntities, newName) }

+			newName, err := createNewNameForPatterns(now, prePatternData, "test-project", profile.NamePatterns, extras, validator)

+			if err != nil {

+				t.Error("Error creating new name: ", err)

+			}

+			if newName != expectedName {

+				t.Error("Expected name to be ", expectedName, " but got ", newName)

+			}

+			if prePatternData.ProjectName != "test-project" {

+				t.Error("Expected prePatternData.ProjectName to be test-project but got ", prePatternData.ProjectName)

+			}

+			if prePatternData.Year != "2006" {

+				t.Error("Expected prePatternData.Year to be 2006 but got ", prePatternData.Year)

+			}

+			if prePatternData.Month != "01" {

+				t.Error("Expected prePatternData.Month to be 01 but got ", prePatternData.Month)

+			}

+			if prePatternData.Day != "02" {

+				t.Error("Expected prePatternData.Day to be 02 but got ", prePatternData.Day)

+			}

+			if prePatternData.Hour != "15" {

+				t.Error("Expected prePatternData.Hour to be 15 but got ", prePatternData.Hour)

+			}

+			if prePatternData.Minute != "04" {

+				t.Error("Expected prePatternData.Minute to be 04 but got ", prePatternData.Minute)

+			}

+			if prePatternData.Second != "05" {

+				t.Error("Expected prePatternData.Second to be 05 but got ", prePatternData.Second)

+			}

+		}

+	}

+

+	// test code

+

+	// happy path

+	t.Run("createNewName - v1", testHappyPath(newName1, []string{}))

+	t.Run("createNewName - v2", testHappyPath(newName2, []string{newName1}))

+	t.Run("createNewName - v3", testHappyPath(newName3, []string{newName1, newName2}))

+	t.Run("createNewName - v2", testHappyPath(newName2, []string{newName1, newName3}))

+}

+

+func TestCreateNewName_sprig(t *testing.T) {

+	profile := Profile{

+		IsDefault:    true,

+		Type:         FileType,

+		NamePatterns: []ParameterizedText{`{{"HELLO" | lower}}`},

+	}

+	projectName := "test-project"

+	prePatternData := createPrePatternData(now, projectName, nil)

+	newName, err := createNewNameForPatterns(

+		now,

+		prePatternData,

+		projectName,

+		profile.NamePatterns,

+		nil,

+		func(newName string) bool {

+			return true

+		})

+	assert.Nil(t, err)

+	assert.Equal(t, "hello", newName)

+}