Skip to content

Commit

Permalink
fix: parseEnvs for expanding env and shell subst (#99)
Browse files Browse the repository at this point in the history
- do parseEnvs with shell echo to handle both /Users/sunggun and shell subst in consist way
- use os.ExpandEnv to handle /Users/sunggun,  and subst env vars of current profile
  • Loading branch information
sunggun-yu authored Nov 21, 2023
1 parent 3017d9e commit 862ce36
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 38 deletions.
56 changes: 19 additions & 37 deletions internal/shell/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
"io"
"os"
"os/exec"
"regexp"
"strings"

"github.com/fatih/color"
"github.com/sunggun-yu/envp/internal/config"
"github.com/sunggun-yu/envp/internal/util"
)

// TODO: refactoring, cleanup
Expand Down Expand Up @@ -137,18 +135,29 @@ func (s *ShellCommand) createCommand(envs *config.Envs, cmd string, arg ...strin
return c
}

// parseEnvs parse config.Envs to "VAR=VAL" format string slice
// parseEnvs parse Env values with shell echo
func parseEnvs(envs config.Envs) (errs error) {
for _, e := range envs {
// it's ok to ignore error. it returns original value if it doesn't contain the home path
e.Value, _ = util.ExpandHomeDir(e.Value)
// parse command substitution value like $(some-command). treat error to let user to know there is error with it
v, err := processCommandSubstitutionValue(e.Value, envs)
if err != nil {
// parse env value with shell echo
cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("echo %s", e.Value))
// append envs to cmd that runs command substitution as well to support the case that reuse env var as ref with substitution
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, envs.Strings()...)

// it never occurs error since it is processed with shell echo.
// so that, it will not exit 1 even command substitution has error. and just print out empty line when it errors
output, _ := cmd.Output()
// trim new lines from result
result := strings.TrimRight(string(output), "\r\n")
// use os.ExpandEnv to replace all the ${var} or $var in the string according to the values of the current environment variables.
// so that $HOME will be replaced to current user's abs home dir
result = os.ExpandEnv(result)

if len(e.Value) > 0 && len(result) == 0 {
// join errors
errs = errors.Join(errs, fmt.Errorf("[envp] error processing value of %s: %s", e.Name, err))
errs = errors.Join(errs, fmt.Errorf("[envp] error processing value of %s: %s", e.Name, e.Value))
} else {
e.Value = v
e.Value = result
}
}
return errs
Expand All @@ -159,30 +168,3 @@ func appendEnvpProfile(envs []string, profile string) []string {
envs = append(envs, fmt.Sprintf("%s=%s", envpEnvVarKey, profile))
return envs
}

// processCommandSubstitutionValue checks whether the env value is in the format of shell substitution $() and runs the shell to replace the env value with the result of its execution.
func processCommandSubstitutionValue(val string, envs config.Envs) (string, error) {
// check if val is pattern of command substitution using regex
// support only $() substitution. not support `` substitution
re := regexp.MustCompile(`^\$\((.*?)\)`) // use MustCompile. no expect it's failing

matches := re.FindStringSubmatch(val)
if len(matches) < 2 {
// no valid script found. just return original value
return val, nil
}

script := strings.TrimSpace(matches[1])
cmd := exec.Command("sh", "-c", script)
// append envs to cmd that runs command substitution as well to support the case that reuse env var as ref with substitution
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, envs.Strings()...)

// output, err := cmd.CombinedOutput()
output, err := cmd.Output()
if err != nil {
return val, fmt.Errorf("error executing script: %v", err)
}

return strings.TrimRight(string(output), "\r\n"), nil
}
22 changes: 21 additions & 1 deletion internal/shell/shell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ var _ = Describe("Shell", func() {
})
})

var _ = Describe("env functions", func() {
var _ = Describe("parseEnvs function", func() {

envs := config.Envs{}
envs.AddEnv("PATH", "~/.config")
Expand Down Expand Up @@ -142,6 +142,26 @@ var _ = Describe("env functions", func() {
})
})

var _ = Describe("parseEnvs with os.ExpandEnv", func() {
When("var is referencing other vars", func() {
envs := config.Envs{}
envs.AddEnv("VAR1", "VAL_1")
envs.AddEnv("VAR2", "$VAR1")
envs.AddEnv("VAR3", "my-$VAR2")
err := parseEnvs(envs)
pe := envs.Strings()

It("should not occur error", func() {
Expect(err).ToNot(HaveOccurred())
})

It("expanding value of other vars", func() {
Expect(pe).To(ContainElement("VAR2=VAL_1"))
Expect(pe).To(ContainElement("VAR3=my-VAL_1"))
})
})
})

var _ = Describe("env shell command substitution", func() {

envs := config.Envs{}
Expand Down

0 comments on commit 862ce36

Please sign in to comment.