fuzzyworld

template / commit

Got basic patterns working:

319b8b3fff54da50ad41578d5fd4aef64960c601 · Pavel Safronov · 2022-09-22 15:43:05 -0700 · parents 230da3bece0f

Got basic patterns working:
- made IsDefault into a boolean property on the profiles.
- if "extra" args are specified, use them as such: first arg is profileName, second arg is ProjectName
- added some tasks to VSCode, including a cleanup task
 .vscode/launch.json |  39 ++++++++++-
 config.go           |  49 ++++++++++----
 main.go             |  78 ++++++++++++----------
 new.go              | 187 +++++++++++++++++++++++++++++++++++++++++++---------
 utils.go            |  20 ++++++
 5 files changed, 293 insertions(+), 80 deletions(-)

diff --git a/.vscode/launch.json b/.vscode/launch.json
index 896dd01..f9195f2 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,7 +5,21 @@
     "version": "0.2.0",
     "configurations": [
         {
-            "name": "Launch Package",
+            "name": "Delete ~/.template.yaml",
+            "type": "PowerShell",
+            "request": "launch",
+            "script": "rm ~/.template.yaml -ErrorAction SilentlyContinue",
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "name": "Run: template",
+            "type": "go",
+            "request": "launch",
+            "mode": "auto",
+            "program": ".",
+        },
+        {
+            "name": "Run: template blog",
             "type": "go",
             "request": "launch",
             "mode": "auto",
@@ -13,6 +27,29 @@
             "args": [
                 "blog"
             ]
+        },
+        {
+            "name": "Run: template work",
+            "type": "go",
+            "request": "launch",
+            "mode": "auto",
+            "program": ".",
+            "args": [
+                "work"
+            ]
+        },
+        {
+            "name": "Run: template --profile work --name testProj",
+            "type": "go",
+            "request": "launch",
+            "mode": "auto",
+            "program": ".",
+            "args": [
+                "--profile",
+                "work",
+                "--name",
+                "testProj"
+            ]
         }
     ]
 }
\ No newline at end of file
diff --git a/config.go b/config.go
index 2aa8cfe..962eccc 100644
--- a/config.go
+++ b/config.go
@@ -14,9 +14,12 @@ const (
 )
 
 type Profile struct {
+	IsDefault    bool
 	Path         string
 	Type         ProfileType
 	NamePatterns []string
+	PostScript   []string
+
 	// ReplacementStrings map[string][]string
 	// FileTemplatePath string
 }
@@ -25,31 +28,53 @@ type Config struct {
 	Profiles map[string]Profile
 }
 
-type PatternInfo struct {
-	Year4, Month2, Day2, Hour2, Minute2, Second2 string
-	ProjectName                                  string
-}
-
 func defaultConfig() Config {
 	return Config{
 		Profiles: map[string]Profile{
-			"default": {
-				Path: "C:/DailyNotes",
-				Type: FileType,
+			"daily": {
+				IsDefault: true,
+				Path:      "C:\\work\\daily-notes",
+				Type:      FileType,
 				NamePatterns: []string{
-					"{{.Year4}}-{{.Month2}}-{{.Day2}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
-					"{{.Year4}}-{{.Month2}}-{{.Day2}}-{{.Hour2}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
-					"{{.Year4}}-{{.Month2}}-{{.Day2}}-{{.Hour2}}-{{.Minute2}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
-					"{{.Year4}}-{{.Month2}}-{{.Day2}}-{{.Hour2}}-{{.Minute2}}-{{.Second2}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+					"{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+				},
+				PostScript: []string{
+					"code",
+					".",
+					"{{.Path}}",
 				},
 			},
 			"blog": {
 				Path: "~/Dropbox/Blog",
 				Type: FileType,
+				NamePatterns: []string{
+					"{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}{{if .ProjectName}}-{{.ProjectName}}{{end}}.md",
+				},
+				PostScript: []string{
+					"code",
+					".",
+					"{{.Name}}",
+				},
 			},
 			"work": {
 				Path: "C:/work",
 				Type: FolderType,
+				NamePatterns: []string{
+					"{{.Year}}-{{.Month}}-{{.Day}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
+					"{{.Year}}-{{.Month}}-{{.Day}}-{{.Hour}}-{{.Minute}}-{{.Second}}{{if .ProjectName}}-{{.ProjectName}}{{end}}",
+				},
+				PostScript: []string{
+					"explorer",
+					"{{.Path}}",
+				},
 			},
 		},
 	}
diff --git a/main.go b/main.go
index eea92df..d1e87f6 100644
--- a/main.go
+++ b/main.go
@@ -4,13 +4,13 @@ import (
 	"fmt"
 	"log"
 	"os"
-	"path/filepath"
 
 	"github.com/urfave/cli/v2" // imports as package "cli"
 )
 
 func main() {
-	defaultProfileName := "default"
+	configFilePath := getFullPath("~/.template.yaml")
+	// defaultProfileName := "default"
 	var profileName string
 	var projectName string
 
@@ -20,9 +20,10 @@ func main() {
 		EnableBashCompletion: true,
 		Flags: []cli.Flag{
 			&cli.StringFlag{
-				Name:        "profile",
-				Aliases:     []string{"p"},
-				Value:       defaultProfileName,
+				Name:    "profile",
+				Aliases: []string{"p"},
+				Value:   "",
+				// Value:       defaultProfileName,
 				Usage:       "`PROFILE` to use for the template",
 				Destination: &profileName,
 				// Required:    true,
@@ -37,25 +38,10 @@ func main() {
 			},
 		},
 		Action: func(context *cli.Context) error {
-			// get profile name from args
-			if profileName == defaultProfileName && context.Args().Len() > 0 {
-				profileName = context.Args().Get(0)
-			}
-			message := fmt.Sprintf("Profile: %v", profileName)
-			fmt.Println(message)
-
-			// construct config file path
-			userProfile, err := os.UserHomeDir()
-			if err != nil {
-				processError(err, "Getting user home dir")
-			}
-			configFilePath := filepath.Join(userProfile, ".template.yaml")
-
 			// try and load config
 			config, err := loadConfig(configFilePath)
 			if err != nil {
-				fmt.Printf("No config present at '%v', creating and using sample config", configFilePath)
-				fmt.Println()
+				fmt.Printf("No config present at '%v', creating and using sample config\n", configFilePath)
 
 				// create config and save
 				err := saveConfig(configFilePath, defaultConfig())
@@ -70,24 +56,46 @@ func main() {
 				}
 			} else {
 				// log successful load
-				fmt.Printf("Config loaded from '%v'", configFilePath)
-				fmt.Println()
+				fmt.Printf("Config loaded from '%v'\n", configFilePath)
 			}
 
-			// find profile by name
-			profile, ok := config.Profiles[profileName]
-			if ok {
-				fmt.Println("Found profile!")
-				// fmt.Println(profile)
-			} else {
-				processError(fmt.Errorf("Unable to find profile '%v'", profileName), "Error finding profile")
+			// read args
+			args := context.Args()
+
+			// get profile name from args
+			// if not set explicitly, use first arg, if present
+			if profileName == "" && args.Len() > 0 {
+				profileName = args.Get(0)
+			}
+			fmt.Println("Profile:", profileName)
+
+			// get project name from args
+			// if not set explicitly, use second arg, if present
+			if projectName == "" && args.Len() > 1 {
+				projectName = args.Get(1)
+			}
+			if projectName != "" {
+				fmt.Println("Project name:", projectName)
+			}
+
+			// find profile
+			profile, err := findProfile(config, profileName)
+			if err != nil {
+				processError(err, "Error while finding profile")
 			}
 
-			// cwd, err := os.Getwd()
-			// if err != nil {
-			// 	panic(err)
-			// }
-			processProfile(profile, projectName, profile.Path)
+			// change cwd
+			path := getFullPath(profile.Path)
+			err = os.Chdir(path)
+			if err != nil {
+				processError(err, fmt.Sprintf("Error while changing directory to '%v'", path))
+			}
+
+			// process profile
+			err = processProfile(profile, projectName)
+			if err != nil {
+				processError(err, "Error processing profile")
+			}
 
 			return nil
 		},
diff --git a/new.go b/new.go
index 30e787c..2812a37 100644
--- a/new.go
+++ b/new.go
@@ -4,7 +4,8 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"path"
+	"os/exec"
+	"path/filepath"
 	"strings"
 	"text/template"
 	"time"
@@ -12,12 +13,45 @@ import (
 	"golang.org/x/exp/slices"
 )
 
-func processProfile(profile Profile, name string, cwd string) error {
+func findProfile(config Config, name string) (Profile, error) {
+	if name == "" {
+		// use default profile
+		for _, p := range config.Profiles {
+			if p.IsDefault {
+				return p, nil
+			}
+		}
+	} else {
+		// find profile by name
+		profile, ok := config.Profiles[name]
+		if ok {
+			return profile, nil
+		} else {
+			return Profile{}, fmt.Errorf("Unable to find profile '%v'", name)
+		}
+	}
+	return Profile{}, errors.New("Couldn't find profile")
+}
+
+func processProfile(profile Profile, name string) error {
+	// validate profile
+	if profile.NamePatterns == nil || len(profile.NamePatterns) == 0 {
+		return errors.New("No name patterns specified")
+	}
+
+	// get current directory
+	cwd, err := os.Getwd()
+	if err != nil {
+		return err
+	}
+
+	// get all entities in current directory
 	files, err := os.ReadDir(cwd)
 	if err != nil {
 		return err
 	}
 
+	// determine if we're dealing with files or directories
 	var useDirs bool
 	switch profile.Type {
 	case FileType:
@@ -28,6 +62,7 @@ func processProfile(profile Profile, name string, cwd string) error {
 		return fmt.Errorf("Unknown profile type: %v", profile.Type)
 	}
 
+	// filter entities to just files or directories
 	var entities []string
 	for _, file := range files {
 		isDir := file.IsDir()
@@ -36,50 +71,138 @@ func processProfile(profile Profile, name string, cwd string) error {
 		}
 	}
 
-	var newName string
+	// generate a new name based on profile and existing entities
+	newName, err := createNewName(name, profile, entities)
+	if err != nil {
+		return err
+	}
+
+	fullPath := filepath.Join(cwd, newName)
+
+	// create new entity
+	if newName == "" {
+		return errors.New("Couldn't find a good new name")
+	} else {
+		fmt.Printf("newName: %v\n", newName)
+		if useDirs {
+			fmt.Printf("Creating directory: %v\n", fullPath)
+			err = os.Mkdir(fullPath, os.ModePerm)
+			if err != nil {
+				return err
+			}
+		} else {
+			fmt.Printf("Creating file: %v\n", fullPath)
+			os.WriteFile(fullPath, []byte{}, os.ModePerm)
+		}
+	}
+
+	// execute PostScript, if present
+	postScripts, err := createPostScriptArray(profile, newName, fullPath)
+	if err != nil {
+		return err
+	}
+	if postScripts != nil && len(postScripts) > 0 {
+		allArgs := []string{"/C"}
+		allArgs = append(allArgs, postScripts...)
+		fmt.Printf("Executing post script: cmd %v \n", strings.Trim(fmt.Sprint(allArgs), "[]"))
+		cmd := exec.Command("cmd", allArgs...)
+		if err := cmd.Run(); err != nil {
+			// return err
+		}
+	}
+
+	return nil
+}
+
+type NamePatternData struct {
+	Year, Month, Day, Hour, Minute, Second string
+	ProjectName                            string
+}
+type PostScriptPatternData struct {
+	Name string
+	Path string
+}
+
+func createNewName(name string, profile Profile, existingEntities []string) (string, error) {
 	now := time.Now()
-	patternInfo := PatternInfo{
-		Year4: now.Format("2006"),
-		Month2:   now.Format("01"),
-		Day2:   now.Format("02"),
-		Hour2:   now.Format("15"),
-		Minute2:   now.Format("04"),
-		Second2:   now.Format("04"),
-		ProjectName: name,
+	patternInfos := createNamePatternData(now, name)
+	for _, patternInfo := range patternInfos {
+		newName, err := createNewNameForProfile(profile, patternInfo, existingEntities)
+		if err != nil {
+			return "", err
+		} else {
+			return newName, nil
+		}
+	}
+	return "", errors.New("Couldn't find a new name")
+}
+
+func createNamePatternData(now time.Time, projectName string) []NamePatternData {
+	patternInfo := NamePatternData{
+		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,
 	}
+
+	return []NamePatternData{patternInfo}
+}
+
+func createNewNameForProfile(profile Profile, patternData NamePatternData, existingEntities []string) (string, error) {
 	for _, namePattern := range profile.NamePatterns {
-		newName = ""
-		t, err := template.New("template-name").Parse(namePattern)
+		template, err := template.New("template-name").Parse(namePattern)
 		if err != nil {
-			panic(err)
+			return "", err
 		}
+
 		sb := new(strings.Builder)
-		err = t.Execute(sb, patternInfo)
+		err = template.Execute(sb, patternData)
 		if err != nil {
-			panic(err)
+			return "", err
 		}
-		newName = sb.String()
-		alreadyExists := slices.Contains(entities, newName)
-		if alreadyExists {
-			break
+
+		newName := sb.String()
+		alreadyExists := slices.Contains(existingEntities, newName)
+		if !alreadyExists {
+			return newName, nil
 		}
 	}
+	return "", errors.New("Couldn't find a new name")
+}
 
-	if newName == "" {
-		panic(errors.New("Couldn't find a good new name"))
-	} else {
-		fullName := path.Join(cwd, newName)
-		if useDirs {
-			err = os.Mkdir(fullName, os.ModePerm)
-			if err != nil {
-				panic(err)
-			}
-		} else {
-			os.WriteFile(fullName, []byte{}, os.ModePerm)
+func createPostScriptArray(profile Profile, name string, path string) ([]string, error) {
+	array := make([]string, 0)
+
+	if profile.PostScript == nil || len(profile.PostScript) == 0 {
+		return nil, nil
+	}
+
+	patternData := PostScriptPatternData{
+		Name: name,
+		Path: path,
+	}
+
+	for _, postScriptPattern := range profile.PostScript {
+
+		template, err := template.New("template-name").Parse(postScriptPattern)
+		if err != nil {
+			return nil, err
+		}
+
+		sb := new(strings.Builder)
+		err = template.Execute(sb, patternData)
+		if err != nil {
+			return nil, err
 		}
+
+		postScript := sb.String()
+		array = append(array, postScript)
 	}
 
-	return nil
+	return array, nil
 }
 
 // func getNextEntry(name string, existingNames []string, profile Profile) string {
diff --git a/utils.go b/utils.go
index d76a032..bbc6035 100644
--- a/utils.go
+++ b/utils.go
@@ -3,6 +3,9 @@ package main
 import (
 	"fmt"
 	"os"
+	"os/user"
+	"path/filepath"
+	"strings"
 )
 
 func processError(err error, message string) {
@@ -15,3 +18,20 @@ func processError(err error, message string) {
 
 	os.Exit(2)
 }
+
+func getFullPath(path string) string {
+	usr, _ := user.Current()
+	dir := usr.HomeDir
+	var newPath string
+	if path == "~" {
+		// In case of "~", which won't be caught by the "else if"
+		newPath = dir
+	} else if strings.HasPrefix(path, "~/") {
+		// Use strings.HasPrefix so we don't match paths like
+		// "/something/~/something/"
+		newPath = filepath.Join(dir, path[2:])
+	} else {
+		newPath = path
+	}
+	return newPath
+}