diff --git a/docs/configuration/overview.md b/docs/configuration/overview.md index cb468d17..2007c355 100644 --- a/docs/configuration/overview.md +++ b/docs/configuration/overview.md @@ -28,8 +28,8 @@ snipkit config init This command creates a config file in the SnipKit home directory. The initial config file looks similar to this: -```yaml -version: 1.0.0 +```yaml title="config.yaml" +version: 1.1.0 config: style: # The theme defines the terminal colors used by Snipkit. @@ -58,34 +58,20 @@ When typing `snipkit config edit` the configuration file will be opened in an ed The default editor is defined by the `$VISUAL` or `$EDITOR` environment variables. This behavior can be overwritten by setting the `editor` field in the configuration file to a non-empty string, e.g.: -```yaml -version: 1.0.0 +```yaml title="config.yaml" +version: 1.1.0 config: editor: "code" ``` If no value is provided at all, SnipKit will try to use `vim`. -### Shell - -The shell for script executions is defined by the `$SHELL` environment variable. This behavior can be overwritten by -setting the `shell` option to a non-empty string, e.g.: - -```yaml -version: 1.0.0 -config: - shell: "/bin/zsh" -``` - -If neither `$SHELL` nor the config option `shell` is defined, SnipKit will try to use `/bin/bash` as a fallback value. - - ### Theme SnipKit supports multiple themes out of the box and also allows you to define your own themes: -```yaml -version: 1.0.0 +```yaml title="config.yaml" +version: 1.1.0 config: theme: "default" ``` @@ -100,8 +86,8 @@ For a list of supported default themes, have a look at the [Themes][themes] page Most of the time, you want to call the same subcommand, e.g. `print` or `exec`. You can configure `snipkit` so that this command gets executed by default: -```yaml -version: 1.0.0 +```yaml title="config.yaml" +version: 1.1.0 config: defaultRootCommand: "exec" ``` @@ -109,6 +95,81 @@ config: This way, calling `snipkit` will yield the same result as `snipkit exec`. If you want to call the `print` command instead, you can still call `snipkit print`. +### Script Options + +#### Shell + +The shell for script executions is defined by the `$SHELL` environment variable. This behavior can be overwritten by setting +the `shell` option to a non-empty string, e.g.: + +```yaml title="config.yaml" +version: 1.1.0 +config: + script: + shell: "/bin/zsh" +``` + +If neither `$SHELL` nor the config option `shell` is defined, SnipKit will try to use `/bin/bash` as a fallback value. + +#### Parameter mode + +How values are injected into your snippet for the defined parameters is defined by the `parameterMode` option: + +```yaml title="config.yaml" +version: 1.1.0 +config: + script: + parameterMode: SET +``` + +The default value is `SET`, defining that values should be set as variables. This means that the following script + +```sh title="Raw snippet before execution" +# ${VAR} Description: What to print +echo ${VAR} +``` + +will be updated in the following way, e.g. for `VAR = "Hello word"`: + +```sh title="Example for parameterMode SET" +# ${VAR} Description: What to print +VAR="Hello world" +echo ${VAR} +``` + +Alternatively, all occurrences of a parameter can be replaced with the actual value when +specifying `REPLACE` for `parameterMode`: + +```sh title="Example for parameterMode = REPLACE" +echo "Hello world" +``` + +#### Remove Comments + +SnipKit will remove all parameter comments from a snippet when specifying `removeComments`: + +```yaml title="config.yaml" +version: 1.1.0 +config: + script: + removeComments: true +``` + +This means that the following script + +```sh title="Raw snippet before execution" +# ${VAR} Description: What to print +echo ${VAR} +``` + +will be formatted in the following way: + +```sh title="Example for removeComments = true" +echo ${VAR} +``` + +!!! info + Comments will always be removed if `parameterMode` is set to `REPLACE`. ## Clean up @@ -122,4 +183,5 @@ The cleanup method is a way to remove all SnipKit artifacts from your hard drive home directory. If this directory is empty at the end of the cleanup process, it will be deleted as well. [managers]: ../managers/overview.md + [themes]: themes.md diff --git a/go.mod b/go.mod index c6caa973..4ef8bcb0 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kr/pty v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-isatty v0.0.14 // indirect diff --git a/go.sum b/go.sum index 2d848755..761cab90 100644 --- a/go.sum +++ b/go.sum @@ -120,6 +120,7 @@ github.com/corbym/gocrest v1.0.5/go.mod h1:lF3xBPnOU5DYDpa/vUq63SMxUhd5UAwGgmA8Z github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -295,8 +296,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lemoony/tview v0.0.0-20220108075851-caa9ee4cec2d h1:0yBL9dq9G56KsVHb+KYqesB0tlTgDR8GZP2VlyWb8ZY= diff --git a/internal/app/app_exec.go b/internal/app/app_exec.go index 5bbc9588..2f2e3ab5 100644 --- a/internal/app/app_exec.go +++ b/internal/app/app_exec.go @@ -7,6 +7,8 @@ import ( "emperror.dev/errors" "github.com/phuslu/log" + "github.com/lemoony/snipkit/internal/config" + "github.com/lemoony/snipkit/internal/model" "github.com/lemoony/snipkit/internal/ui" "github.com/lemoony/snipkit/internal/utils/stringutil" ) @@ -21,7 +23,7 @@ func (a *appImpl) LookupAndExecuteSnippet() { parameters := snippet.GetParameters() if parameterValues, ok := a.tui.ShowParameterForm(parameters, ui.OkButtonExecute); ok { - executeScript(snippet.Format(parameterValues), a.config.Shell) + executeScript(snippet.Format(parameterValues, formatOptions(a.config.Script)), a.config.Script.Shell) } } @@ -43,3 +45,16 @@ func executeScript(script, configuredShell string) { log.Info().Err(err) } } + +func formatOptions(cfg config.ScriptConfig) model.SnippetFormatOptions { + var paramMode model.SnippetParamMode + if cfg.ParameterMode == config.ParameterModeReplace { + paramMode = model.SnippetParamModeReplace + } else { + paramMode = model.SnippetParamModeSet + } + return model.SnippetFormatOptions{ + RemoveComments: cfg.RemoveComments, + ParamMode: paramMode, + } +} diff --git a/internal/app/app_exec_test.go b/internal/app/app_exec_test.go index 22fa8a9c..0ed3634d 100644 --- a/internal/app/app_exec_test.go +++ b/internal/app/app_exec_test.go @@ -1,10 +1,13 @@ package app import ( + "fmt" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/lemoony/snipkit/internal/config" "github.com/lemoony/snipkit/internal/config/configtest" "github.com/lemoony/snipkit/internal/model" "github.com/lemoony/snipkit/internal/utils/testutil" @@ -43,3 +46,25 @@ echo "${VAR1}"` app.LookupAndExecuteSnippet() } + +func Test_formatOptions(t *testing.T) { + tests := []struct { + config config.ScriptConfig + expected model.SnippetFormatOptions + }{ + { + config: config.ScriptConfig{RemoveComments: true, ParameterMode: config.ParameterModeSet}, + expected: model.SnippetFormatOptions{RemoveComments: true, ParamMode: model.SnippetParamModeSet}, + }, + { + config: config.ScriptConfig{RemoveComments: false, ParameterMode: config.ParameterModeReplace}, + expected: model.SnippetFormatOptions{RemoveComments: false, ParamMode: model.SnippetParamModeReplace}, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { + assert.Equal(t, tt.expected, formatOptions(tt.config)) + }) + } +} diff --git a/internal/app/app_print.go b/internal/app/app_print.go index 7f51b2d7..5123362d 100644 --- a/internal/app/app_print.go +++ b/internal/app/app_print.go @@ -12,7 +12,7 @@ func (a *appImpl) LookupAndCreatePrintableSnippet() (string, bool) { parameters := snippet.GetParameters() if parameterValues, ok := a.tui.ShowParameterForm(parameters, ui.OkButtonPrint); ok { - return snippet.Format(parameterValues), true + return snippet.Format(parameterValues, formatOptions(a.config.Script)), true } return "", false diff --git a/internal/config/config.go b/internal/config/config.go index 207c8b64..081ea609 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,13 @@ import ( "github.com/lemoony/snipkit/internal/ui" ) +type ParameterMode string + +const ( + ParameterModeSet = "SET" + ParameterModeReplace = "Replace" +) + type VersionWrapper struct { Version string `yaml:"version" mapstructure:"version"` Config Config `yaml:"config" mapstructure:"config"` @@ -13,7 +20,13 @@ type VersionWrapper struct { type Config struct { Style ui.Config `yaml:"style" mapstructure:"style"` Editor string `yaml:"editor" mapstructure:"editor" head_comment:"Your preferred editor to open the config file when typing 'snipkit config edit'." line_comment:"Defaults to a reasonable value for your operation system when empty."` - Shell string `yaml:"shell" mapstructure:"shell" head_comment:"The path to the shell to execute scripts with. If not set or empty, $SHELL will be used instead. Fallback is '/bin/bash'."` DefaultRootCommand string `yaml:"defaultRootCommand" mapstructure:"defaultRootCommand" head_comment:"The command which should run if you don't provide any subcommand." line_comment:"If not set, the help text will be shown."` + Script ScriptConfig `yaml:"scripts" mapstructure:"Options regarding script handling"` Manager managers.Config `yaml:"manager" mapstructure:"manager"` } + +type ScriptConfig struct { + Shell string `yaml:"shell" mapstructure:"shell" head_comment:"The path to the shell to execute scripts with. If not set or empty, $SHELL will be used instead. Fallback is '/bin/bash'."` + ParameterMode ParameterMode `yaml:"parameterMode" mapstructure:"parameterMode" head_comment:"Defines how parameters are handled. Allowed values: SET (sets the parameter value as shell variable) and REPLACE (replaces all occurrences of the variable with the actual value)"` + RemoveComments bool `yaml:"removeComments" mapstructure:"removeComments" head_comment:"If set to true, any comments in your scripts will be removed upon executing or printing."` +} diff --git a/internal/config/create.go b/internal/config/create.go index 3f0de455..2de5f41e 100644 --- a/internal/config/create.go +++ b/internal/config/create.go @@ -28,30 +28,39 @@ const ( yamlCommentHead = yamlCommentKind(2) yamlDefaultIndent = 2 + + version = "1.1.0" ) var sliceIndexRegex = regexp.MustCompile(`\[\d]`) func wrap(config Config) VersionWrapper { return VersionWrapper{ - Version: "1.0.0", + Version: version, Config: config, } } func createConfigFile(system *system.System, viper *viper.Viper) { - config := wrap(Config{}) - - config.Config.Style = ui.DefaultConfig() + config := wrap(defaultConfig()) data := SerializeToYamlWithComment(config) configPath := viper.ConfigFileUsed() - log.Debug().Msgf("Going to use config path %s", configPath) system.CreatePath(configPath) system.WriteFile(configPath, data) } +func defaultConfig() Config { + return Config{ + Style: ui.DefaultConfig(), + Script: ScriptConfig{ + ParameterMode: ParameterModeSet, + RemoveComments: false, + }, + } +} + func SerializeToYamlWithComment(value interface{}) []byte { // get all tag comments commentMap := map[string][]yamlComment{} diff --git a/internal/config/create_test.go b/internal/config/create_test.go index 9c6dd8c7..8299aa64 100644 --- a/internal/config/create_test.go +++ b/internal/config/create_test.go @@ -15,9 +15,11 @@ import ( func Test_serializeToYamlWithComment(t *testing.T) { var testConfig VersionWrapper - testConfig.Version = "1.0.0" + testConfig.Version = version testConfig.Config.Editor = "foo-editor" - testConfig.Config.Shell = "/bin/zsh" + testConfig.Config.Script.Shell = "/bin/zsh" + testConfig.Config.Script.RemoveComments = true + testConfig.Config.Script.ParameterMode = ParameterModeSet testConfig.Config.Style.Theme = "simple" testConfig.Config.Manager.SnippetsLab = &snippetslab.Config{ diff --git a/internal/config/service.go b/internal/config/service.go index abd911fa..3aa5d2af 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -87,6 +87,10 @@ func (s *serviceImpl) LoadConfig() (Config, error) { return invalidConfig, err } + if wrapper.Version != version { + log.Warn().Msgf("Config version is not up to date - expected %s, actual %s", version, wrapper.Version) + } + s.config = &wrapper.Config return *s.config, nil diff --git a/internal/config/testdata/example-config.yaml b/internal/config/testdata/example-config.yaml index af4d8209..74ae2ba5 100644 --- a/internal/config/testdata/example-config.yaml +++ b/internal/config/testdata/example-config.yaml @@ -1,4 +1,4 @@ -version: 1.0.0 +version: 1.1.0 config: style: # The theme defines the terminal colors used by Snipkit. @@ -6,10 +6,15 @@ config: theme: simple # Your preferred editor to open the config file when typing 'snipkit config edit'. editor: foo-editor # Defaults to a reasonable value for your operation system when empty. - # The path to the shell to execute scripts with. If not set or empty, $SHELL will be used instead. Fallback is '/bin/bash'. - shell: /bin/zsh # The command which should run if you don't provide any subcommand. defaultRootCommand: "" # If not set, the help text will be shown. + scripts: + # The path to the shell to execute scripts with. If not set or empty, $SHELL will be used instead. Fallback is '/bin/bash'. + shell: /bin/zsh + # Defines how parameters are handled. Allowed values: SET (sets the parameter value as shell variable) and REPLACE (replaces all occurrences of the variable with the actual value) + parameterMode: SET + # If set to true, any comments in your scripts will be removed upon executing or printing. + removeComments: true manager: snippetsLab: # Set to true if you want to use SnippetsLab. diff --git a/internal/managers/fslibrary/manager_test.go b/internal/managers/fslibrary/manager_test.go index 9c396bc1..ef222317 100644 --- a/internal/managers/fslibrary/manager_test.go +++ b/internal/managers/fslibrary/manager_test.go @@ -129,7 +129,7 @@ func Test_GetSnippets_LazyOpen_HideTitleHeader(t *testing.T) { assert.Empty(t, snippets[0].GetTags()) assert.Equal(t, model.LanguageBash, snippets[0].GetLanguage()) assert.Empty(t, snippets[0].GetParameters()) - assert.Equal(t, "content", snippets[0].Format([]string{})) + assert.Equal(t, "content", snippets[0].Format([]string{}, model.SnippetFormatOptions{})) } func Test_checkSuffix(t *testing.T) { diff --git a/internal/managers/fslibrary/model.go b/internal/managers/fslibrary/model.go index d205d876..3f24a6a9 100644 --- a/internal/managers/fslibrary/model.go +++ b/internal/managers/fslibrary/model.go @@ -39,6 +39,6 @@ func (s snippetImpl) GetParameters() []model.Parameter { return parser.ParseParameters(s.GetContent()) } -func (s snippetImpl) Format(values []string) string { - return parser.CreateSnippet(s.GetContent(), s.GetParameters(), values) +func (s snippetImpl) Format(values []string, options model.SnippetFormatOptions) string { + return parser.CreateSnippet(s.GetContent(), s.GetParameters(), values, options) } diff --git a/internal/managers/githubgist/model.go b/internal/managers/githubgist/model.go index d480c0ec..45c2515b 100644 --- a/internal/managers/githubgist/model.go +++ b/internal/managers/githubgist/model.go @@ -37,6 +37,6 @@ func (s snippetImpl) GetParameters() []model.Parameter { return parser.ParseParameters(s.content) } -func (s snippetImpl) Format(values []string) string { - return parser.CreateSnippet(s.content, s.GetParameters(), values) +func (s snippetImpl) Format(values []string, options model.SnippetFormatOptions) string { + return parser.CreateSnippet(s.GetContent(), s.GetParameters(), values, options) } diff --git a/internal/managers/githubgist/parser_test.go b/internal/managers/githubgist/parser_test.go index 9718d9f9..d7e15148 100644 --- a/internal/managers/githubgist/parser_test.go +++ b/internal/managers/githubgist/parser_test.go @@ -38,7 +38,7 @@ echo "Hello World"`), assert.Equal(t, model.LanguageBash, snippet.GetLanguage()) assert.Equal(t, []string{"test"}, snippet.GetTags()) assert.Empty(t, snippet.GetParameters()) - assert.Equal(t, snippet.GetContent(), snippet.Format([]string{})) + assert.Equal(t, snippet.GetContent(), snippet.Format([]string{}, model.SnippetFormatOptions{})) } func Test_parseTitle(t *testing.T) { diff --git a/internal/managers/pet/model.go b/internal/managers/pet/model.go index fd22715c..f1fe6ef2 100644 --- a/internal/managers/pet/model.go +++ b/internal/managers/pet/model.go @@ -1,6 +1,8 @@ package pet -import "github.com/lemoony/snipkit/internal/model" +import ( + "github.com/lemoony/snipkit/internal/model" +) type snippetImpl struct { id string @@ -34,6 +36,6 @@ func (s snippetImpl) GetParameters() []model.Parameter { return parseParameters(s.content) } -func (s snippetImpl) Format(values []string) string { +func (s snippetImpl) Format(values []string, _ model.SnippetFormatOptions) string { return formatContent(s.content, values) } diff --git a/internal/managers/pet/parser_test.go b/internal/managers/pet/parser_test.go index febe328b..67a115df 100644 --- a/internal/managers/pet/parser_test.go +++ b/internal/managers/pet/parser_test.go @@ -57,14 +57,14 @@ func Test_parseSnippetsFromTOML(t *testing.T) { assert.Equal(t, model.LanguageBash, snippets[0].GetLanguage()) assert.Len(t, snippets[0].GetParameters(), 3) assert.Len(t, snippets[0].GetTags(), 0) - assert.Equal(t, snippets[0].Format([]string{"one", "two", "three"}), "echo one && two three") + assert.Equal(t, snippets[0].Format([]string{"one", "two", "three"}, model.SnippetFormatOptions{}), "echo one && two three") assert.Equal(t, "Watches Kubernetes pods with refresh", snippets[1].GetTitle()) assert.Equal(t, "watch -n 5 'kubectl get pods | grep '", snippets[1].GetContent()) assert.Equal(t, model.LanguageBash, snippets[1].GetLanguage()) assert.Len(t, snippets[1].GetParameters(), 1) assert.Len(t, snippets[1].GetTags(), 2) - assert.Equal(t, snippets[1].Format([]string{"foo"}), "watch -n 5 'kubectl get pods | grep foo'") + assert.Equal(t, snippets[1].Format([]string{"foo"}, model.SnippetFormatOptions{}), "watch -n 5 'kubectl get pods | grep foo'") } func Test_parseParameters(t *testing.T) { diff --git a/internal/managers/pictarinesnip/model.go b/internal/managers/pictarinesnip/model.go index ec8972f7..74fa26f6 100644 --- a/internal/managers/pictarinesnip/model.go +++ b/internal/managers/pictarinesnip/model.go @@ -37,6 +37,6 @@ func (s snippetImpl) GetParameters() []model.Parameter { return parser.ParseParameters(s.content) } -func (s snippetImpl) Format(values []string) string { - return parser.CreateSnippet(s.content, s.GetParameters(), values) +func (s snippetImpl) Format(values []string, options model.SnippetFormatOptions) string { + return parser.CreateSnippet(s.GetContent(), s.GetParameters(), values, options) } diff --git a/internal/managers/pictarinesnip/parser_test.go b/internal/managers/pictarinesnip/parser_test.go index 98c2a591..59b4fdba 100644 --- a/internal/managers/pictarinesnip/parser_test.go +++ b/internal/managers/pictarinesnip/parser_test.go @@ -23,7 +23,7 @@ func Test_parseLibrary(t *testing.T) { assert.Equal(t, model.LanguageBash, snippet1.GetLanguage()) assert.Equal(t, []string{"snipkit"}, snippet1.GetTags()) assert.Len(t, snippet1.GetParameters(), 3) - assert.NotEqual(t, snippet1.GetContent(), snippet1.Format([]string{"one", "two", "three"})) + assert.NotEqual(t, snippet1.GetContent(), snippet1.Format([]string{"one", "two", "three"}, model.SnippetFormatOptions{})) snippet2 := snippets[1] assert.Equal(t, "B3473DF8-6ED6-4589-BFFC-C75F73B1B522", snippet2.GetID()) @@ -32,5 +32,5 @@ func Test_parseLibrary(t *testing.T) { assert.Equal(t, model.LanguageUnknown, snippet2.GetLanguage()) assert.Equal(t, []string{}, snippet2.GetTags()) assert.Empty(t, snippet2.GetParameters()) - assert.Equal(t, snippet2.GetContent(), snippet2.Format([]string{})) + assert.Equal(t, snippet2.GetContent(), snippet2.Format([]string{}, model.SnippetFormatOptions{})) } diff --git a/internal/managers/snippetslab/model.go b/internal/managers/snippetslab/model.go index ead9e0dd..7f56902d 100644 --- a/internal/managers/snippetslab/model.go +++ b/internal/managers/snippetslab/model.go @@ -37,6 +37,6 @@ func (s snippetImpl) GetParameters() []model.Parameter { return parser.ParseParameters(s.content) } -func (s snippetImpl) Format(values []string) string { - return parser.CreateSnippet(s.content, s.GetParameters(), values) +func (s snippetImpl) Format(values []string, options model.SnippetFormatOptions) string { + return parser.CreateSnippet(s.GetContent(), s.GetParameters(), values, options) } diff --git a/internal/managers/snippetslab/parser_test.go b/internal/managers/snippetslab/parser_test.go index dc310d0d..bbaec2ec 100644 --- a/internal/managers/snippetslab/parser_test.go +++ b/internal/managers/snippetslab/parser_test.go @@ -34,7 +34,7 @@ func Test_parseSnippets(t *testing.T) { assert.Empty(t, snippet1.GetTags()) assert.Equal(t, model.LanguageBash, snippet1.GetLanguage()) assert.Len(t, snippet1.GetParameters(), 2) - assert.NotEqual(t, snippet1.Format([]string{"one", "two"}), snippet1.GetContent()) + assert.NotEqual(t, snippet1.Format([]string{"one", "two"}, model.SnippetFormatOptions{}), snippet1.GetContent()) then.AssertThat(t, snippet1.GetContent(), is.MatchForPattern("^# some comment.*")) then.AssertThat(t, snippet1.GetTitle(), is.AnyOf(is.EqualTo("Simple echo"))) @@ -43,7 +43,7 @@ func Test_parseSnippets(t *testing.T) { assert.Equal(t, []string{"2DA8009E-7BE7-420D-AD57-E7F9BB3ADCBE"}, snippet2.GetTags()) assert.Equal(t, model.LanguageBash, snippet2.GetLanguage()) assert.Empty(t, snippet2.GetParameters()) - assert.NotEqual(t, snippet2.Format([]string{}), snippet1.GetContent()) + assert.NotEqual(t, snippet2.Format([]string{}, model.SnippetFormatOptions{}), snippet1.GetContent()) then.AssertThat(t, snippet2.GetContent(), is.MatchForPattern("echo \"Foo!\"")) then.AssertThat(t, snippet2.GetTitle(), is.AnyOf(is.EqualTo("Foos script"))) } diff --git a/internal/model/snippet.go b/internal/model/snippet.go index b8fd630a..f5f77450 100644 --- a/internal/model/snippet.go +++ b/internal/model/snippet.go @@ -1,5 +1,17 @@ package model +type SnippetParamMode int + +const ( + SnippetParamModeSet = 0 + SnippetParamModeReplace = 1 +) + +type SnippetFormatOptions struct { + RemoveComments bool + ParamMode SnippetParamMode +} + type Snippet interface { GetID() string GetTitle() string @@ -7,5 +19,5 @@ type Snippet interface { GetTags() []string GetLanguage() Language GetParameters() []Parameter - Format([]string) string + Format([]string, SnippetFormatOptions) string } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 92500f20..1394b163 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -47,7 +47,7 @@ func ParseParameters(snippet string) []model.Parameter { return hintsToParameters(hints) } -func CreateSnippet(snippet string, parameters []model.Parameter, values []string) string { +func CreateSnippet(snippet string, parameters []model.Parameter, values []string, options model.SnippetFormatOptions) string { if len(values) != len(parameters) { log.Warn().Msgf( "Number of parameters (%d) and number of supplied values (%d) does not match", @@ -57,6 +57,20 @@ func CreateSnippet(snippet string, parameters []model.Parameter, values []string return snippet } + var result string + if options.ParamMode == model.SnippetParamModeSet { + result = setParameters(snippet, parameters, values) + if options.RemoveComments { + result = pruneComments(result) + } + } else { + result = replaceParameters(snippet, parameters, values) + } + + return result +} + +func setParameters(snippet string, parameters []model.Parameter, values []string) string { hints := parseHints(snippet) start := 0 @@ -82,6 +96,14 @@ func CreateSnippet(snippet string, parameters []model.Parameter, values []string return result } +func replaceParameters(snippet string, parameters []model.Parameter, values []string) string { + result := pruneComments(snippet) + for i, parameter := range parameters { + result = strings.ReplaceAll(result, fmt.Sprintf("${%s}", parameter.Key), values[i]) + } + return result +} + func hintsToParameters(hints []hint) []model.Parameter { var result []model.Parameter @@ -211,3 +233,19 @@ func toRegexNamedGroup(val string) (regexNamedGroup, bool) { func (h *hint) isValid() bool { return h.variable != "" && h.typeDescriptor != hintTypeInvalid && h.value != "" } + +func pruneComments(script string) string { + scanner := bufio.NewScanner(strings.NewReader(script)) + result := "" + for scanner.Scan() { + line := scanner.Text() + if hintRegex.MatchString(line) { + continue + } + if result != "" { + result += "\n" + } + result += line + } + return result +} diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 8ba0fb87..524b8f4f 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -70,9 +70,16 @@ func Test_parseParameters(t *testing.T) { func Test_createSnippet(t *testing.T) { parameters := ParseParameters(testSnippet1) - printable := CreateSnippet(testSnippet1, parameters, []string{"FOO-1", "FOO-2"}) - assert.Equal(t, ` + tests := []struct { + name string + options model.SnippetFormatOptions + expected string + }{ + { + name: "default", + options: model.SnippetFormatOptions{}, + expected: ` # some comment # ${VAR1} Name: First Output # ${VAR1} Description: What to print on the terminal first @@ -84,5 +91,54 @@ echo "1 -> ${VAR1}" # ${VAR2} Default: Hey there! VAR2="FOO-2" echo "2 -> ${VAR2}" -`, printable) +`, + }, + { + name: "set & remove comments", + options: model.SnippetFormatOptions{ParamMode: model.SnippetParamModeSet, RemoveComments: true}, + expected: `# some comment +VAR1="FOO-1" +echo "1 -> ${VAR1}" + +VAR2="FOO-2" +echo "2 -> ${VAR2}"`, + }, + { + name: "replace", + options: model.SnippetFormatOptions{ParamMode: model.SnippetParamModeReplace}, + expected: `# some comment +echo "1 -> FOO-1" + +echo "2 -> FOO-2"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + printable := CreateSnippet(testSnippet1, parameters, []string{"FOO-1", "FOO-2"}, tt.options) + assert.Equal(t, tt.expected, printable) + }) + } +} + +func Test_createSnippet_invalidArguments(t *testing.T) { + assert.Equal(t, testSnippet1, CreateSnippet(testSnippet1, ParseParameters(testSnippet1), []string{}, model.SnippetFormatOptions{})) +} + +func Test_pruneComment(t *testing.T) { + tests := []struct { + name string + script string + expected string + }{ + {name: "no comment", script: "echo hello!", expected: "echo hello!"}, + {name: "comment but no hint", script: "#comment\necho hello!", expected: "#comment\necho hello!"}, + {name: "hint comment", script: "# ${VAR1} Description: Foo\necho hello!", expected: "echo hello!"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, pruneComments(tt.script)) + }) + } } diff --git a/internal/ui/picker/picker_test.go b/internal/ui/picker/picker_test.go index 9d1b65f2..0dca19c6 100644 --- a/internal/ui/picker/picker_test.go +++ b/internal/ui/picker/picker_test.go @@ -30,8 +30,6 @@ func Test_ShowPicker(t *testing.T) { } func Test_ShowPicker_Cancel(t *testing.T) { - t.Parallel() - tests := []struct { name string key termtest.Key @@ -43,7 +41,6 @@ func Test_ShowPicker_Cancel(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - t.Parallel() termtest.RunTerminalTest(t, func(c *termtest.Console) { c.ExpectString("Which snippet manager should be added to your configuration") c.SendKey(tt.key) diff --git a/internal/utils/testutil/model.go b/internal/utils/testutil/model.go index 7a30ed11..18939246 100644 --- a/internal/utils/testutil/model.go +++ b/internal/utils/testutil/model.go @@ -38,8 +38,8 @@ func (t TestSnippet) GetParameters() []model.Parameter { return parser.ParseParameters(t.Content) } -func (t TestSnippet) Format(values []string) string { - return parser.CreateSnippet(t.Content, t.GetParameters(), values) +func (t TestSnippet) Format(values []string, options model.SnippetFormatOptions) string { + return parser.CreateSnippet(t.Content, t.GetParameters(), values, options) } func (t TestSnippet) String() string {