-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
context.go
159 lines (139 loc) · 4.11 KB
/
context.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package carapace
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/carapace-sh/carapace/internal/env"
"github.com/carapace-sh/carapace/internal/shell/zsh"
"github.com/carapace-sh/carapace/pkg/execlog"
"github.com/carapace-sh/carapace/pkg/util"
"github.com/carapace-sh/carapace/third_party/github.com/drone/envsubst"
"github.com/spf13/cobra"
)
// Context provides information during completion.
type Context struct {
// Value contains the value currently being completed (or part of it during an ActionMultiParts).
Value string
// Args contains the positional arguments of current (sub)command (exclusive the one currently being completed).
Args []string
// Parts contains the splitted Value during an ActionMultiParts (exclusive the part currently being completed).
Parts []string
// Env contains environment variables for current context.
Env []string
// Dir contains the working directory for current context.
Dir string
mockedReplies map[string]string
cmd *cobra.Command // needed for ActionCobra
}
// NewContext creates a new context for given arguments.
func NewContext(args ...string) Context {
if len(args) == 0 {
args = append(args, "")
}
context := Context{
Value: args[len(args)-1],
Args: args[:len(args)-1],
Env: os.Environ(),
}
if wd, err := os.Getwd(); err == nil {
context.Dir = wd
}
if m, err := env.Sandbox(); err == nil {
context.Dir = m.WorkDir()
context.mockedReplies = m.Replies
}
return context
}
// LookupEnv retrieves the value of the environment variable named by the key.
func (c Context) LookupEnv(key string) (string, bool) {
prefix := key + "="
for i := len(c.Env) - 1; i >= 0; i-- {
if env := c.Env[i]; strings.HasPrefix(env, prefix) {
return strings.SplitN(env, "=", 2)[1], true
}
}
return "", false
}
// Getenv retrieves the value of the environment variable named by the key.
func (c Context) Getenv(key string) string {
v, _ := c.LookupEnv(key)
return v
}
// Setenv sets the value of the environment variable named by the key.
func (c *Context) Setenv(key, value string) {
if c.Env == nil {
c.Env = []string{}
}
c.Env = append(c.Env, fmt.Sprintf("%v=%v", key, value))
}
// Envsubst replaces ${var} in the string based on environment variables in current context.
func (c Context) Envsubst(s string) (string, error) {
return envsubst.Eval(s, c.Getenv)
}
// Command returns the Cmd struct to execute the named program with the given arguments.
// Env and Dir are set using the Context.
// See exec.Command for most details.
func (c Context) Command(name string, arg ...string) *execlog.Cmd {
if c.mockedReplies != nil {
if m, err := json.Marshal(append([]string{name}, arg...)); err == nil {
if reply, exists := c.mockedReplies[string(m)]; exists {
return execlog.Command("echo", reply) // TODO use mock
}
}
}
cmd := execlog.Command(name, arg...)
cmd.Env = c.Env
cmd.Dir = c.Dir
return cmd
}
func expandHome(s string) (string, error) {
if strings.HasPrefix(s, "~") {
if zsh.NamedDirectories.Matches(s) {
return zsh.NamedDirectories.Replace(s), nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
home = filepath.ToSlash(home)
switch s {
case "~":
s = home
default:
s = strings.Replace(s, "~/", home+"/", 1)
}
}
return s, nil
}
// Abs returns an absolute representation of path.
func (c Context) Abs(path string) (string, error) {
path = filepath.ToSlash(path)
if !strings.HasPrefix(path, "/") && !strings.HasPrefix(path, "~") && !util.HasVolumePrefix(path) { // path is relative
switch c.Dir {
case "":
path = "./" + path
default:
path = c.Dir + "/" + path
}
}
path, err := expandHome(path)
if err != nil {
return "", err
}
if len(path) == 2 && util.HasVolumePrefix(path) {
path += "/" // prevent `C:` -> `C:./current/working/directory`
}
result, err := filepath.Abs(path)
if err != nil {
return "", err
}
result = filepath.ToSlash(result)
if strings.HasSuffix(path, "/") && !strings.HasSuffix(result, "/") {
result += "/"
} else if strings.HasSuffix(path, "/.") && !strings.HasSuffix(result, "/.") {
result += "/."
}
return result, nil
}