• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

UiPath / uipathcli / 11162363984

03 Oct 2024 12:53PM UTC coverage: 90.424% (-0.02%) from 90.447%
11162363984

push

github

thschmitt
Create command abstraction and split up command builder

Added command and flag definition structures to separate building the
command metadata and actually rendering the CLI commands.

Moved all the interaction with the cli/v2 module in the cli.go
source file which simplifies the interactive with the module and
abstracts the details away.

The change also moves out some parts from the command_builder which
grew in complexity.

463 of 478 new or added lines in 8 files covered. (96.86%)

1 existing line in 1 file now uncovered.

4268 of 4720 relevant lines covered (90.42%)

1.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.43
/commandline/autocomplete_handler.go
1
package commandline
2

3
import (
4
        "errors"
5
        "fmt"
6
        "os"
7
        "path/filepath"
8
        "strings"
9
)
10

11
const AutocompletePowershell = "powershell"
12
const AutocompleteBash = "bash"
13

14
const directoryPermissions = 0755
15
const filePermissions = 0644
16

17
const completeHandlerEnabledCheck = "uipath_auto_complete"
18

19
const powershellCompleteHandler = `
20
$uipath_auto_complete = {
21
    param($wordToComplete, $commandAst, $cursorPosition)
22
    $padLength = $cursorPosition - $commandAst.Extent.StartOffset
23
    $textToComplete = $commandAst.ToString().PadRight($padLength, ' ').Substring(0, $padLength)
24
    $command, $params = $commandAst.ToString() -split " ", 2
25
    & $command autocomplete complete --command "$textToComplete" | foreach-object {
26
        [system.management.automation.completionresult]::new($_, $_, 'parametervalue', $_)
27
    }
28
}
29
Register-ArgumentCompleter -Native -CommandName uipath -ScriptBlock $uipath_auto_complete
30
`
31

32
const bashCompleteHandler = `
33
function _uipath_auto_complete()
34
{
35
  local executable="${COMP_WORDS[0]}"
36
  local cur="${COMP_WORDS[COMP_CWORD]}" IFS=$'\n'
37
  local candidates
38
  read -d '' -ra candidates < <($executable autocomplete complete --command "${COMP_LINE}" 2>/dev/null)
39
  read -d '' -ra COMPREPLY < <(compgen -W "${candidates[*]:-}" -- "$cur")
40
}
41
complete -f -F _uipath_auto_complete uipath
42
`
43

44
// autoCompleteHandler parses the autocomplete command and provides suggestions for the available commands.
45
// It tries to perform a prefix- as well as contains-match based on the current context.
46
// Example:
47
// uipath autocomplete complete --command "uipath o"
48
// returns:
49
// oms
50
// orchestrator
51
// documentunderstanding
52
type autoCompleteHandler struct {
53
}
54

55
func (a autoCompleteHandler) EnableCompleter(shell string, filePath string) (string, error) {
1✔
56
        if shell != AutocompletePowershell && shell != AutocompleteBash {
2✔
57
                return "", fmt.Errorf("Invalid shell, supported values: %s, %s", AutocompletePowershell, AutocompleteBash)
1✔
58
        }
1✔
59

60
        profileFilePath, err := a.profileFilePath(shell, filePath)
1✔
61
        if err != nil {
1✔
62
                return "", err
×
63
        }
×
64
        completeHandler := a.completeHandler(shell)
1✔
65
        return a.enableCompleter(shell, profileFilePath, completeHandlerEnabledCheck, completeHandler)
1✔
66
}
67

68
func (a autoCompleteHandler) profileFilePath(shell string, filePath string) (string, error) {
1✔
69
        if filePath != "" {
2✔
70
                return filePath, nil
1✔
71
        }
1✔
NEW
72
        if shell == AutocompletePowershell {
×
73
                return PowershellProfilePath()
×
74
        }
×
75
        return BashrcPath()
×
76
}
77

78
func (a autoCompleteHandler) completeHandler(shell string) string {
1✔
79
        if shell == AutocompletePowershell {
2✔
80
                return powershellCompleteHandler
1✔
81
        }
1✔
82
        return bashCompleteHandler
1✔
83
}
84

85
func (a autoCompleteHandler) enableCompleter(shell string, filePath string, enabledCheck string, completerHandler string) (string, error) {
1✔
86
        err := a.ensureDirectoryExists(filePath)
1✔
87
        if err != nil {
1✔
88
                return "", err
×
89
        }
×
90
        enabled, err := a.completerEnabled(filePath, enabledCheck)
1✔
91
        if err != nil {
1✔
92
                return "", err
×
93
        }
×
94
        if enabled {
2✔
95
                output := fmt.Sprintf("Shell: %s\nProfile: %s\n\nCommand completion is already enabled.", shell, filePath)
1✔
96
                return output, nil
1✔
97
        }
1✔
98
        err = a.writeCompleterHandler(filePath, completerHandler)
1✔
99
        if err != nil {
1✔
100
                return "", err
×
101
        }
×
102
        output := fmt.Sprintf("Shell: %s\nProfile: %s\n\nSuccessfully enabled command completion! Restart your shell for the changes to take effect.", shell, filePath)
1✔
103
        return output, nil
1✔
104
}
105

106
func (a autoCompleteHandler) ensureDirectoryExists(filePath string) error {
1✔
107
        err := os.MkdirAll(filepath.Dir(filePath), directoryPermissions)
1✔
108
        if err != nil {
1✔
109
                return fmt.Errorf("Error creating profile folder: %w", err)
×
110
        }
×
111
        return nil
1✔
112
}
113

114
func (a autoCompleteHandler) completerEnabled(filePath string, enabledCheck string) (bool, error) {
1✔
115
        content, err := os.ReadFile(filePath)
1✔
116
        if err != nil && errors.Is(err, os.ErrNotExist) {
1✔
117
                return false, nil
×
118
        }
×
119
        if err != nil {
1✔
120
                return false, fmt.Errorf("Error reading profile file: %w", err)
×
121
        }
×
122
        return strings.Contains(string(content), enabledCheck), nil
1✔
123
}
124

125
func (a autoCompleteHandler) writeCompleterHandler(filePath string, completerHandler string) error {
1✔
126
        file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, filePermissions)
1✔
127
        if err != nil {
1✔
128
                return fmt.Errorf("Error opening profile file: %w", err)
×
129
        }
×
130
        defer file.Close()
1✔
131
        if _, err := file.WriteString(completerHandler); err != nil {
1✔
132
                return fmt.Errorf("Error writing profile file: %w", err)
×
133
        }
×
134
        return nil
1✔
135
}
136

137
func (a autoCompleteHandler) Find(commandText string, command *CommandDefinition, exclude []string) []string {
1✔
138
        words := strings.Split(commandText, " ")
1✔
139
        if len(words) < 2 {
1✔
140
                return []string{}
×
141
        }
×
142

143
        for _, word := range words[1 : len(words)-1] {
2✔
144
                if strings.HasPrefix(word, "-") {
2✔
145
                        break
1✔
146
                }
147
                command = a.findCommand(word, command.Subcommands)
1✔
148
                if command == nil {
1✔
149
                        return []string{}
×
150
                }
×
151
        }
152

153
        lastWord := words[len(words)-1]
1✔
154
        if strings.HasPrefix(lastWord, "-") {
2✔
155
                return a.searchFlags(strings.TrimLeft(lastWord, "-"), command, append(exclude, words...))
1✔
156
        }
1✔
157
        return a.searchCommands(lastWord, command.Subcommands, exclude)
1✔
158
}
159

160
func (a autoCompleteHandler) findCommand(name string, commands []*CommandDefinition) *CommandDefinition {
1✔
161
        for _, command := range commands {
2✔
162
                if command.Name == name {
2✔
163
                        return command
1✔
164
                }
1✔
165
        }
166
        return nil
×
167
}
168

169
func (a autoCompleteHandler) searchCommands(word string, commands []*CommandDefinition, exclude []string) []string {
1✔
170
        result := []string{}
1✔
171
        for _, command := range commands {
2✔
172
                if strings.HasPrefix(command.Name, word) {
2✔
173
                        result = append(result, command.Name)
1✔
174
                }
1✔
175
        }
176
        for _, command := range commands {
2✔
177
                if strings.Contains(command.Name, word) {
2✔
178
                        result = append(result, command.Name)
1✔
179
                }
1✔
180
        }
181
        return a.removeDuplicates(a.removeExcluded(result, exclude))
1✔
182
}
183

184
func (a autoCompleteHandler) searchFlags(word string, command *CommandDefinition, exclude []string) []string {
1✔
185
        result := []string{}
1✔
186
        for _, flag := range command.Flags {
2✔
187
                if strings.HasPrefix(flag.Name, word) {
2✔
188
                        result = append(result, "--"+flag.Name)
1✔
189
                }
1✔
190
        }
191
        for _, flag := range command.Flags {
2✔
192
                if strings.Contains(flag.Name, word) {
2✔
193
                        result = append(result, "--"+flag.Name)
1✔
194
                }
1✔
195
        }
196
        return a.removeDuplicates(a.removeExcluded(result, exclude))
1✔
197
}
198

199
func (a autoCompleteHandler) removeDuplicates(values []string) []string {
1✔
200
        keys := make(map[string]bool)
1✔
201
        result := []string{}
1✔
202

1✔
203
        for _, entry := range values {
2✔
204
                if _, value := keys[entry]; !value {
2✔
205
                        keys[entry] = true
1✔
206
                        result = append(result, entry)
1✔
207
                }
1✔
208
        }
209
        return result
1✔
210
}
211

212
func (a autoCompleteHandler) removeExcluded(values []string, exclude []string) []string {
1✔
213
        result := []string{}
1✔
214
        for _, entry := range values {
2✔
215
                if !a.contains(exclude, entry) {
2✔
216
                        result = append(result, entry)
1✔
217
                }
1✔
218
        }
219
        return result
1✔
220
}
221

222
func (a autoCompleteHandler) contains(values []string, value string) bool {
1✔
223
        for _, v := range values {
2✔
224
                if v == value {
2✔
225
                        return true
1✔
226
                }
1✔
227
        }
228
        return false
1✔
229
}
230

231
func newAutoCompleteHandler() *autoCompleteHandler {
1✔
232
        return &autoCompleteHandler{}
1✔
233
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc