fuzzyworld

template / commit

Lots of internal changes:

687d562601d3d5b11c845de760d19fd8cc8eb774 · Pavel Safronov · 2022-10-28 15:14:48 -0700 · parents 8179bb1c72c3

Lots of internal changes:
- simplified the pattern data to be of two types: pre-generation, post-generation, and this simplified a lot of things
- simplifications and testing
- added a way to run the postscripts even if the generation failed (for the 'daily' profile, this just opens VSCode in the destination folder)
 default_config.go    |  36 ++++++++-----
 file.go              |  61 +++++++++++-----------
 main.go              |   6 +--
 name_pattern.go      |  24 ++++-----
 name_pattern_test.go |  49 ++++++++++++++++--
 parameter_data.go    |  27 ++++++++++
 postscript.go        |  18 ++-----
 postscript_test.go   |  17 ++++---
 profile.go           | 141 +++++++++++++++++++++++++++++----------------------
 9 files changed, 234 insertions(+), 145 deletions(-)

diff --git a/default_config.go b/default_config.go
index 24946c1..48c9202 100644
--- a/default_config.go
+++ b/default_config.go
@@ -10,9 +10,9 @@ func defaultConfig() Config {
 				Type:         FileType,
 				NamePatterns: []ParameterizedText{"{{.Year}}-{{.Month}}-{{.Day}}.md"},
 				File: File{
-					Template: `# {{.Year}}-{{.Month}}-{{.Day}}
+					Template: `# {{.FileNameWithoutExtension}}
 
-Today's notes
+Today's notes for {{.Year}}-{{.Month}}-{{.Day}}.
 
 ## Todo
 
@@ -25,8 +25,9 @@ Today's notes
 				},
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
-					"code . \"{{.Path}}\"",
+					"code . {{ if .FullPath }}\"{{.FullPath}}\"{{ end }}",
 				},
+				RunPostscriptOnFailure: true,
 			},
 			{
 				Name:        "note",
@@ -44,7 +45,7 @@ Today's notes
 				},
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
-					"code \"{{.Path}}\"",
+					"code \"{{.FullPath}}\"",
 				},
 			},
 			{
@@ -55,7 +56,7 @@ Today's notes
 				File: File{
 					Template: `# {{.ProjectName}}
 
-Created by template, on {{.Time}}
+Created by template, on {{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}.
 
 ## Spelling
 
@@ -76,7 +77,7 @@ cspell:words nanite
 				},
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
-					"code . \"{{.Name}}\"",
+					"code . \"{{.FileName}}\"",
 				},
 			},
 			// {
@@ -102,7 +103,7 @@ cspell:words nanite
 				OS:          "All",
 				Path:        "~/Therapy",
 				File: File{
-					Template: `# {{.Year}}-{{.Month}}-{{.Day}}
+					Template: `# {{.FilenameWithoutExtension}}
 
 {{if .ProjectName}}Therapy notes on {{.ProjectName}}{{else}}Therapy notes{{end}}
 
@@ -124,7 +125,7 @@ Sincerely, me
 				},
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
-					"code . \"{{.Name}}\"",
+					"code . \"{{.FileName}}\"",
 				},
 			},
 			{
@@ -140,10 +141,11 @@ Sincerely, me
 				},
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
-					"code \"{{.Path}}\"",
+					"code .",
+					"chmod +x repro.sh",
 				},
 				Files: []FileInFolder{
-					FileInFolder{
+					{
 						File: File{
 							Template: `# {{.ProjectName}}
 
@@ -152,6 +154,14 @@ Work notes on {{.ProjectName}}
 						},
 						Name: "README.md",
 					},
+					{
+						File: File{
+							Template: `clear
+
+echo "Let's test {{.ProjectName}}!"`,
+						},
+						Name: "repro.sh",
+					},
 				},
 			},
 			{
@@ -198,7 +208,7 @@ Work notes on {{.ProjectName}}
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
 					"git init",
-					"code \"{{.Path}}\"",
+					"code .",
 				},
 			},
 			{
@@ -210,8 +220,8 @@ Work notes on {{.ProjectName}}
 				},
 				ScriptShell: "pwsh",
 				PostScripts: []ParameterizedText{
-					"echo \"Name={{.Name}}\"",
-					"echo \"Path={{.Path}}\"",
+					"echo \"Name={{.FileName}}\"",
+					"echo \"Path={{.FullPath}}\"",
 					"echo \"Value1={{.Extras.value1}}\"",
 					"echo \"Value2={{.Extras.value2}}\"",
 				},
diff --git a/file.go b/file.go
index 5574051..90bd5d0 100644
--- a/file.go
+++ b/file.go
@@ -2,38 +2,20 @@ package main
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os/user"
 	"path/filepath"
 	"strings"
 	"text/template"
-	"time"
 )
 
-type FilePatternData struct {
-	Name             string
-	ProjectName      string
-	Time             string
-	Year, Month, Day string
-	Extras           map[string]string
-}
-
-func createFilePatternData(now time.Time, projectName string, name string, extras map[string]string) FilePatternData {
-	return FilePatternData{
-		Name:        name,
-		ProjectName: projectName,
-		Time:        now.Format("Monday, Jan 02, 2006 15:04:05 MST"),
-		Year:        now.Format("2006"),
-		Month:       now.Format("01"),
-		Day:         now.Format("02"),
-		Extras:      extras,
-	}
-}
-
-func createFileFromTemplateFile(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)
-
+func createFileFromTemplateFile(
+	srcPath string,
+	destPath string,
+	name string,
+	prePatternData PrePatternData,
+	usr user.User) error {
 	// standardize paths
 	srcPath = getFullPath(srcPath, usr)
 	destPath = getFullPath(destPath, usr)
@@ -52,6 +34,7 @@ func createFileFromTemplateFile(srcPath string, destPath string, projectName str
 	}
 
 	// write template to output file
+	filePatternData := createPostPatternData(filepath.Base(destPath), prePatternData)
 	err = template.Execute(writer, filePatternData)
 	if err != nil {
 		return fmt.Errorf("error executing template: %v", err)
@@ -60,9 +43,9 @@ func createFileFromTemplateFile(srcPath string, destPath string, projectName str
 	return nil
 }
 
-func createTemplateString(templateText ParameterizedText, projectName string, name string, extras map[string]string) (string, error) {
-	now := time.Now()
-	filePatternData := createFilePatternData(now, projectName, name, extras)
+func createTemplateString(
+	templateText ParameterizedText,
+	postPatternData PostPatternData) (string, error) {
 
 	// parse template
 	template, err := template.New("text-template").Parse(string(templateText))
@@ -72,10 +55,30 @@ func createTemplateString(templateText ParameterizedText, projectName string, na
 
 	// create output string
 	sb := new(strings.Builder)
-	err = template.Execute(sb, filePatternData)
+	err = template.Execute(sb, postPatternData)
 	if err != nil {
 		return "", fmt.Errorf("error creating templated file: %v", err)
 	}
 
 	return sb.String(), nil
 }
+
+func createFile(
+	prePatternData PrePatternData,
+	file File,
+	fullPath string) error {
+	useTemplate := file.Template != ""
+	if useTemplate {
+		postPatternData := createPostPatternData(fullPath, prePatternData)
+		fmt.Printf("Creating file '%v' from template string\n", fullPath)
+		templateString, err := createTemplateString(file.Template, postPatternData)
+		if err != nil {
+			return fmt.Errorf("error creating template: %v", err)
+		}
+		ioutil.WriteFile(fullPath, []byte(templateString), os.ModePerm)
+	} else {
+		fmt.Printf("Creating empty file: %v\n", fullPath)
+		os.WriteFile(fullPath, []byte{}, os.ModePerm)
+	}
+	return nil
+}
diff --git a/main.go b/main.go
index 4ae11ff..c5eb514 100644
--- a/main.go
+++ b/main.go
@@ -154,9 +154,9 @@ func main() {
 			}
 
 			// process profile
-			err = processProfile(profile, projectName, extras, *usr)
-			if err != nil {
-				return fmt.Errorf("error processing profile: %v", err)
+			errors := processProfile(profile, projectName, extras, *usr)
+			if len(errors) > 0 {
+				return fmt.Errorf("error processing profile: %v", errors)
 			}
 
 			return nil
diff --git a/name_pattern.go b/name_pattern.go
index 0ef34b3..9e60573 100644
--- a/name_pattern.go
+++ b/name_pattern.go
@@ -10,13 +10,11 @@ import (
 	"golang.org/x/exp/slices"
 )
 
-type NamePatternData struct {
-	Year, Month, Day, Hour, Minute, Second string
-	ProjectName                            string
-	Extras                                 map[string]string
-}
-
-func createNewNameForPatterns(now time.Time, projectName string, namePatterns []ParameterizedText, extras map[string]string, existingEntities []string) (string, error) {
+func createNewNameForPatterns(
+	now time.Time,
+	projectName string,
+	namePatterns []ParameterizedText,
+	extras map[string]string, existingEntities []string) (string, PrePatternData, error) {
 	for index, namePattern := range namePatterns {
 
 		startingVersion := 1
@@ -28,7 +26,7 @@ func createNewNameForPatterns(now time.Time, projectName string, namePatterns []
 			},
 		}
 		template := template.New("template-name").Funcs(funcMap)
-		patternData := NamePatternData{
+		patternData := PrePatternData{
 			Year:        now.Format("2006"),
 			Month:       now.Format("01"),
 			Day:         now.Format("02"),
@@ -43,13 +41,13 @@ func createNewNameForPatterns(now time.Time, projectName string, namePatterns []
 
 			template, err := template.Parse(string(namePattern))
 			if err != nil {
-				return "", fmt.Errorf("error parsing name pattern %d: %w", index, err)
+				return "", patternData, fmt.Errorf("error parsing name pattern %d: %w", index, err)
 			}
 
 			sb := new(strings.Builder)
 			err = template.Execute(sb, patternData)
 			if err != nil {
-				return "", fmt.Errorf("error executing name pattern %d: %w", index, err)
+				return "", patternData, fmt.Errorf("error executing name pattern %d: %w", index, err)
 			}
 
 			newName := sb.String()
@@ -59,7 +57,7 @@ func createNewNameForPatterns(now time.Time, projectName string, namePatterns []
 
 			// if no conflict, use this name
 			if !alreadyExists {
-				return newName, nil
+				return newName, patternData, nil
 			}
 			// if index is not being used (template did not invoke getVersion)
 			// then we're done
@@ -69,11 +67,11 @@ func createNewNameForPatterns(now time.Time, projectName string, namePatterns []
 
 			// 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 "", patternData, 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 "", PrePatternData{}, errors.New("couldn't find a new name")
 }
diff --git a/name_pattern_test.go b/name_pattern_test.go
index 6e53ae6..4cf5db5 100644
--- a/name_pattern_test.go
+++ b/name_pattern_test.go
@@ -29,18 +29,40 @@ func TestCreateNewName(t *testing.T) {
 
 	testHappyPath := func(projectName string, expectedName string, existingEntities []string) func(t *testing.T) {
 		return func(t *testing.T) {
-			newName, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, existingEntities)
+			newName, prePatternData, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, existingEntities)
 			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) {
-			_, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, existingEntities)
+			_, _, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, existingEntities)
 			if err == nil {
 				t.Error("Expected error but got none")
 			}
@@ -78,13 +100,34 @@ func TestCreateNewName_Index(t *testing.T) {
 
 	testHappyPath := func(expectedName string, existingEntities []string) func(t *testing.T) {
 		return func(t *testing.T) {
-			newName, err := createNewNameForPatterns(now, "test-project", profile.NamePatterns, extras, existingEntities)
+			newName, prePatternData, err := createNewNameForPatterns(now, "test-project", profile.NamePatterns, extras, existingEntities)
 			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)
+			}
 		}
 	}
 
diff --git a/parameter_data.go b/parameter_data.go
new file mode 100644
index 0000000..d4b5f09
--- /dev/null
+++ b/parameter_data.go
@@ -0,0 +1,27 @@
+package main
+
+import "path/filepath"
+
+type PrePatternData struct {
+	ProjectName                            string
+	Year, Month, Day, Hour, Minute, Second string
+	Extras                                 map[string]string
+}
+
+type PostPatternData struct {
+	PrePatternData
+	FileName                 string
+	FileNameWithoutExtension string
+	FullPath                 string
+}
+
+func createPostPatternData(fullPath string, prePatternData PrePatternData) PostPatternData {
+	fileName := filepath.Base(fullPath)
+	fileNameWithoutExt := fileName[:len(fileName)-len(filepath.Ext(fileName))]
+	return PostPatternData{
+		FullPath:                 fullPath,
+		FileName:                 fileName,
+		FileNameWithoutExtension: fileNameWithoutExt,
+		PrePatternData:           prePatternData,
+	}
+}
diff --git a/postscript.go b/postscript.go
index d9967f5..23d0f6c 100644
--- a/postscript.go
+++ b/postscript.go
@@ -6,25 +6,15 @@ import (
 	"text/template"
 )
 
-type PostScriptPatternData struct {
-	Name   string
-	Path   string
-	Extras map[string]string
-}
-
-func createPostScriptArrays(profile Profile, newName string, fullPath string, extras map[string]string) ([]string, error) {
+func createPostScriptArrays(
+	profile Profile,
+	postPatternData PostPatternData) ([]string, error) {
 	array := make([]string, 0)
 
 	if profile.PostScripts == nil || len(profile.PostScripts) == 0 {
 		return nil, fmt.Errorf("no post scripts specified")
 	}
 
-	patternData := PostScriptPatternData{
-		Name:   newName,
-		Path:   fullPath,
-		Extras: extras,
-	}
-
 	for _, postScriptPattern := range profile.PostScripts {
 		template, err := template.New("postscript").Parse(string(postScriptPattern))
 		if err != nil {
@@ -32,7 +22,7 @@ func createPostScriptArrays(profile Profile, newName string, fullPath string, ex
 		}
 
 		sb := new(strings.Builder)
-		err = template.Execute(sb, patternData)
+		err = template.Execute(sb, postPatternData)
 		if err != nil {
 			return nil, err
 		}
diff --git a/postscript_test.go b/postscript_test.go
index 722cb62..e4e4fae 100644
--- a/postscript_test.go
+++ b/postscript_test.go
@@ -8,22 +8,23 @@ import (
 
 func TestCreatePostScriptArrays(t *testing.T) {
 	testFailure := func(profile Profile) {
-		_, err := createPostScriptArrays(profile, "test", "test", nil)
+		_, err := createPostScriptArrays(profile, PostPatternData{FullPath: "test"})
 		assert.NotNil(t, err, "Expected to fail to create post script arrays, but succeeded")
 	}
 	testSuccess := func(profile Profile) {
-		extras := map[string]string{
-			"foo": "bar",
-		}
-
-		postscripts, err := createPostScriptArrays(profile, "test", "full-path", extras)
+		postscripts, err := createPostScriptArrays(profile, PostPatternData{
+			FullPath: "test",
+			PrePatternData: PrePatternData{
+				Extras: map[string]string{"foo": "bar"},
+			},
+		})
 		assert.Nil(t, err, "Error creating postscript array")
-		assert.Equal(t, postscripts, []string{"echo \"test\""})
+		assert.Equal(t, postscripts, []string{"echo \"test\"-bar "})
 	}
 
 	testSuccess(Profile{
 		PostScripts: []ParameterizedText{
-			"echo \"{{.Name}}\"",
+			"echo \"{{.FullPath}}\"-{{.Extras.foo}} ",
 		},
 	})
 	testFailure(Profile{
diff --git a/profile.go b/profile.go
index cd89c5f..6dd1a56 100644
--- a/profile.go
+++ b/profile.go
@@ -3,7 +3,6 @@ package main
 import (
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"os/user"
@@ -44,16 +43,17 @@ type FileInFolder struct {
 
 type Profile struct {
 	File
-	IsDefault    bool
-	Name         string
-	Description  string
-	Path         string
-	Type         ProfileType
-	NamePatterns []ParameterizedText
-	PostScripts  []ParameterizedText
-	ScriptShell  string
-	OS           OSType
-	Files        []FileInFolder
+	IsDefault              bool
+	Name                   string
+	Description            string
+	Path                   string
+	Type                   ProfileType
+	NamePatterns           []ParameterizedText
+	PostScripts            []ParameterizedText
+	ScriptShell            string
+	OS                     OSType
+	Files                  []FileInFolder
+	RunPostscriptOnFailure bool
 }
 
 var forbiddenProfileNames = []string{
@@ -80,7 +80,7 @@ func findProfile(config Config, profileName string) (Profile, error) {
 	return Profile{}, fmt.Errorf("unable to find profile '%v'", profileName)
 }
 
-func processProfile(profile Profile, projectName string, extras map[string]string, usr user.User) error {
+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,14 +95,14 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 
 	cwd, err := os.Getwd()
 	if err != nil {
-		return fmt.Errorf("unable to get current working directory: %v", err)
+		return []error{fmt.Errorf("unable to get current working directory: %v", err)}
 	}
 	if profile.Path != "" {
 		// change cwd to the profile's path, if one is specified
 		path := getFullPath(profile.Path, usr)
 		err = os.Chdir(path)
 		if err != nil {
-			return fmt.Errorf("unable to change directory (1) to %v: %v", path, err)
+			return []error{fmt.Errorf("unable to change directory (1) to %v: %v", path, err)}
 		}
 		cwd = path
 	}
@@ -110,7 +110,7 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 	// get all entities in current directory
 	files, err := os.ReadDir(cwd)
 	if err != nil {
-		return err
+		return []error{err}
 	}
 
 	// determine if we're dealing with files or directories
@@ -127,57 +127,41 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 
 	// generate a new name based on time, profile, and existing entities
 	now := time.Now()
-	newName, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, entities)
+	errors := []error{}
+	newName, prePatternData, err := createNewNameForPatterns(now, projectName, profile.NamePatterns, extras, entities)
+
 	if err != nil {
-		return err
+		errors = append(errors, err)
+		if !profile.RunPostscriptOnFailure {
+			return errors
+		}
 	}
 
 	// check newName is created
 	if newName == "" {
-		return errors.New("couldn't find a good new name")
+		errors = append(errors, fmt.Errorf("couldn't find a good new name"))
+		if !profile.RunPostscriptOnFailure {
+			return errors
+		}
 	}
 
-	// create new entity
-	fullPath := filepath.Join(cwd, newName)
-	if profileType == FolderType {
-		fmt.Printf("Creating directory: %v\n", fullPath)
-		err = os.Mkdir(fullPath, os.ModePerm)
+	fullPath := ""
+	if err == nil && newName != "" {
+		fullPath, err = createEntity(now, profile, profileType, cwd, newName, projectName, extras, prePatternData)
 		if err != nil {
-			return fmt.Errorf("error creating directory: %v", err)
-		}
-		err := os.Chdir(fullPath)
-		if err != nil {
-			return fmt.Errorf("unable to change directory (2) to %v: %v", fullPath, err)
-		}
-
-		// create files in the new folder
-		if len(profile.Files) > 0 {
-			fmt.Printf("Creating files in directory: %v\n", fullPath)
-			for index, file := range profile.Files {
-				fileName, err := createNewNameForPatterns(now, projectName, []ParameterizedText{file.Name}, extras, []string{})
-				if err != nil {
-					return fmt.Errorf("error creating file name for file index %v : %v", index, err)
-				}
-				subFilePath := filepath.Join(fullPath, fileName)
-				err = createFile(file.File, subFilePath, projectName, fileName, extras, usr)
-				if err != nil {
-					return err
-				}
+			errors = append(errors, fmt.Errorf("unable to create entity: %v", err))
+			if !profile.RunPostscriptOnFailure {
+				return errors
 			}
 		}
-	} else if profileType == FileType {
-		err := createFile(profile.File, fullPath, projectName, newName, extras, usr)
-		if err != nil {
-			return err
-		}
-	} else {
-		fmt.Printf("Profile type is script, doing nothing during main phase\n")
 	}
 
 	// execute PostScript, if present
-	postScripts, err := createPostScriptArrays(profile, newName, fullPath, extras)
+	postPatternData := createPostPatternData(fullPath, prePatternData)
+	postScripts, err := createPostScriptArrays(profile, postPatternData)
 	if err != nil {
-		return err
+		errors = append(errors, fmt.Errorf("unable to create post scripts: %v", err))
+		return errors
 	}
 	for _, postScript := range postScripts {
 		allArgs := []string{"-c", postScript}
@@ -195,20 +179,53 @@ func processProfile(profile Profile, projectName string, extras map[string]strin
 	return nil
 }
 
-func createFile(file File, fullPath string, projectName string, newName string, extras map[string]string, usr user.User) error {
-	useTemplate := file.Template != ""
-	if useTemplate {
-		fmt.Printf("Creating file '%v' from template string\n", fullPath)
-		templateString, err := createTemplateString(file.Template, projectName, newName, extras)
+func createEntity(
+	now time.Time,
+	profile Profile,
+	profileType ProfileType,
+	cwd string,
+	newName string,
+	projectName string,
+	extras map[string]string,
+	prePatternData PrePatternData) (string, error) {
+
+	// create new entity
+	fullPath := filepath.Join(cwd, newName)
+	if profileType == FolderType {
+		fmt.Printf("Creating directory: %v\n", fullPath)
+		err := os.Mkdir(fullPath, os.ModePerm)
+		if err != nil {
+			return "", fmt.Errorf("error creating directory: %v", err)
+		}
+		err = os.Chdir(fullPath)
+		if err != nil {
+			return "", fmt.Errorf("unable to change directory (2) to %v: %v", fullPath, err)
+		}
+
+		// create files in the new folder
+		if len(profile.Files) > 0 {
+			fmt.Printf("Creating files in directory: %v\n", fullPath)
+			for index, file := range profile.Files {
+				fileName, filePrePatternData, err := createNewNameForPatterns(now, projectName, []ParameterizedText{file.Name}, extras, []string{})
+				if err != nil {
+					return "", fmt.Errorf("error creating file name for file index %v : %v", index, err)
+				}
+				subFilePath := filepath.Join(fullPath, fileName)
+				err = createFile(filePrePatternData, file.File, subFilePath)
+				if err != nil {
+					return "", err
+				}
+			}
+		}
+	} else if profileType == FileType {
+		err := createFile(prePatternData, profile.File, fullPath)
 		if err != nil {
-			return fmt.Errorf("error creating template: %v", err)
+			return "", err
 		}
-		ioutil.WriteFile(fullPath, []byte(templateString), os.ModePerm)
 	} else {
-		fmt.Printf("Creating empty file: %v\n", fullPath)
-		os.WriteFile(fullPath, []byte{}, os.ModePerm)
+		fmt.Printf("Profile type is script, doing nothing during main phase\n")
 	}
-	return nil
+	return fullPath, nil
 }
 
 func validateProfile(profile *Profile) error {