template / commit
Lots of internal changes:
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 {