fuzzyworld

template / commit

Adding file templates support:

71bdf574ec2910ca9cccf9827ef008d2b97a5290 · Pavel Safronov · 2022-09-23 14:27:50 -0700 · parents c62570e23b9c

Adding file templates support:
- file profiles can specify a file template
- template is templated with name, time, and project name
- added install script that copies the templates to user directory
- removed extraneous tasks and launch configs, no just using a single launch config to reinstall everything
- added a good starting template for blog posts
 .vscode/launch.json        | 18 ++----------
 .vscode/tasks.json         | 13 +--------
 config.go                  | 29 +++++++++++--------
 file.go                    | 48 +++++++++++++++++++++++++++++++
 name_pattern.go            | 61 +++++++++++++++++++++++++++++++++++++++-
 profile.go                 | 70 ++++++++--------------------------------------
 scripts/clean-install.ps1  |  7 +++++
 templates/blog-template.md | 11 ++++++++
 8 files changed, 158 insertions(+), 99 deletions(-)

diff --git a/.vscode/launch.json b/.vscode/launch.json
index 8f03d80..c823651 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,24 +5,10 @@
     "version": "0.2.0",
     "configurations": [
         {
-            "name": "Delete ~/.template.yaml",
+            "name": "Clean install",
             "type": "PowerShell",
             "request": "launch",
-            "script": "Remove-Item ~/.template.yaml -ErrorAction SilentlyContinue",
-            "cwd": "${workspaceFolder}"
-        },
-        {
-            "name": "Install template",
-            "type": "PowerShell",
-            "request": "launch",
-            "script": "go install",
-            "cwd": "${workspaceFolder}"
-        },
-        {
-            "name": "Refresh install: install and delete config",
-            "type": "PowerShell",
-            "request": "launch",
-            "script": "go install; Remove-Item ~/.template.yaml -ErrorAction SilentlyContinue",
+            "script": "./scripts/clean-install.ps1",
             "cwd": "${workspaceFolder}"
         },
         {
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 2e7bb7f..f89b479 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,16 +2,5 @@
     // See https://go.microsoft.com/fwlink/?LinkId=733558
     // for the documentation about the tasks.json format
     "version": "2.0.0",
-    "tasks": [
-        {
-            "label": "remove-template-yaml",
-            "type": "shell",
-            "command": "rm ~/.template.yaml -ErrorAction SilentlyContinue",
-            "options": {
-                "shell": {
-                    "executable": "powershell.exe"
-                }
-            }
-        }
-    ]
+    "tasks": []
 }
\ No newline at end of file
diff --git a/config.go b/config.go
index 7f9a6cd..89416ab 100644
--- a/config.go
+++ b/config.go
@@ -23,14 +23,15 @@ const (
 )
 
 type Profile struct {
-	IsDefault    bool
-	Name         string
-	Path         string
-	Type         ProfileType
-	NamePatterns []string
-	PostScript   []string
-	ScriptShell  string
-	OS           OSType
+	IsDefault        bool
+	Name             string
+	Path             string
+	Type             ProfileType
+	NamePatterns     []string
+	PostScript       []string
+	ScriptShell      string
+	OS               OSType
+	FileTemplatePath string
 
 	// FileTemplatePath string
 }
@@ -79,10 +80,11 @@ func defaultConfig() Config {
 				},
 			},
 			{
-				Name: "blog",
-				OS:   "All",
-				Path: "~/Dropbox/Blog",
-				Type: FileType,
+				Name:             "blog",
+				OS:               "All",
+				Path:             "~/Dropbox/Blog",
+				FileTemplatePath: "~/.templates/blog-template.md",
+				Type:             FileType,
 				NamePatterns: []string{
 					"{{$version := getVersion}}" +
 						"{{.Year}}-{{.Month}}-{{.Day}}" +
@@ -137,6 +139,9 @@ func validateConfig(config Config) error {
 		if profile.Type == "" || (profile.Type != FileType && profile.Type != FolderType) {
 			return fmt.Errorf("Invalid profile type %s for profile %s", profile.Type, profile.Name)
 		}
+		if profile.Type != FileType && profile.FileTemplatePath != "" {
+			return fmt.Errorf("File template path is not supported for profile %s, which has Type=%s", profile.Name, profile.Type)
+		}
 	}
 	return nil
 }
diff --git a/file.go b/file.go
new file mode 100644
index 0000000..fdc1af9
--- /dev/null
+++ b/file.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"text/template"
+	"time"
+)
+
+type FilePatternData struct {
+	Name        string
+	ProjectName string
+	Time        string
+}
+
+func createFilePatternData(now time.Time, projectName string, name string) FilePatternData {
+	return FilePatternData{
+		Name:        name,
+		ProjectName: projectName,
+		Time:        now.Format("Monday, Jan 02, 2006 15:04:05 MST"),
+	}
+}
+
+func createTemplateFile(srcPath string, destPath string, projectName string, name string) error {
+	now := time.Now()
+	filePatternData := createFilePatternData(now, projectName, name)
+
+	srcPath = getFullPath(srcPath)
+	srcName := filepath.Base(srcPath)
+	template, err := template.New(srcName).ParseFiles(srcPath)
+	if err != nil {
+		return fmt.Errorf("Error parsing source file '%v': %v", srcPath, err)
+	}
+
+	destPath = getFullPath(destPath)
+	writer, err := os.Create(destPath)
+	if err != nil {
+		return fmt.Errorf("Error creating destination file '%v': %v", destPath, err)
+	}
+
+	err = template.Execute(writer, filePatternData)
+	if err != nil {
+		return fmt.Errorf("Error executing template: %v", err)
+	}
+
+	return nil
+}
diff --git a/name_pattern.go b/name_pattern.go
index 064c521..2a79908 100644
--- a/name_pattern.go
+++ b/name_pattern.go
@@ -1,6 +1,13 @@
 package main
 
-import "time"
+import (
+	"errors"
+	"strings"
+	"text/template"
+	"time"
+
+	"golang.org/x/exp/slices"
+)
 
 type NamePatternData struct {
 	Year, Month, Day, Hour, Minute, Second string
@@ -30,3 +37,55 @@ func createNamePatternData(now time.Time, projectName string) NamePatternData {
 
 	return patternInfo
 }
+
+func createNewNameForProfile(profile Profile, patternData NamePatternData, existingEntities []string) (string, error) {
+	for index, namePattern := range profile.NamePatterns {
+
+		startingVersion := 1
+		invokeCount := 0
+		funcMap := template.FuncMap{
+			"getVersion": func() int {
+				invokeCount++
+				return startingVersion
+			},
+		}
+		template := template.New("template-name").Funcs(funcMap)
+
+		for {
+
+			template, err := template.Parse(namePattern)
+			if err != nil {
+				return "", err
+			}
+
+			sb := new(strings.Builder)
+			err = template.Execute(sb, patternData)
+			if err != nil {
+				return "", err
+			}
+
+			newName := sb.String()
+			alreadyExists := slices.Contains(existingEntities, newName)
+			useIndex := invokeCount > 0
+			isLastPattern := index == len(profile.NamePatterns)-1
+
+			// if no conflict, use this name
+			if !alreadyExists {
+				return newName, nil
+			}
+			// if index is not being used (template did not invoke getVersion)
+			// then we're done
+			if !useIndex {
+				break
+			}
+
+			// 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")
+			}
+
+			startingVersion++
+		}
+	}
+	return "", errors.New("Couldn't find a new name")
+}
diff --git a/profile.go b/profile.go
index b183961..d70b936 100644
--- a/profile.go
+++ b/profile.go
@@ -8,10 +8,7 @@ import (
 	"path/filepath"
 	"runtime"
 	"strings"
-	"text/template"
 	"time"
-
-	"golang.org/x/exp/slices"
 )
 
 func findProfile(config Config, profileName string) (Profile, error) {
@@ -109,11 +106,20 @@ func processProfile(profile Profile, projectName string) error {
 		fmt.Printf("Creating directory: %v\n", fullPath)
 		err = os.Mkdir(fullPath, os.ModePerm)
 		if err != nil {
-			return err
+			return fmt.Errorf("Error creating directory: %v", err)
 		}
 	} else {
-		fmt.Printf("Creating file: %v\n", fullPath)
-		os.WriteFile(fullPath, []byte{}, os.ModePerm)
+		useTemplateFile := profile.FileTemplatePath != ""
+		if useTemplateFile {
+			fmt.Printf("Creating file '%v' from template: %v\n", fullPath, profile.FileTemplatePath)
+			err := createTemplateFile(profile.FileTemplatePath, fullPath, projectName, newName)
+			if err != nil {
+				return fmt.Errorf("Error creating template file: %v", err)
+			}
+		} else {
+			fmt.Printf("Creating empty file: %v\n", fullPath)
+			os.WriteFile(fullPath, []byte{}, os.ModePerm)
+		}
 	}
 
 	// execute PostScript, if present
@@ -135,55 +141,3 @@ func processProfile(profile Profile, projectName string) error {
 
 	return nil
 }
-
-func createNewNameForProfile(profile Profile, patternData NamePatternData, existingEntities []string) (string, error) {
-	for index, namePattern := range profile.NamePatterns {
-
-		startingVersion := 1
-		invokeCount := 0
-		funcMap := template.FuncMap{
-			"getVersion": func() int {
-				invokeCount++
-				return startingVersion
-			},
-		}
-		template := template.New("template-name").Funcs(funcMap)
-
-		for {
-
-			template, err := template.Parse(namePattern)
-			if err != nil {
-				return "", err
-			}
-
-			sb := new(strings.Builder)
-			err = template.Execute(sb, patternData)
-			if err != nil {
-				return "", err
-			}
-
-			newName := sb.String()
-			alreadyExists := slices.Contains(existingEntities, newName)
-			useIndex := invokeCount > 0
-			isLastPattern := index == len(profile.NamePatterns)-1
-
-			// if no conflict, use this name
-			if !alreadyExists {
-				return newName, nil
-			}
-			// if index is not being used (template did not invoke getVersion)
-			// then we're done
-			if !useIndex {
-				break
-			}
-
-			// 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")
-			}
-
-			startingVersion++
-		}
-	}
-	return "", errors.New("Couldn't find a new name")
-}
diff --git a/scripts/clean-install.ps1 b/scripts/clean-install.ps1
new file mode 100644
index 0000000..ce4b25b
--- /dev/null
+++ b/scripts/clean-install.ps1
@@ -0,0 +1,7 @@
+$scriptPath = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
+Set-Location "$scriptPath/.."
+
+Remove-Item ~/.template.yaml -ErrorAction SilentlyContinue
+go install
+Remove-Item ~/.templates -Recurse -ErrorAction SilentlyContinue
+Copy-Item .\templates -Destination ~/.templates -Recurse
\ No newline at end of file
diff --git a/templates/blog-template.md b/templates/blog-template.md
new file mode 100644
index 0000000..bccf68f
--- /dev/null
+++ b/templates/blog-template.md
@@ -0,0 +1,11 @@
+# {{.ProjectName}}
+
+Created by template, on {{.Time}}
+
+## Spelling
+
+cspell:words nanite
+
+## Removed writing
+
+## Writing