From 9d8b69bfe41c62c19d15746f4f089b0be0cfe664 Mon Sep 17 00:00:00 2001 From: Fabian Barajas Date: Sat, 9 Aug 2025 04:46:40 -0700 Subject: [PATCH] Add static flag to disable parameter expansion Adds a new `--static` flag to the `new` command. When used, it creates snippets without parameter expansion --- README.md | 20 +++--- cmd/new.go | 16 +++++ cmd/new_test.go | 176 +++++++++++++++++++++++++++++++++++++++++++++ cmd/util.go | 12 +++- cmd/util_test.go | 115 +++++++++++++++++++++++++++++ config/config.go | 1 + snippet/snippet.go | 1 + 7 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 cmd/util_test.go diff --git a/README.md b/README.md index 13bf2b2..04d6c14 100644 --- a/README.md +++ b/README.md @@ -90,11 +90,12 @@ You can use also use variables in snippets, these are called parameters. More in You can also *tag* snippets to search for them faster. More information on that in the tag section. + # Parameters There are `` ways of entering parameters. They can contain default values: Hello `` -defined by the equal sign. +defined by the equal sign. They can even contain `` where the default value would be \spaces & = signs\>. @@ -105,6 +106,9 @@ Hello `` The values in this case would be :Hello \John\_\|\|\_Sam\_\|\|\_Jane Doe = special #chars\_\|\> +If you want to disable parameter expansion (i.e. treat `<...>` as literal text), you can use the `-s` or `--static` flag when creating a new snippet with `pet new`, or you can add the field `Static = true` in the snippet's definition table. + + # Examples Some examples are shown below. @@ -131,7 +135,7 @@ function prev() { ``` ### fish -See below for details. +See below for details. https://github.com/otms61/fish-pet @@ -170,7 +174,7 @@ bindkey '^s' pet-select ``` ### fish -See below for details. +See below for details. https://github.com/otms61/fish-pet @@ -253,7 +257,7 @@ export FZF_CTRL_R_OPTS=" --info=right --color header:italic --header 'alt+s (pet new)' - --preview 'echo {}' --preview-window down:3:hidden:wrap + --preview 'echo {}' --preview-window down:3:hidden:wrap --bind '?:toggle-preview' --bind 'alt-s:execute(pet new --tag {2..})+abort'" ``` @@ -298,7 +302,7 @@ Use "pet [command] --help" for more information about a command. ``` # Snippet -Run `pet edit` +Run `pet edit` You can also register the output of command (but cannot search). ``` @@ -452,7 +456,7 @@ You must obtain access token. Go https://github.com/settings/tokens/new and create access token (only need "gist" scope). Set that to `access_token` in `[Gist]` or use an environment variable with the name `$PET_GITHUB_ACCESS_TOKEN`. -After setting, you can upload snippets to Gist. +After setting, you can upload snippets to Gist. If `gist_id` is not set, new gist will be created. ``` pet sync @@ -541,14 +545,14 @@ Upload success ``` # Installation -You need to install selector command ([fzf](https://github.com/junegunn/fzf) or [peco](https://github.com/peco/peco)). +You need to install selector command ([fzf](https://github.com/junegunn/fzf) or [peco](https://github.com/peco/peco)). `homebrew` install `fzf` automatically. After you install Pet, it's HIGHLY recommended to install the shortcuts mentioned in the section on [ZSH Prev](#zsh-prev-function) ## Binary -Go to [the releases page](https://github.com/knqyf263/pet/releases), find the version you want, and download the zip file. Unpack the zip file, and put the binary to somewhere you want (on UNIX-y systems, /usr/local/bin or the like). Make sure it has execution bits turned on. +Go to [the releases page](https://github.com/knqyf263/pet/releases), find the version you want, and download the zip file. Unpack the zip file, and put the binary to somewhere you want (on UNIX-y systems, /usr/local/bin or the like). Make sure it has execution bits turned on. ## macOS / Homebrew diff --git a/cmd/new.go b/cmd/new.go index f9d0743..6376fc7 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -30,6 +30,14 @@ func CanceledError() error { return errors.New("canceled") } +// setStaticField sets the static field on a snippet if the static flag is enabled +func setStaticField(snippetInfo *snippet.SnippetInfo) { + if config.Flag.Static { + static := true + snippetInfo.Static = &static + } +} + func scan(prompt string, out io.Writer, in io.ReadCloser, allowEmpty bool) (string, error) { f, err := os.CreateTemp("", "pet-") if err != nil { @@ -232,6 +240,9 @@ func _new(in io.ReadCloser, out io.Writer, args []string) (err error) { Tag: tags, } + // Set static field if static flag is used + setStaticField(&newSnippet) + return createAndEditSnippet(newSnippet, snippets, lineCount+3) } else { command, err = scan(color.HiYellowString("Command> "), out, in, false) @@ -274,6 +285,9 @@ func _new(in io.ReadCloser, out io.Writer, args []string) (err error) { Tag: tags, } + // Set static field if static flag is used + setStaticField(&newSnippet) + snippets.Snippets = append(snippets.Snippets, newSnippet) if err = snippets.Save(); err != nil { return err @@ -298,4 +312,6 @@ func init() { `Can enter multiline snippet (Double \n to quit)`) newCmd.Flags().BoolVarP(&config.Flag.UseEditor, "editor", "e", false, `Use editor to create snippet`) + newCmd.Flags().BoolVarP(&config.Flag.Static, "static", "s", false, + `Create snippet without parameter expansion (static = true)`) } diff --git a/cmd/new_test.go b/cmd/new_test.go index 394dadd..3029870 100644 --- a/cmd/new_test.go +++ b/cmd/new_test.go @@ -255,3 +255,179 @@ func compareSnippets(a, b snippet.Snippets) bool { } return true } + +func TestNew_WithStaticFlag(t *testing.T) { + // Setup temporary directory for config + tempDir, _ := os.MkdirTemp("", "testdata") + tempSnippetFile := filepath.Join(tempDir, "snippet.toml") + + // Clean up temp dirs + defer os.RemoveAll(tempDir) + + // Create empty snippet file + _, err := os.Create(tempSnippetFile) + if err != nil { + t.Fatalf("Failed to create temp snippet file: %v", err) + } + + // Mock configuration + config.Conf.General.SnippetFile = tempSnippetFile + config.Conf.General.SnippetDirs = []string{} + + // Set the static flag to true + originalStatic := config.Flag.Static + config.Flag.Static = true + defer func() { config.Flag.Static = originalStatic }() + + // Simulate creating a new snippet with static flag + args := []string{"echo hello "} + + // Create a buffer for output + var outputBuffer bytes.Buffer + // Create a mock ReadCloser for input (description) + inputReader := &MockReadCloser{strings.NewReader("test description\n")} + + err = _new(inputReader, &outputBuffer, args) + if err != nil { + t.Fatalf("Failed to create new snippet: %v", err) + } + + // Load the snippet file and check if static field is set + var snippets snippet.Snippets + loadSnippetsFromFile(t, tempSnippetFile, &snippets) + + if len(snippets.Snippets) != 1 { + t.Fatalf("Expected 1 snippet, got %d", len(snippets.Snippets)) + } + + createdSnippet := snippets.Snippets[0] + if createdSnippet.Command != "echo hello " { + t.Errorf("Expected command to be 'echo hello ', got '%s'", createdSnippet.Command) + } + + if createdSnippet.Description != "test description" { + t.Errorf("Expected description to be 'test description', got '%s'", createdSnippet.Description) + } + + // Check that static field is set to true + if createdSnippet.Static == nil { + t.Error("Expected Static field to be set, got nil") + } else if !*createdSnippet.Static { + t.Error("Expected Static field to be true, got false") + } +} + +func TestNew_WithoutStaticFlag(t *testing.T) { + // Setup temporary directory for config + tempDir, _ := os.MkdirTemp("", "testdata") + tempSnippetFile := filepath.Join(tempDir, "snippet.toml") + + // Clean up temp dirs + defer os.RemoveAll(tempDir) + + // Create empty snippet file + _, err := os.Create(tempSnippetFile) + if err != nil { + t.Fatalf("Failed to create temp snippet file: %v", err) + } + + // Mock configuration + config.Conf.General.SnippetFile = tempSnippetFile + config.Conf.General.SnippetDirs = []string{} + + // Ensure the static flag is false + originalStatic := config.Flag.Static + config.Flag.Static = false + defer func() { config.Flag.Static = originalStatic }() + + // Simulate creating a new snippet without static flag + args := []string{"echo hello "} + + // Create a buffer for output + var outputBuffer bytes.Buffer + // Create a mock ReadCloser for input (description) + inputReader := &MockReadCloser{strings.NewReader("test description\n")} + + err = _new(inputReader, &outputBuffer, args) + if err != nil { + t.Fatalf("Failed to create new snippet: %v", err) + } + + // Load the snippet file and check if static field is not set + var snippets snippet.Snippets + loadSnippetsFromFile(t, tempSnippetFile, &snippets) + + if len(snippets.Snippets) != 1 { + t.Fatalf("Expected 1 snippet, got %d", len(snippets.Snippets)) + } + + createdSnippet := snippets.Snippets[0] + if createdSnippet.Command != "echo hello " { + t.Errorf("Expected command to be 'echo hello ', got '%s'", createdSnippet.Command) + } + + // Check that static field is nil (not set) when flag is false + if createdSnippet.Static != nil { + t.Errorf("Expected Static field to be nil when flag is false, got %v", *createdSnippet.Static) + } +} + +func TestNew_WithStaticFlagAndEditor(t *testing.T) { + // Setup temporary directory for config + tempDir, _ := os.MkdirTemp("", "testdata") + tempSnippetFile := filepath.Join(tempDir, "snippet.toml") + + // Clean up temp dirs + defer os.RemoveAll(tempDir) + + // Create empty snippet file + _, err := os.Create(tempSnippetFile) + if err != nil { + t.Fatalf("Failed to create temp snippet file: %v", err) + } + + // Mock configuration + config.Conf.General.SnippetFile = tempSnippetFile + config.Conf.General.SnippetDirs = []string{} + config.Conf.General.Editor = "echo" // Use echo as a mock editor that won't actually open + + // Set flags + originalStatic := config.Flag.Static + originalUseEditor := config.Flag.UseEditor + config.Flag.Static = true + config.Flag.UseEditor = true + defer func() { + config.Flag.Static = originalStatic + config.Flag.UseEditor = originalUseEditor + }() + + // Simulate creating a new snippet with static flag and editor + args := []string{} // No command args when using editor + + // Create a buffer for output + var outputBuffer bytes.Buffer + // Create a mock ReadCloser for input + inputReader := &MockReadCloser{strings.NewReader("")} + + err = _new(inputReader, &outputBuffer, args) + if err != nil { + t.Fatalf("Failed to create new snippet: %v", err) + } + + // Load the snippet file and check if static field is set + var snippets snippet.Snippets + loadSnippetsFromFile(t, tempSnippetFile, &snippets) + + if len(snippets.Snippets) != 1 { + t.Fatalf("Expected 1 snippet, got %d", len(snippets.Snippets)) + } + + createdSnippet := snippets.Snippets[0] + + // Check that static field is set to true even when using editor + if createdSnippet.Static == nil { + t.Error("Expected Static field to be set, got nil") + } else if !*createdSnippet.Static { + t.Error("Expected Static field to be true, got false") + } +} diff --git a/cmd/util.go b/cmd/util.go index 394fad2..200e9df 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -78,7 +78,17 @@ func filter(options []string, tag string, raw bool) (commands []string, err erro // If only one line is selected, search for params in the command if len(lines) == 1 && !raw { snippetInfo := snippetTexts[lines[0]] - params = dialog.SearchForParams(snippetInfo.Command) + // Check if snippet is static (parameter expansion disabled) + isStatic := false + if snippetInfo.Static != nil { + isStatic = *snippetInfo.Static + } + + if !isStatic { + params = dialog.SearchForParams(snippetInfo.Command) + } else { + params = nil + } } else { params = nil } diff --git a/cmd/util_test.go b/cmd/util_test.go new file mode 100644 index 0000000..35b8139 --- /dev/null +++ b/cmd/util_test.go @@ -0,0 +1,115 @@ +package cmd + +import ( + "os" + "path/filepath" + "testing" + + "github.com/knqyf263/pet/config" + "github.com/knqyf263/pet/snippet" + "github.com/pelletier/go-toml" +) + +func TestFilterStaticSnippetHandling(t *testing.T) { + // Test the static field handling in filter logic + // This is a unit test for the static field behavior in the filter function + + // Create test snippet with static flag + staticFlag := true + testSnippet := snippet.SnippetInfo{ + Description: "static snippet", + Command: "echo hello ", + Tag: []string{"test"}, + Static: &staticFlag, // This snippet has static = true + } + + // Test static field detection + isStatic := false + if testSnippet.Static != nil { + isStatic = *testSnippet.Static + } + + if !isStatic { + t.Error("Expected snippet to be static, but it was not") + } + + // Test regular snippet without static flag + regularSnippet := snippet.SnippetInfo{ + Description: "regular snippet", + Command: "echo hello ", + Tag: []string{"test"}, + Static: nil, // No static field + } + + isStatic = false + if regularSnippet.Static != nil { + isStatic = *regularSnippet.Static + } + + if isStatic { + t.Error("Expected snippet to not be static, but it was") + } +} + +func TestSnippetSaveWithStatic(t *testing.T) { + // Test saving and loading snippets with static field + tempDir, _ := os.MkdirTemp("", "testdata") + tempSnippetFile := filepath.Join(tempDir, "snippet.toml") + defer os.RemoveAll(tempDir) + + // Create test snippets - one static, one regular + staticFlag := true + testSnippets := snippet.Snippets{ + Snippets: []snippet.SnippetInfo{ + { + Description: "regular snippet", + Command: "echo hello ", + Tag: []string{"test"}, + }, + { + Description: "static snippet", + Command: "echo hello ", + Tag: []string{"test"}, + Static: &staticFlag, + }, + }, + } + + // Mock configuration + config.Conf.General.SnippetFile = tempSnippetFile + + // Save snippets + err := testSnippets.Save() + if err != nil { + t.Fatalf("Failed to save snippets: %v", err) + } + + // Load snippets back + var loadedSnippets snippet.Snippets + f, err := os.ReadFile(tempSnippetFile) + if err != nil { + t.Fatalf("Failed to read snippet file: %v", err) + } + + err = toml.Unmarshal(f, &loadedSnippets) + if err != nil { + t.Fatalf("Failed to unmarshal snippets: %v", err) + } + + // Verify we have 2 snippets + if len(loadedSnippets.Snippets) != 2 { + t.Fatalf("Expected 2 snippets, got %d", len(loadedSnippets.Snippets)) + } + + // Check first snippet (regular - no static field) + if loadedSnippets.Snippets[0].Static != nil { + t.Error("Expected first snippet to have nil Static field") + } + + // Check second snippet (static) + if loadedSnippets.Snippets[1].Static == nil { + t.Error("Expected second snippet to have Static field set") + } else if !*loadedSnippets.Snippets[1].Static { + t.Error("Expected second snippet to have Static field set to true") + } +} diff --git a/config/config.go b/config/config.go index e259bc2..51122b1 100644 --- a/config/config.go +++ b/config/config.go @@ -85,6 +85,7 @@ type FlagConfig struct { UseEditor bool Silent bool Raw bool + Static bool } // Load loads a config toml diff --git a/snippet/snippet.go b/snippet/snippet.go index 07b3fc9..548fa46 100644 --- a/snippet/snippet.go +++ b/snippet/snippet.go @@ -22,6 +22,7 @@ type SnippetInfo struct { Command string `toml:"command,multiline"` Tag []string Output string + Static *bool `toml:",omitempty"` } // Loads snippets from the main snippet file and all snippet