From e4eee1b56e6e2c53d4500b22cb35fd4a59385052 Mon Sep 17 00:00:00 2001 From: Greg Curtis Date: Mon, 26 Sep 2022 15:15:52 -0400 Subject: [PATCH] devbox: add AppendScript method to ConfigShellCmds (#149) This method takes a script as a string and appends it to the slice of shell commands in the config. From the docs: AppendScript appends each line of a script to s.Cmds. It also applies the following formatting rules: - Trim leading newlines from the script. - Trim trailing whitespace from the script. - If the first line of the script begins with one or more tabs ('\t'), then unindent each line by that same number of tabs. - Remove trailing whitespace from each line. Note that unindenting only happens when a line starts with at least as many tabs as the first line. If it starts with fewer tabs, then it is not unindented at all. See `config_test.go` for an example. --- config.go | 28 +++++++++++++ config_test.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/config.go b/config.go index 01eac4ccb4f..8f10fa8237d 100644 --- a/config.go +++ b/config.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "strings" + "unicode" "github.com/pkg/errors" "go.jetpack.io/devbox/cuecfg" @@ -93,6 +94,33 @@ type ConfigShellCmds struct { Cmds []string } +// AppendScript appends each line of a script to s.Cmds. It also applies the +// following formatting rules: +// +// - Trim leading newlines from the script. +// - Trim trailing whitespace from the script. +// - If the first line of the script begins with one or more tabs ('\t'), then +// unindent each line by that same number of tabs. +// - Remove trailing whitespace from each line. +// +// Note that unindenting only happens when a line starts with at least as many +// tabs as the first line. If it starts with fewer tabs, then it is not +// unindented at all. +func (s *ConfigShellCmds) AppendScript(script string) { + script = strings.TrimLeft(script, "\r\n ") + script = strings.TrimRightFunc(script, unicode.IsSpace) + if len(script) == 0 { + return + } + prefixLen := strings.IndexFunc(script, func(r rune) bool { return r != '\t' }) + prefix := strings.Repeat("\t", prefixLen) + for _, line := range strings.Split(script, "\n") { + line = strings.TrimRightFunc(line, unicode.IsSpace) + line = strings.TrimPrefix(line, prefix) + s.Cmds = append(s.Cmds, line) + } +} + // MarshalJSON marshals shell commands according to s.MarshalAs. It marshals // commands to a string by joining s.Cmds with newlines. func (s ConfigShellCmds) MarshalJSON() ([]byte, error) { diff --git a/config_test.go b/config_test.go index da89d36d5c4..3c2af888ee8 100644 --- a/config_test.go +++ b/config_test.go @@ -2,6 +2,7 @@ package devbox import ( "encoding/json" + "fmt" "testing" "github.com/google/go-cmp/cmp" @@ -145,3 +146,106 @@ func TestConfigShellCmdsString(t *testing.T) { }) } } + +func ExampleConfigShellCmds_AppendScript() { + shCmds := ConfigShellCmds{} + shCmds.AppendScript(` + # This script will be unindented by the number of leading tabs + # on the first line. + if true; then + echo "this is always printed" + fi`, + ) + b, _ := json.MarshalIndent(&shCmds, "", " ") + fmt.Println(string(b)) + + // Output: + // [ + // "# This script will be unindented by the number of leading tabs", + // "# on the first line.", + // "if true; then", + // "\techo \"this is always printed\"", + // "fi" + // ] +} + +func TestAppendScript(t *testing.T) { + tests := []struct { + name string + script string + wantCmds []string + }{ + { + name: "Empty", + script: "", + wantCmds: nil, + }, + { + name: "OnlySpaces", + script: " ", + wantCmds: nil, + }, + { + name: "Only newlines", + script: "\r\n", + wantCmds: nil, + }, + { + name: "Simple", + script: "echo test", + wantCmds: []string{"echo test"}, + }, + { + name: "LeadingNewline", + script: "\necho test", + wantCmds: []string{"echo test"}, + }, + { + name: "LeadingNewlineAndSpace", + script: "\n echo test", + wantCmds: []string{"echo test"}, + }, + { + name: "TrailingWhitespace", + script: "echo test \n", + wantCmds: []string{"echo test"}, + }, + { + name: "SecondLineIndent", + script: "if true; then\n\techo test\nfi", + wantCmds: []string{ + "if true; then", + "\techo test", + "fi", + }, + }, + { + name: "Unindent", + script: "\n\tif true; then\n\t\techo test\n\tfi", + wantCmds: []string{ + "if true; then", + "\techo test", + "fi", + }, + }, + { + name: "UnindentTooFewTabs", + script: "\t\tif true; then\n\techo test\n\t\tfi", + wantCmds: []string{ + "if true; then", + "\techo test", + "fi", + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + shCmds := ConfigShellCmds{} + shCmds.AppendScript(test.script) + gotCmds := shCmds.Cmds + if diff := cmp.Diff(test.wantCmds, gotCmds); diff != "" { + t.Errorf("Got incorrect commands slice (-want +got):\n%s", diff) + } + }) + } +}