diff --git a/auth/browser_launcher.go b/auth/browser_launcher.go index f60aece..af2102d 100644 --- a/auth/browser_launcher.go +++ b/auth/browser_launcher.go @@ -4,12 +4,12 @@ import ( "fmt" "time" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/process" ) // BrowserLauncher tries to open the default browser on the local system. type BrowserLauncher struct { - Exec utils.ExecProcess + Exec process.ExecProcess } func (l BrowserLauncher) Open(url string) error { @@ -32,5 +32,5 @@ func (l BrowserLauncher) Open(url string) error { } func NewBrowserLauncher() *BrowserLauncher { - return &BrowserLauncher{utils.NewExecProcess()} + return &BrowserLauncher{process.NewExecProcess()} } diff --git a/auth/browser_launcher_darwin.go b/auth/browser_launcher_darwin.go index 013ef5e..0da656c 100644 --- a/auth/browser_launcher_darwin.go +++ b/auth/browser_launcher_darwin.go @@ -2,10 +2,8 @@ package auth -import ( - "github.com/UiPath/uipathcli/utils" -) +import "github.com/UiPath/uipathcli/utils/process" -func (l BrowserLauncher) openBrowser(url string) utils.ExecCmd { +func (l BrowserLauncher) openBrowser(url string) process.ExecCmd { return l.Exec.Command("open", url) } diff --git a/auth/browser_launcher_linux.go b/auth/browser_launcher_linux.go index 03d7160..d05d210 100644 --- a/auth/browser_launcher_linux.go +++ b/auth/browser_launcher_linux.go @@ -2,10 +2,8 @@ package auth -import ( - "github.com/UiPath/uipathcli/utils" -) +import "github.com/UiPath/uipathcli/utils/process" -func (l BrowserLauncher) openBrowser(url string) utils.ExecCmd { +func (l BrowserLauncher) openBrowser(url string) process.ExecCmd { return l.Exec.Command("xdg-open", url) } diff --git a/auth/browser_launcher_windows.go b/auth/browser_launcher_windows.go index 45ab3b2..f4f9b67 100644 --- a/auth/browser_launcher_windows.go +++ b/auth/browser_launcher_windows.go @@ -2,10 +2,8 @@ package auth -import ( - "github.com/UiPath/uipathcli/utils" -) +import "github.com/UiPath/uipathcli/utils/process" -func (l BrowserLauncher) openBrowser(url string) utils.ExecCmd { +func (l BrowserLauncher) openBrowser(url string) process.ExecCmd { return l.Exec.Command("rundll32", "url.dll,FileProtocolHandler", url) } diff --git a/auth/identity_client.go b/auth/identity_client.go index 0330ad3..10cf6ce 100644 --- a/auth/identity_client.go +++ b/auth/identity_client.go @@ -10,7 +10,7 @@ import ( "strings" "github.com/UiPath/uipathcli/cache" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/resiliency" ) type identityClient struct { @@ -48,7 +48,7 @@ func (c identityClient) GetToken(tokenRequest tokenRequest) (*tokenResponse, err func (c identityClient) retrieveToken(baseUri url.URL, form url.Values, insecure bool) (*tokenResponse, error) { var response *tokenResponse - err := utils.Retry(func() error { + err := resiliency.Retry(func() error { var err error response, err = c.send(baseUri, form, insecure) return err @@ -70,15 +70,15 @@ func (c identityClient) send(baseUri url.URL, form url.Values, insecure bool) (* client := http.Client{Transport: transport} response, err := client.Do(request) if err != nil { - return nil, utils.Retryable(fmt.Errorf("Error sending request: %w", err)) + return nil, resiliency.Retryable(fmt.Errorf("Error sending request: %w", err)) } defer response.Body.Close() bytes, err := io.ReadAll(response.Body) if err != nil { - return nil, utils.Retryable(fmt.Errorf("Error reading response: %w", err)) + return nil, resiliency.Retryable(fmt.Errorf("Error reading response: %w", err)) } if response.StatusCode >= 500 { - return nil, utils.Retryable(fmt.Errorf("Token service returned status code '%v' and body '%v'", response.StatusCode, string(bytes))) + return nil, resiliency.Retryable(fmt.Errorf("Token service returned status code '%v' and body '%v'", response.StatusCode, string(bytes))) } if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("Token service returned status code '%v' and body '%v'", response.StatusCode, string(bytes)) diff --git a/auth/oauth_authenticator_test.go b/auth/oauth_authenticator_test.go index 36a3961..2b35fed 100644 --- a/auth/oauth_authenticator_test.go +++ b/auth/oauth_authenticator_test.go @@ -16,7 +16,7 @@ import ( "testing" "github.com/UiPath/uipathcli/cache" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/process" ) func TestOAuthAuthenticatorNotEnabled(t *testing.T) { @@ -211,7 +211,7 @@ func TestMissingCodeShowsErrorMessage(t *testing.T) { func callAuthenticator(context AuthenticatorContext) (url.URL, chan AuthenticatorResult) { loginChan := make(chan string) authenticator := NewOAuthAuthenticator(cache.NewFileCache(), BrowserLauncher{ - Exec: utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + Exec: process.NewExecCustomProcess(0, "", "", func(name string, args []string) { switch runtime.GOOS { case "windows": loginChan <- args[1] diff --git a/cache/file_cache.go b/cache/file_cache.go index 00a8822..a4f44de 100644 --- a/cache/file_cache.go +++ b/cache/file_cache.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/directories" ) const cacheFilePermissions = 0600 @@ -63,7 +63,7 @@ func (c FileCache) readValue(key string) (int64, string, error) { } func (c FileCache) cacheFilePath(key string) (string, error) { - cacheDirectory, err := utils.Directories{}.Cache() + cacheDirectory, err := directories.Cache() if err != nil { return "", err } diff --git a/commandline/cli.go b/commandline/cli.go index ddf4029..ab4d62b 100644 --- a/commandline/cli.go +++ b/commandline/cli.go @@ -8,7 +8,7 @@ import ( "github.com/UiPath/uipathcli/config" "github.com/UiPath/uipathcli/executor" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" "github.com/urfave/cli/v2" ) @@ -24,7 +24,7 @@ type Cli struct { pluginExecutor executor.Executor } -func (c Cli) run(args []string, input utils.Stream) error { +func (c Cli) run(args []string, input stream.Stream) error { err := c.configProvider.Load() if err != nil { return err @@ -78,7 +78,7 @@ func (c Cli) run(args []string, input utils.Stream) error { const colorRed = "\033[31m" const colorReset = "\033[0m" -func (c Cli) Run(args []string, input utils.Stream) error { +func (c Cli) Run(args []string, input stream.Stream) error { err := c.run(args, input) if err != nil { message := err.Error() diff --git a/commandline/command_builder.go b/commandline/command_builder.go index 1d774e2..d36774d 100644 --- a/commandline/command_builder.go +++ b/commandline/command_builder.go @@ -16,12 +16,12 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/output" "github.com/UiPath/uipathcli/parser" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" ) // The CommandBuilder is creating all available operations and arguments for the CLI. type CommandBuilder struct { - Input utils.Stream + Input stream.Stream StdIn io.Reader StdOut io.Writer StdErr io.Writer @@ -37,7 +37,7 @@ func (b CommandBuilder) sort(commands []*CommandDefinition) { }) } -func (b CommandBuilder) fileInput(context *CommandExecContext, parameters []parser.Parameter) utils.Stream { +func (b CommandBuilder) fileInput(context *CommandExecContext, parameters []parser.Parameter) stream.Stream { value := context.String(FlagNameFile) if value == "" { return nil @@ -50,7 +50,7 @@ func (b CommandBuilder) fileInput(context *CommandExecContext, parameters []pars return nil } } - return utils.NewFileStream(value) + return stream.NewFileStream(value) } func (b CommandBuilder) createExecutionParameters(context *CommandExecContext, config *config.Config, operation parser.Operation) (executor.ExecutionParameters, error) { diff --git a/commandline/type_converter.go b/commandline/type_converter.go index 0bb620e..d4e22f2 100644 --- a/commandline/type_converter.go +++ b/commandline/type_converter.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/UiPath/uipathcli/parser" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" ) // The typeConverter converts the string value from the command-line argument into the type @@ -53,8 +53,8 @@ func (c typeConverter) convertToBoolean(value string, parameter parser.Parameter return false, fmt.Errorf("Cannot convert '%s' value '%s' to boolean", parameter.Name, value) } -func (c typeConverter) convertToBinary(value string, parameter parser.Parameter) (utils.Stream, error) { - return utils.NewFileStream(value), nil +func (c typeConverter) convertToBinary(value string, parameter parser.Parameter) (stream.Stream, error) { + return stream.NewFileStream(value), nil } func (c typeConverter) findParameter(parameter *parser.Parameter, name string) *parser.Parameter { diff --git a/commandline/type_converter_test.go b/commandline/type_converter_test.go index aef088c..a011926 100644 --- a/commandline/type_converter_test.go +++ b/commandline/type_converter_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/UiPath/uipathcli/parser" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" ) func TestConvertReturnsErrorForInvalidBoolean(t *testing.T) { @@ -41,7 +41,7 @@ func TestConvertStringToFileStream(t *testing.T) { if err != nil { t.Errorf("Should not return error, but got: %v", err) } - fileStream := result.(*utils.FileStream) + fileStream := result.(*stream.FileStream) if fileStream.Name() != "test.txt" { t.Errorf("Result should be file stream, but got: %v", result) } diff --git a/executor/execution_context.go b/executor/execution_context.go index d800473..8fbcbae 100644 --- a/executor/execution_context.go +++ b/executor/execution_context.go @@ -5,7 +5,7 @@ import ( "github.com/UiPath/uipathcli/config" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" ) // The ExecutionContext provides all the data needed by the executor to construct the HTTP @@ -17,7 +17,7 @@ type ExecutionContext struct { BaseUri url.URL Route string ContentType string - Input utils.Stream + Input stream.Stream Parameters ExecutionParameters AuthConfig config.AuthConfig Insecure bool @@ -33,7 +33,7 @@ func NewExecutionContext( uri url.URL, route string, contentType string, - input utils.Stream, + input stream.Stream, parameters []ExecutionParameter, authConfig config.AuthConfig, insecure bool, diff --git a/executor/http_executor.go b/executor/http_executor.go index c395098..91d8376 100644 --- a/executor/http_executor.go +++ b/executor/http_executor.go @@ -20,6 +20,10 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/output" "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/converter" + "github.com/UiPath/uipathcli/utils/resiliency" + "github.com/UiPath/uipathcli/utils/stream" + "github.com/UiPath/uipathcli/utils/visualization" ) const NotConfiguredErrorTemplate = `Run config command to set organization and tenant: @@ -40,7 +44,7 @@ type HttpExecutor struct { } func (e HttpExecutor) Call(context ExecutionContext, writer output.OutputWriter, logger log.Logger) error { - return utils.Retry(func() error { + return resiliency.Retry(func() error { return e.call(context, writer, logger) }) } @@ -52,11 +56,11 @@ func (e HttpExecutor) requestId() string { } func (e HttpExecutor) addHeaders(request *http.Request, headerParameters []ExecutionParameter) { - formatter := newParameterFormatter() + converter := converter.NewStringConverter() request.Header.Add("x-request-id", e.requestId()) request.Header.Add("User-Agent", UserAgent) for _, parameter := range headerParameters { - headerValue := formatter.Format(parameter) + headerValue := converter.ToString(parameter.Value) request.Header.Add(parameter.Name, headerValue) } } @@ -67,7 +71,7 @@ func (e HttpExecutor) calculateMultipartSize(parameters []ExecutionParameter) in switch v := parameter.Value.(type) { case string: result = result + int64(len(v)) - case utils.Stream: + case stream.Stream: size, err := v.Size() if err == nil { result = result + size @@ -89,7 +93,7 @@ func (e HttpExecutor) writeMultipartForm(writer *multipart.Writer, parameters [] if err != nil { return fmt.Errorf("Error writing form field '%s': %w", parameter.Name, err) } - case utils.Stream: + case stream.Stream: w, err := writer.CreateFormFile(parameter.Name, v.Name()) if err != nil { return fmt.Errorf("Error writing form file '%s': %w", parameter.Name, err) @@ -140,12 +144,14 @@ func (e HttpExecutor) validateUri(uri string) (*url.URL, error) { } func (e HttpExecutor) formatUri(baseUri url.URL, route string, pathParameters []ExecutionParameter, queryParameters []ExecutionParameter) (*url.URL, error) { - formatter := newUriFormatter(baseUri, route) + uriBuilder := converter.NewUriBuilder(baseUri, route) for _, parameter := range pathParameters { - formatter.FormatPath(parameter) + uriBuilder.FormatPath(parameter.Name, parameter.Value) } - formatter.AddQueryString(queryParameters) - return e.validateUri(formatter.Uri()) + for _, parameter := range queryParameters { + uriBuilder.AddQueryString(parameter.Name, parameter.Value) + } + return e.validateUri(uriBuilder.Build()) } func (e HttpExecutor) executeAuthenticators(authConfig config.AuthConfig, identityUri url.URL, debug bool, insecure bool, request *http.Request) (*auth.AuthenticatorResult, error) { @@ -164,11 +170,11 @@ func (e HttpExecutor) executeAuthenticators(authConfig config.AuthConfig, identi return auth.AuthenticatorSuccess(ctx.Request.Header, ctx.Config), nil } -func (e HttpExecutor) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader { +func (e HttpExecutor) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader { if length < 10*1024*1024 { return reader } - progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) { + progressReader := visualization.NewProgressReader(reader, func(progress visualization.Progress) { displayText := text if progress.Completed { displayText = completedText @@ -193,7 +199,7 @@ func (e HttpExecutor) writeMultipartBody(bodyWriter *io.PipeWriter, parameters [ return formWriter.FormDataContentType(), multipartSize } -func (e HttpExecutor) writeInputBody(bodyWriter *io.PipeWriter, input utils.Stream, errorChan chan error) { +func (e HttpExecutor) writeInputBody(bodyWriter *io.PipeWriter, input stream.Stream, errorChan chan error) { go func() { defer bodyWriter.Close() data, err := input.Data() @@ -213,8 +219,11 @@ func (e HttpExecutor) writeInputBody(bodyWriter *io.PipeWriter, input utils.Stre func (e HttpExecutor) writeUrlEncodedBody(bodyWriter *io.PipeWriter, parameters []ExecutionParameter, errorChan chan error) { go func() { defer bodyWriter.Close() - formatter := newQueryStringFormatter() - queryString := formatter.Format(parameters) + queryStringBuilder := converter.NewQueryStringBuilder() + for _, parameter := range parameters { + queryStringBuilder.Add(parameter.Name, parameter.Value) + } + queryString := queryStringBuilder.Build() _, err := bodyWriter.Write([]byte(queryString)) if err != nil { errorChan <- err @@ -312,7 +321,7 @@ func (e HttpExecutor) call(context ExecutionContext, writer output.OutputWriter, } requestError := make(chan error) bodyReader, contentType, contentLength, size := e.writeBody(context, requestError) - uploadBar := utils.NewProgressBar(logger) + uploadBar := visualization.NewProgressBar(logger) uploadReader := e.progressReader("uploading...", "completing ", bodyReader, size, uploadBar) defer uploadBar.Remove() request, err := http.NewRequest(context.Method, uri.String(), uploadReader) @@ -344,19 +353,19 @@ func (e HttpExecutor) call(context ExecutionContext, writer output.OutputWriter, } response, err := e.send(client, request, requestError) if err != nil { - return utils.Retryable(fmt.Errorf("Error sending request: %w", err)) + return resiliency.Retryable(fmt.Errorf("Error sending request: %w", err)) } defer response.Body.Close() - downloadBar := utils.NewProgressBar(logger) + downloadBar := visualization.NewProgressBar(logger) downloadReader := e.progressReader("downloading...", "completing ", response.Body, response.ContentLength, downloadBar) defer downloadBar.Remove() body, err := io.ReadAll(downloadReader) if err != nil { - return utils.Retryable(fmt.Errorf("Error reading response body: %w", err)) + return resiliency.Retryable(fmt.Errorf("Error reading response body: %w", err)) } e.logResponse(logger, response, body) if response.StatusCode >= 500 { - return utils.Retryable(fmt.Errorf("Service returned status code '%v' and body '%v'", response.StatusCode, string(body))) + return resiliency.Retryable(fmt.Errorf("Service returned status code '%v' and body '%v'", response.StatusCode, string(body))) } err = writer.WriteResponse(*output.NewResponseInfo(response.StatusCode, response.Status, response.Proto, response.Header, bytes.NewReader(body))) if err != nil { diff --git a/executor/parameter_formatter.go b/executor/parameter_formatter.go deleted file mode 100644 index 8a7d87b..0000000 --- a/executor/parameter_formatter.go +++ /dev/null @@ -1,46 +0,0 @@ -package executor - -import ( - "fmt" - "strings" -) - -// parameterFormatter converts ExecutionParameter into a string. -// Depending on the type of the parameter, the formatter converts the value to -// the proper format: -// - Integers, Float, etc.. are simply converted to a string -// - Arrays are formatted comma-separated -// - Booleans are converted to true or false -type parameterFormatter struct{} - -func (f parameterFormatter) Format(parameter ExecutionParameter) string { - return f.formatParameter(parameter) -} - -func (f parameterFormatter) formatParameter(parameter ExecutionParameter) string { - switch value := parameter.Value.(type) { - case []int: - return f.arrayToCommaSeparatedString(value) - case []float64: - return f.arrayToCommaSeparatedString(value) - case []bool: - return f.arrayToCommaSeparatedString(value) - case []string: - return f.arrayToCommaSeparatedString(value) - default: - return fmt.Sprintf("%v", value) - } -} - -func (f parameterFormatter) arrayToCommaSeparatedString(array interface{}) string { - switch value := array.(type) { - case []string: - return strings.Join(value, ",") - default: - return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(value)), ","), "[]") - } -} - -func newParameterFormatter() *parameterFormatter { - return ¶meterFormatter{} -} diff --git a/executor/query_string_formatter.go b/executor/query_string_formatter.go deleted file mode 100644 index 300451f..0000000 --- a/executor/query_string_formatter.go +++ /dev/null @@ -1,95 +0,0 @@ -package executor - -import ( - "fmt" - "net/url" - "strings" -) - -// queryStringFormatter converts ExecutionParameter's into a query string. -// -// Depending on the type of the parameter, the formatter converts the value to -// the proper format and makes sure the query string is properly escaped. -// -// Example: -// - parameter 'username' and value 'tschmitt' -// - parameter 'message' and value 'Hello World' -// --> username=tschmitt&message=Hello+World -type queryStringFormatter struct{} - -func (f queryStringFormatter) Format(parameters []ExecutionParameter) string { - result := "" - for _, parameter := range parameters { - param := f.formatQueryStringParam(parameter) - if result == "" { - result = param - } else { - result = result + "&" + param - } - } - return result -} - -func (f queryStringFormatter) formatQueryStringParam(parameter ExecutionParameter) string { - switch value := parameter.Value.(type) { - case []int: - return f.integerArrayToQueryString(parameter.Name, value) - case []float64: - return f.numberArrayToQueryString(parameter.Name, value) - case []bool: - return f.booleanArrayToQueryString(parameter.Name, value) - case []string: - return f.stringArrayToQueryString(parameter.Name, value) - default: - return f.toQueryString(parameter.Name, value) - } -} - -func (f queryStringFormatter) integerArrayToQueryString(key string, value []int) string { - result := make([]interface{}, len(value)) - for i, v := range value { - result[i] = v - } - return f.arrayToQueryString(key, result) -} - -func (f queryStringFormatter) numberArrayToQueryString(key string, value []float64) string { - result := make([]interface{}, len(value)) - for i, v := range value { - result[i] = v - } - return f.arrayToQueryString(key, result) -} - -func (f queryStringFormatter) booleanArrayToQueryString(key string, value []bool) string { - result := make([]interface{}, len(value)) - for i, v := range value { - result[i] = v - } - return f.arrayToQueryString(key, result) -} - -func (f queryStringFormatter) stringArrayToQueryString(key string, value []string) string { - result := make([]interface{}, len(value)) - for i, v := range value { - result[i] = v - } - return f.arrayToQueryString(key, result) -} - -func (f queryStringFormatter) arrayToQueryString(key string, value []interface{}) string { - result := make([]string, len(value)) - for i, v := range value { - result[i] = f.toQueryString(key, v) - } - return strings.Join(result, "&") -} - -func (f queryStringFormatter) toQueryString(key string, value interface{}) string { - stringValue := fmt.Sprintf("%v", value) - return fmt.Sprintf("%s=%v", key, url.QueryEscape(stringValue)) -} - -func newQueryStringFormatter() *queryStringFormatter { - return &queryStringFormatter{} -} diff --git a/executor/uri_formatter.go b/executor/uri_formatter.go deleted file mode 100644 index 8ca11af..0000000 --- a/executor/uri_formatter.go +++ /dev/null @@ -1,51 +0,0 @@ -package executor - -import ( - "fmt" - "net/url" - "path" - "strings" -) - -// uriFormatter takes an Uri and formats it with ExecutionParameter values. -// -// The formatter supports replacing path placeholders like organization and tenant: -// https://cloud.uipath.com/{organization} -// with parameter 'organization' and value 'my-org' -// --> https://cloud.uipath.com/my-org -// -// The formatter also supports adding query strings to the uri: -// https://cloud.uipath.com/users -// with parameter 'firstName' and value 'Thomas' -// and parameter 'lastName' and value 'Schmitt' -// --> https://cloud.uipath.com/users?firstName=Thomas&lastName=Schmitt -type uriFormatter struct { - uri string - queryString string -} - -func (f *uriFormatter) FormatPath(parameter ExecutionParameter) { - formatter := newParameterFormatter() - value := formatter.Format(parameter) - f.uri = strings.ReplaceAll(f.uri, "{"+parameter.Name+"}", value) -} - -func (f *uriFormatter) AddQueryString(parameters []ExecutionParameter) { - formatter := newQueryStringFormatter() - f.queryString = formatter.Format(parameters) -} - -func (f uriFormatter) Uri() string { - if f.queryString == "" { - return f.uri - } - return f.uri + "?" + f.queryString -} - -func newUriFormatter(baseUri url.URL, route string) *uriFormatter { - normalizedPath := strings.Trim(baseUri.Path, "/") - normalizedRoute := strings.Trim(route, "/") - path := path.Join(normalizedPath, normalizedRoute) - uri := fmt.Sprintf("%s://%s/%s", baseUri.Scheme, baseUri.Host, path) - return &uriFormatter{uri, ""} -} diff --git a/main.go b/main.go index 7bcdfae..2b9b83a 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ import ( plugin_digitizer "github.com/UiPath/uipathcli/plugin/digitizer" plugin_orchestrator "github.com/UiPath/uipathcli/plugin/orchestrator" plugin_studio "github.com/UiPath/uipathcli/plugin/studio" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" ) //go:embed definitions/*.yaml @@ -40,8 +40,8 @@ func colorsSupported() bool { return !omitColors } -func stdIn() utils.Stream { - return utils.NewReaderStream(parser.RawBodyParameterName, os.Stdin) +func stdIn() stream.Stream { + return stream.NewReaderStream(parser.RawBodyParameterName, os.Stdin) } func main() { diff --git a/plugin/digitizer/digitize_command.go b/plugin/digitizer/digitize_command.go index c0495dd..d580f6d 100644 --- a/plugin/digitizer/digitize_command.go +++ b/plugin/digitizer/digitize_command.go @@ -17,7 +17,8 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/output" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" + "github.com/UiPath/uipathcli/utils/visualization" ) // The DigitizeCommand is a convenient wrapper over the async digitizer API @@ -59,7 +60,7 @@ func (c DigitizeCommand) Execute(context plugin.ExecutionContext, writer output. } func (c DigitizeCommand) startDigitization(context plugin.ExecutionContext, logger log.Logger) (string, error) { - uploadBar := utils.NewProgressBar(logger) + uploadBar := visualization.NewProgressBar(logger) defer uploadBar.Remove() requestError := make(chan error) request, err := c.createDigitizeRequest(context, uploadBar, requestError) @@ -123,7 +124,7 @@ func (c DigitizeCommand) waitForDigitization(documentId string, context plugin.E return true, err } -func (c DigitizeCommand) createDigitizeRequest(context plugin.ExecutionContext, uploadBar *utils.ProgressBar, requestError chan error) (*http.Request, error) { +func (c DigitizeCommand) createDigitizeRequest(context plugin.ExecutionContext, uploadBar *visualization.ProgressBar, requestError chan error) (*http.Request, error) { projectId := c.getProjectId(context.Parameters) var err error @@ -152,11 +153,11 @@ func (c DigitizeCommand) createDigitizeRequest(context plugin.ExecutionContext, return request, nil } -func (c DigitizeCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader { +func (c DigitizeCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader { if length < 10*1024*1024 { return reader } - progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) { + progressReader := visualization.NewProgressReader(reader, func(progress visualization.Progress) { displayText := text if progress.Completed { displayText = completedText @@ -191,12 +192,12 @@ func (c DigitizeCommand) createDigitizeStatusRequest(documentId string, context return request, nil } -func (c DigitizeCommand) calculateMultipartSize(stream utils.Stream) int64 { +func (c DigitizeCommand) calculateMultipartSize(stream stream.Stream) int64 { size, _ := stream.Size() return size } -func (c DigitizeCommand) writeMultipartForm(writer *multipart.Writer, stream utils.Stream, contentType string) error { +func (c DigitizeCommand) writeMultipartForm(writer *multipart.Writer, stream stream.Stream, contentType string) error { filePart := textproto.MIMEHeader{} filePart.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, stream.Name())) filePart.Set("Content-Type", contentType) @@ -216,7 +217,7 @@ func (c DigitizeCommand) writeMultipartForm(writer *multipart.Writer, stream uti return nil } -func (c DigitizeCommand) writeMultipartBody(bodyWriter *io.PipeWriter, stream utils.Stream, contentType string, errorChan chan error) (string, int64) { +func (c DigitizeCommand) writeMultipartBody(bodyWriter *io.PipeWriter, stream stream.Stream, contentType string, errorChan chan error) (string, int64) { contentLength := c.calculateMultipartSize(stream) formWriter := multipart.NewWriter(bodyWriter) go func() { @@ -279,11 +280,11 @@ func (c DigitizeCommand) getParameter(name string, parameters []plugin.Execution return result } -func (c DigitizeCommand) getFileParameter(parameters []plugin.ExecutionParameter) utils.Stream { - var result utils.Stream +func (c DigitizeCommand) getFileParameter(parameters []plugin.ExecutionParameter) stream.Stream { + var result stream.Stream for _, p := range parameters { if p.Name == "file" { - if stream, ok := p.Value.(utils.Stream); ok { + if stream, ok := p.Value.(stream.Stream); ok { result = stream break } diff --git a/plugin/execution_context.go b/plugin/execution_context.go index 796ea40..8e5ba17 100644 --- a/plugin/execution_context.go +++ b/plugin/execution_context.go @@ -3,7 +3,7 @@ package plugin import ( "net/url" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" ) // The ExecutionContext provides all the data needed by the plugin to perform the operation. @@ -12,7 +12,7 @@ type ExecutionContext struct { Tenant string BaseUri url.URL Auth AuthResult - Input utils.Stream + Input stream.Stream Parameters []ExecutionParameter Insecure bool Debug bool @@ -23,7 +23,7 @@ func NewExecutionContext( tenant string, baseUri url.URL, auth AuthResult, - input utils.Stream, + input stream.Stream, parameters []ExecutionParameter, insecure bool, debug bool) *ExecutionContext { diff --git a/plugin/external_plugin.go b/plugin/external_plugin.go index 35d2a8c..113fa7a 100644 --- a/plugin/external_plugin.go +++ b/plugin/external_plugin.go @@ -12,7 +12,8 @@ import ( "path/filepath" "github.com/UiPath/uipathcli/log" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/directories" + "github.com/UiPath/uipathcli/utils/visualization" ) const pluginDirectoryPermissions = 0700 @@ -34,7 +35,7 @@ func (p ExternalPlugin) GetTool(name string, url string, executable string) (str tmpPluginDirectory := pluginDirectory + "-" + p.randomFolderName() _ = os.MkdirAll(tmpPluginDirectory, pluginDirectoryPermissions) - progressBar := utils.NewProgressBar(p.Logger) + progressBar := visualization.NewProgressBar(p.Logger) defer progressBar.Remove() progressBar.UpdatePercentage("downloading...", 0) zipArchivePath := filepath.Join(tmpPluginDirectory, name) @@ -54,7 +55,7 @@ func (p ExternalPlugin) GetTool(name string, url string, executable string) (str return path, nil } -func (p ExternalPlugin) download(name string, url string, destination string, progressBar *utils.ProgressBar) error { +func (p ExternalPlugin) download(name string, url string, destination string, progressBar *visualization.ProgressBar) error { out, err := os.Create(destination) if err != nil { return fmt.Errorf("Could not download %s: %v", name, err) @@ -77,8 +78,8 @@ func (p ExternalPlugin) download(name string, url string, destination string, pr return nil } -func (p ExternalPlugin) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader { - progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) { +func (p ExternalPlugin) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader { + progressReader := visualization.NewProgressReader(reader, func(progress visualization.Progress) { displayText := text if progress.Completed { displayText = completedText @@ -89,7 +90,7 @@ func (p ExternalPlugin) progressReader(text string, completedText string, reader } func (p ExternalPlugin) pluginDirectory(name string, url string) (string, error) { - pluginDirectory, err := utils.Directories{}.Plugin() + pluginDirectory, err := directories.Plugin() if err != nil { return "", err } diff --git a/plugin/orchestrator/download_command.go b/plugin/orchestrator/download_command.go index 7818114..7b06a5c 100644 --- a/plugin/orchestrator/download_command.go +++ b/plugin/orchestrator/download_command.go @@ -14,7 +14,7 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/output" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/visualization" ) // The DownloadCommand is a custom command for the orchestrator service which makes downloading @@ -53,7 +53,7 @@ func (c DownloadCommand) download(context plugin.ExecutionContext, writer output return fmt.Errorf("Error sending request: %w", err) } defer response.Body.Close() - downloadBar := utils.NewProgressBar(logger) + downloadBar := visualization.NewProgressBar(logger) downloadReader := c.progressReader("downloading...", "completing ", response.Body, response.ContentLength, downloadBar) defer downloadBar.Remove() body, err := io.ReadAll(downloadReader) @@ -68,11 +68,11 @@ func (c DownloadCommand) download(context plugin.ExecutionContext, writer output return nil } -func (c DownloadCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader { +func (c DownloadCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader { if length < 10*1024*1024 { return reader } - progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) { + progressReader := visualization.NewProgressReader(reader, func(progress visualization.Progress) { displayText := text if progress.Completed { displayText = completedText diff --git a/plugin/orchestrator/upload_command.go b/plugin/orchestrator/upload_command.go index ba5bb36..500395d 100644 --- a/plugin/orchestrator/upload_command.go +++ b/plugin/orchestrator/upload_command.go @@ -14,7 +14,8 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/output" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" + "github.com/UiPath/uipathcli/utils/visualization" ) // The UploadCommand is a custom command for the orchestrator service which makes uploading @@ -41,7 +42,7 @@ func (c UploadCommand) Execute(context plugin.ExecutionContext, writer output.Ou } func (c UploadCommand) upload(context plugin.ExecutionContext, logger log.Logger, url string) error { - uploadBar := utils.NewProgressBar(logger) + uploadBar := visualization.NewProgressBar(logger) defer uploadBar.Remove() requestError := make(chan error) request, err := c.createUploadRequest(context, url, uploadBar, requestError) @@ -67,7 +68,7 @@ func (c UploadCommand) upload(context plugin.ExecutionContext, logger log.Logger return nil } -func (c UploadCommand) createUploadRequest(context plugin.ExecutionContext, url string, uploadBar *utils.ProgressBar, requestError chan error) (*http.Request, error) { +func (c UploadCommand) createUploadRequest(context plugin.ExecutionContext, url string, uploadBar *visualization.ProgressBar, requestError chan error) (*http.Request, error) { file := context.Input if file == nil { file = c.getFileParameter(context.Parameters) @@ -86,7 +87,7 @@ func (c UploadCommand) createUploadRequest(context plugin.ExecutionContext, url return request, nil } -func (c UploadCommand) writeBody(bodyWriter *io.PipeWriter, input utils.Stream, errorChan chan error) (string, int64) { +func (c UploadCommand) writeBody(bodyWriter *io.PipeWriter, input stream.Stream, errorChan chan error) (string, int64) { go func() { defer bodyWriter.Close() data, err := input.Data() @@ -105,11 +106,11 @@ func (c UploadCommand) writeBody(bodyWriter *io.PipeWriter, input utils.Stream, return "application/octet-stream", size } -func (c UploadCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *utils.ProgressBar) io.Reader { +func (c UploadCommand) progressReader(text string, completedText string, reader io.Reader, length int64, progressBar *visualization.ProgressBar) io.Reader { if length < 10*1024*1024 { return reader } - progressReader := utils.NewProgressReader(reader, func(progress utils.Progress) { + progressReader := visualization.NewProgressReader(reader, func(progress visualization.Progress) { displayText := text if progress.Completed { displayText = completedText @@ -236,11 +237,11 @@ func (c UploadCommand) getIntParameter(name string, parameters []plugin.Executio return result } -func (c UploadCommand) getFileParameter(parameters []plugin.ExecutionParameter) utils.Stream { - var result utils.Stream +func (c UploadCommand) getFileParameter(parameters []plugin.ExecutionParameter) stream.Stream { + var result stream.Stream for _, p := range parameters { if p.Name == "file" { - if stream, ok := p.Value.(utils.Stream); ok { + if stream, ok := p.Value.(stream.Stream); ok { result = stream break } diff --git a/plugin/studio/package_analyze_command.go b/plugin/studio/package_analyze_command.go index 5aef3da..184ff79 100644 --- a/plugin/studio/package_analyze_command.go +++ b/plugin/studio/package_analyze_command.go @@ -19,12 +19,14 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/output" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/directories" + "github.com/UiPath/uipathcli/utils/process" + "github.com/UiPath/uipathcli/utils/visualization" ) // The PackageAnalyzeCommand runs static code analyis on the project to detect common errors. type PackageAnalyzeCommand struct { - Exec utils.ExecProcess + Exec process.ExecProcess } func (c PackageAnalyzeCommand) Command() plugin.Command { @@ -133,7 +135,7 @@ func (c PackageAnalyzeCommand) execute(source string, treatWarningsAsErrors bool } func (c PackageAnalyzeCommand) getTemporaryJsonResultFilePath() (string, error) { - tempDirectory, err := utils.Directories{}.Temp() + tempDirectory, err := directories.Temp() if err != nil { return "", err } @@ -203,13 +205,13 @@ func (c PackageAnalyzeCommand) convertToViolations(json analyzeResultJson) []pac return violations } -func (c PackageAnalyzeCommand) wait(cmd utils.ExecCmd, wg *sync.WaitGroup) { +func (c PackageAnalyzeCommand) wait(cmd process.ExecCmd, wg *sync.WaitGroup) { defer wg.Done() _ = cmd.Wait() } func (c PackageAnalyzeCommand) newAnalyzingProgressBar(logger log.Logger) chan struct{} { - progressBar := utils.NewProgressBar(logger) + progressBar := visualization.NewProgressBar(logger) ticker := time.NewTicker(10 * time.Millisecond) cancel := make(chan struct{}) var percent float64 = 0 @@ -284,5 +286,5 @@ func (c PackageAnalyzeCommand) getBoolParameter(name string, parameters []plugin } func NewPackageAnalyzeCommand() *PackageAnalyzeCommand { - return &PackageAnalyzeCommand{utils.NewExecProcess()} + return &PackageAnalyzeCommand{process.NewExecProcess()} } diff --git a/plugin/studio/package_pack_command.go b/plugin/studio/package_pack_command.go index b099952..c15ccc1 100644 --- a/plugin/studio/package_pack_command.go +++ b/plugin/studio/package_pack_command.go @@ -17,7 +17,8 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/output" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/process" + "github.com/UiPath/uipathcli/utils/visualization" ) const defaultProjectJson = "project.json" @@ -26,7 +27,7 @@ var OutputTypeAllowedValues = []string{"Process", "Library", "Tests", "Objects"} // The PackagePackCommand packs a project into a single NuGet package type PackagePackCommand struct { - Exec utils.ExecProcess + Exec process.ExecProcess } func (c PackagePackCommand) Command() plugin.Command { @@ -186,13 +187,13 @@ func (c PackagePackCommand) extractVersion(nupkgFile string) string { return fmt.Sprintf("%s.%s.%s", parts[len-4], parts[len-3], parts[len-2]) } -func (c PackagePackCommand) wait(cmd utils.ExecCmd, wg *sync.WaitGroup) { +func (c PackagePackCommand) wait(cmd process.ExecCmd, wg *sync.WaitGroup) { defer wg.Done() _ = cmd.Wait() } func (c PackagePackCommand) newPackagingProgressBar(logger log.Logger) chan struct{} { - progressBar := utils.NewProgressBar(logger) + progressBar := visualization.NewProgressBar(logger) ticker := time.NewTicker(10 * time.Millisecond) cancel := make(chan struct{}) var percent float64 = 0 @@ -276,5 +277,5 @@ func (c PackagePackCommand) getBoolParameter(name string, parameters []plugin.Ex } func NewPackagePackCommand() *PackagePackCommand { - return &PackagePackCommand{utils.NewExecProcess()} + return &PackagePackCommand{process.NewExecProcess()} } diff --git a/plugin/studio/studio_plugin_notwin_test.go b/plugin/studio/studio_plugin_notwin_test.go index 85b5a17..77b5ce1 100644 --- a/plugin/studio/studio_plugin_notwin_test.go +++ b/plugin/studio/studio_plugin_notwin_test.go @@ -8,13 +8,13 @@ import ( "testing" "github.com/UiPath/uipathcli/test" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/process" ) func TestPackOnLinuxWithCorrectArguments(t *testing.T) { commandName := "" commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandName = name commandArgs = args }) @@ -53,7 +53,7 @@ func TestPackOnLinuxWithCorrectArguments(t *testing.T) { func TestAnalyzeOnLinuxWithCorrectArguments(t *testing.T) { commandName := "" commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandName = name commandArgs = args }) diff --git a/plugin/studio/studio_plugin_test.go b/plugin/studio/studio_plugin_test.go index 2b92d24..3733155 100644 --- a/plugin/studio/studio_plugin_test.go +++ b/plugin/studio/studio_plugin_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/UiPath/uipathcli/test" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/process" ) const studioDefinition = ` @@ -85,7 +85,7 @@ func TestInvalidOutputTypeShowsValidationError(t *testing.T) { } func TestFailedPackagingReturnsFailureStatus(t *testing.T) { - exec := utils.NewExecCustomProcess(1, "Build output", "There was an error", func(name string, args []string) {}) + exec := process.NewExecCustomProcess(1, "Build output", "There was an error", func(name string, args []string) {}) context := test.NewContextBuilder(). WithDefinition("studio", studioDefinition). WithCommandPlugin(PackagePackCommand{exec}). @@ -152,7 +152,7 @@ func TestPackCrossPlatformSuccessfully(t *testing.T) { func TestPackWithAutoVersionArgument(t *testing.T) { commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandArgs = args }) context := test.NewContextBuilder(). @@ -171,7 +171,7 @@ func TestPackWithAutoVersionArgument(t *testing.T) { func TestPackWithOutputTypeArgument(t *testing.T) { commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandArgs = args }) context := test.NewContextBuilder(). @@ -190,7 +190,7 @@ func TestPackWithOutputTypeArgument(t *testing.T) { func TestPackWithSplitOutputArgument(t *testing.T) { commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandArgs = args }) context := test.NewContextBuilder(). @@ -209,7 +209,7 @@ func TestPackWithSplitOutputArgument(t *testing.T) { func TestPackWithReleaseNotesArgument(t *testing.T) { commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandArgs = args }) context := test.NewContextBuilder(). @@ -298,7 +298,7 @@ func TestAnalyzeCrossPlatformSuccessfully(t *testing.T) { } func TestFailedAnalyzeReturnsFailureStatus(t *testing.T) { - exec := utils.NewExecCustomProcess(1, "Analyze output", "There was an error", func(name string, args []string) {}) + exec := process.NewExecCustomProcess(1, "Analyze output", "There was an error", func(name string, args []string) {}) context := test.NewContextBuilder(). WithDefinition("studio", studioDefinition). WithCommandPlugin(PackageAnalyzeCommand{exec}). @@ -322,7 +322,7 @@ func TestFailedAnalyzeReturnsFailureStatus(t *testing.T) { func TestAnalyzeWithTreatWarningsAsErrorsArgument(t *testing.T) { commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandArgs = args }) context := test.NewContextBuilder(). @@ -340,7 +340,7 @@ func TestAnalyzeWithTreatWarningsAsErrorsArgument(t *testing.T) { func TestAnalyzeWithStopOnRuleViolationArgument(t *testing.T) { commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandArgs = args }) context := test.NewContextBuilder(). diff --git a/plugin/studio/studio_plugin_windows_test.go b/plugin/studio/studio_plugin_windows_test.go index f9dcdad..1ab526c 100644 --- a/plugin/studio/studio_plugin_windows_test.go +++ b/plugin/studio/studio_plugin_windows_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/UiPath/uipathcli/test" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/process" ) func TestPackWindowsSuccessfully(t *testing.T) { @@ -59,7 +59,7 @@ func TestPackWindowsSuccessfully(t *testing.T) { func TestPackOnWindowsWithCorrectArguments(t *testing.T) { commandName := "" commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandName = name commandArgs = args }) @@ -121,7 +121,7 @@ func TestAnalyzeWindowsSuccessfully(t *testing.T) { func TestAnalyzeOnWindowsWithCorrectArguments(t *testing.T) { commandName := "" commandArgs := []string{} - exec := utils.NewExecCustomProcess(0, "", "", func(name string, args []string) { + exec := process.NewExecCustomProcess(0, "", "", func(name string, args []string) { commandName = name commandArgs = args }) diff --git a/plugin/studio/uipcli.go b/plugin/studio/uipcli.go index 04ddde9..75ee79a 100644 --- a/plugin/studio/uipcli.go +++ b/plugin/studio/uipcli.go @@ -8,7 +8,7 @@ import ( "github.com/UiPath/uipathcli/log" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/process" ) const uipcliVersion = "24.12.9111.31003" @@ -18,18 +18,18 @@ const uipcliWindowsVersion = "24.12.9111.31003" const uipcliWindowsUrl = "https://uipath.pkgs.visualstudio.com/Public.Feeds/_apis/packaging/feeds/1c781268-d43d-45ab-9dfc-0151a1c740b7/nuget/packages/UiPath.CLI.Windows/versions/" + uipcliWindowsVersion + "/content" type uipcli struct { - Exec utils.ExecProcess + Exec process.ExecProcess Logger log.Logger } -func (c uipcli) Execute(targetFramework TargetFramework, args ...string) (utils.ExecCmd, error) { +func (c uipcli) Execute(targetFramework TargetFramework, args ...string) (process.ExecCmd, error) { if targetFramework == TargetFrameworkWindows { return c.execute("uipcli-win", uipcliWindowsUrl, args) } return c.execute("uipcli", uipcliUrl, args) } -func (c uipcli) execute(name string, url string, args []string) (utils.ExecCmd, error) { +func (c uipcli) execute(name string, url string, args []string) (process.ExecCmd, error) { uipcliPath, err := c.getPath(name, url) if err != nil { return nil, err @@ -61,6 +61,6 @@ func (c uipcli) isWindows() bool { return runtime.GOOS == "windows" } -func newUipcli(exec utils.ExecProcess, logger log.Logger) *uipcli { +func newUipcli(exec process.ExecProcess, logger log.Logger) *uipcli { return &uipcli{exec, logger} } diff --git a/test/setup.go b/test/setup.go index 2fdf2b8..c7c466b 100644 --- a/test/setup.go +++ b/test/setup.go @@ -18,7 +18,7 @@ import ( "github.com/UiPath/uipathcli/executor" "github.com/UiPath/uipathcli/parser" "github.com/UiPath/uipathcli/plugin" - "github.com/UiPath/uipathcli/utils" + "github.com/UiPath/uipathcli/utils/stream" ) type ContextBuilder struct { @@ -212,9 +212,9 @@ func RunCli(args []string, context Context) Result { executor.NewPluginExecutor(authenticators), ) args = append([]string{"uipath"}, args...) - var input utils.Stream + var input stream.Stream if context.StdIn != nil { - input = utils.NewMemoryStream(parser.RawBodyParameterName, context.StdIn.Bytes()) + input = stream.NewMemoryStream(parser.RawBodyParameterName, context.StdIn.Bytes()) } err := cli.Run(args, input) diff --git a/utils/converter/query_string_builder.go b/utils/converter/query_string_builder.go new file mode 100644 index 0000000..a31fdd6 --- /dev/null +++ b/utils/converter/query_string_builder.go @@ -0,0 +1,97 @@ +package converter + +import ( + "fmt" + "net/url" + "strings" +) + +// QueryStringBuilder converts a list of parameters into a query string. +// +// Depending on the type of the parameter, the formatter converts the value to +// the proper format and makes sure the query string is properly escaped. +// +// Example: +// - parameter 'username' and value 'tschmitt' +// - parameter 'message' and value 'Hello World' +// --> username=tschmitt&message=Hello+World +type QueryStringBuilder struct { + querystring string +} + +func (f *QueryStringBuilder) Add(name string, value interface{}) { + param := f.formatQueryStringParam(name, value) + if f.querystring == "" { + f.querystring = param + } else { + f.querystring = f.querystring + "&" + param + } +} + +func (f QueryStringBuilder) Build() string { + return f.querystring +} + +func (f QueryStringBuilder) formatQueryStringParam(name string, value interface{}) string { + switch value := value.(type) { + case []int: + return f.integerArrayToQueryString(name, value) + case []float64: + return f.numberArrayToQueryString(name, value) + case []bool: + return f.booleanArrayToQueryString(name, value) + case []string: + return f.stringArrayToQueryString(name, value) + default: + return f.toQueryString(name, value) + } +} + +func (f QueryStringBuilder) integerArrayToQueryString(key string, value []int) string { + result := make([]interface{}, len(value)) + for i, v := range value { + result[i] = v + } + return f.arrayToQueryString(key, result) +} + +func (f QueryStringBuilder) numberArrayToQueryString(key string, value []float64) string { + result := make([]interface{}, len(value)) + for i, v := range value { + result[i] = v + } + return f.arrayToQueryString(key, result) +} + +func (f QueryStringBuilder) booleanArrayToQueryString(key string, value []bool) string { + result := make([]interface{}, len(value)) + for i, v := range value { + result[i] = v + } + return f.arrayToQueryString(key, result) +} + +func (f QueryStringBuilder) stringArrayToQueryString(key string, value []string) string { + result := make([]interface{}, len(value)) + for i, v := range value { + result[i] = v + } + return f.arrayToQueryString(key, result) +} + +func (f QueryStringBuilder) arrayToQueryString(key string, value []interface{}) string { + result := make([]string, len(value)) + for i, v := range value { + result[i] = f.toQueryString(key, v) + } + return strings.Join(result, "&") +} + +func (f QueryStringBuilder) toQueryString(key string, value interface{}) string { + stringValue := fmt.Sprintf("%v", value) + return fmt.Sprintf("%s=%v", key, url.QueryEscape(stringValue)) +} + +func NewQueryStringBuilder() *QueryStringBuilder { + return &QueryStringBuilder{} +} diff --git a/utils/converter/string_converter.go b/utils/converter/string_converter.go new file mode 100644 index 0000000..199696e --- /dev/null +++ b/utils/converter/string_converter.go @@ -0,0 +1,46 @@ +package converter + +import ( + "fmt" + "strings" +) + +// StringConverter converts interface{} values into a string. +// Depending on the type of the parameter, the formatter converts the value to +// the proper format: +// - Integers, Float, etc.. are simply converted to a string +// - Arrays are formatted comma-separated +// - Booleans are converted to true or false +type StringConverter struct{} + +func (c StringConverter) ToString(value interface{}) string { + return c.formatParameter(value) +} + +func (c StringConverter) formatParameter(value interface{}) string { + switch value := value.(type) { + case []int: + return c.arrayToCommaSeparatedString(value) + case []float64: + return c.arrayToCommaSeparatedString(value) + case []bool: + return c.arrayToCommaSeparatedString(value) + case []string: + return c.arrayToCommaSeparatedString(value) + default: + return fmt.Sprintf("%v", value) + } +} + +func (c StringConverter) arrayToCommaSeparatedString(array interface{}) string { + switch value := array.(type) { + case []string: + return strings.Join(value, ",") + default: + return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(value)), ","), "[]") + } +} + +func NewStringConverter() *StringConverter { + return &StringConverter{} +} diff --git a/utils/converter/uri_builder.go b/utils/converter/uri_builder.go new file mode 100644 index 0000000..d81a8f3 --- /dev/null +++ b/utils/converter/uri_builder.go @@ -0,0 +1,51 @@ +package converter + +import ( + "fmt" + "net/url" + "path" + "strings" +) + +// UriBuilder takes an Uri and formats it with parameter values. +// +// The builder supports replacing path placeholders like organization and tenant: +// https://cloud.uipath.com/{organization} +// with parameter 'organization' and value 'my-org' +// --> https://cloud.uipath.com/my-org +// +// The builder also supports adding query strings to the uri: +// https://cloud.uipath.com/users +// with parameter 'firstName' and value 'Thomas' +// and parameter 'lastName' and value 'Schmitt' +// --> https://cloud.uipath.com/users?firstName=Thomas&lastName=Schmitt +type UriBuilder struct { + uri string + converter *StringConverter + queryStringBuilder *QueryStringBuilder +} + +func (f *UriBuilder) FormatPath(name string, value interface{}) { + valueString := f.converter.ToString(value) + f.uri = strings.ReplaceAll(f.uri, "{"+name+"}", valueString) +} + +func (f *UriBuilder) AddQueryString(name string, value interface{}) { + f.queryStringBuilder.Add(name, value) +} + +func (f UriBuilder) Build() string { + queryString := f.queryStringBuilder.Build() + if queryString == "" { + return f.uri + } + return f.uri + "?" + queryString +} + +func NewUriBuilder(baseUri url.URL, route string) *UriBuilder { + normalizedPath := strings.Trim(baseUri.Path, "/") + normalizedRoute := strings.Trim(route, "/") + path := path.Join(normalizedPath, normalizedRoute) + uri := fmt.Sprintf("%s://%s/%s", baseUri.Scheme, baseUri.Host, path) + return &UriBuilder{uri, NewStringConverter(), NewQueryStringBuilder()} +} diff --git a/executor/uri_formatter_test.go b/utils/converter/uri_builder_test.go similarity index 67% rename from executor/uri_formatter_test.go rename to utils/converter/uri_builder_test.go index c044a95..e6a60ae 100644 --- a/executor/uri_formatter_test.go +++ b/utils/converter/uri_builder_test.go @@ -1,4 +1,4 @@ -package executor +package converter import ( "net/url" @@ -6,41 +6,41 @@ import ( ) func TestRemovesTrailingSlash(t *testing.T) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com/"), "/my-service") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com/"), "/my-service") - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/my-service" { t.Errorf("Did not remove trailing slash, got: %v", uri) } } func TestAddsMissingSlashSeparator(t *testing.T) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com"), "my-service") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com"), "my-service") - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/my-service" { t.Errorf("Did not add missing slash separator, got: %v", uri) } } func TestFormatPathReplacesPlaceholder(t *testing.T) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com"), "/{organization}/{tenant}/my-service") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com"), "/{organization}/{tenant}/my-service") - formatter.FormatPath(*NewExecutionParameter("organization", "my-org", "path")) + builder.FormatPath("organization", "my-org") - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/my-org/{tenant}/my-service" { t.Errorf("Did not replace placeholder, got: %v", uri) } } func TestFormatPathReplacesMultiplePlaceholders(t *testing.T) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com"), "/{organization}/{tenant}/my-service") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com"), "/{organization}/{tenant}/my-service") - formatter.FormatPath(*NewExecutionParameter("organization", "my-org", "path")) - formatter.FormatPath(*NewExecutionParameter("tenant", "my-tenant", "path")) + builder.FormatPath("organization", "my-org") + builder.FormatPath("tenant", "my-tenant") - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/my-org/my-tenant/my-service" { t.Errorf("Did not replace placeholder, got: %v", uri) } @@ -65,40 +65,34 @@ func TestFormatPathDataTypes(t *testing.T) { }) } func FormatPathDataTypes(t *testing.T, value interface{}, expected string) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com"), "/{param}") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com"), "/{param}") - formatter.FormatPath(*NewExecutionParameter("param", value, "path")) + builder.FormatPath("param", value) - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/"+expected { t.Errorf("Did not format data type properly, got: %v", uri) } } func TestAddQueryString(t *testing.T) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com"), "/my-service") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com"), "/my-service") - parameters := []ExecutionParameter{ - *NewExecutionParameter("filter", "my-value", "query"), - } - formatter.AddQueryString(parameters) + builder.AddQueryString("filter", "my-value") - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/my-service?filter=my-value" { t.Errorf("Did not add querystring, got: %v", uri) } } func TestAddMultipleQueryStringParameters(t *testing.T) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com"), "/my-service") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com"), "/my-service") - parameters := []ExecutionParameter{ - *NewExecutionParameter("skip", 1, "query"), - *NewExecutionParameter("take", 5, "query"), - } - formatter.AddQueryString(parameters) + builder.AddQueryString("skip", 1) + builder.AddQueryString("take", 5) - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/my-service?skip=1&take=5" { t.Errorf("Did not add querystring, got: %v", uri) } @@ -123,14 +117,11 @@ func TestQueryStringDataTypes(t *testing.T) { }) } func QueryStringDataTypes(t *testing.T, value interface{}, expected string) { - formatter := newUriFormatter(toUrl("https://cloud.uipath.com"), "/my-service") + builder := NewUriBuilder(toUrl("https://cloud.uipath.com"), "/my-service") - parameters := []ExecutionParameter{ - *NewExecutionParameter("param", value, "query"), - } - formatter.AddQueryString(parameters) + builder.AddQueryString("param", value) - uri := formatter.Uri() + uri := builder.Build() if uri != "https://cloud.uipath.com/my-service"+expected { t.Errorf("Did not format data type for query string properly, got: %v", uri) } diff --git a/utils/directories.go b/utils/directories/directories.go similarity index 51% rename from utils/directories.go rename to utils/directories/directories.go index b57caf6..2e98705 100644 --- a/utils/directories.go +++ b/utils/directories/directories.go @@ -1,4 +1,4 @@ -package utils +package directories import ( "os" @@ -7,23 +7,20 @@ import ( const directoryPermissions = 0700 -type Directories struct { +func Temp() (string, error) { + return userDirectory("tmp") } -func (d Directories) Temp() (string, error) { - return d.userDirectory("tmp") +func Cache() (string, error) { + return userDirectory("cache") } -func (d Directories) Cache() (string, error) { - return d.userDirectory("cache") +func Plugin() (string, error) { + return userDirectory("plugins") } -func (d Directories) Plugin() (string, error) { - return d.userDirectory("plugins") -} - -func (d Directories) userDirectory(name string) (string, error) { - userDirectory, err := d.baseUserDirectory() +func userDirectory(name string) (string, error) { + userDirectory, err := baseUserDirectory() if err != nil { return "", err } @@ -32,7 +29,7 @@ func (d Directories) userDirectory(name string) (string, error) { return directory, nil } -func (d Directories) baseUserDirectory() (string, error) { +func baseUserDirectory() (string, error) { userCacheDirectory, err := os.UserCacheDir() if err != nil { return "", err diff --git a/utils/exec_cmd.go b/utils/process/exec_cmd.go similarity index 90% rename from utils/exec_cmd.go rename to utils/process/exec_cmd.go index 5fa7344..8b60234 100644 --- a/utils/exec_cmd.go +++ b/utils/process/exec_cmd.go @@ -1,4 +1,4 @@ -package utils +package process import "io" diff --git a/utils/exec_custom_cmd.go b/utils/process/exec_custom_cmd.go similarity index 97% rename from utils/exec_custom_cmd.go rename to utils/process/exec_custom_cmd.go index 35643b1..43b48bd 100644 --- a/utils/exec_custom_cmd.go +++ b/utils/process/exec_custom_cmd.go @@ -1,4 +1,4 @@ -package utils +package process import ( "io" diff --git a/utils/exec_custom_process.go b/utils/process/exec_custom_process.go similarity index 97% rename from utils/exec_custom_process.go rename to utils/process/exec_custom_process.go index 18a4c96..0c7766c 100644 --- a/utils/exec_custom_process.go +++ b/utils/process/exec_custom_process.go @@ -1,4 +1,4 @@ -package utils +package process import ( "io" diff --git a/utils/exec_default_cmd.go b/utils/process/exec_default_cmd.go similarity index 96% rename from utils/exec_default_cmd.go rename to utils/process/exec_default_cmd.go index 9aa6ffd..51791d1 100644 --- a/utils/exec_default_cmd.go +++ b/utils/process/exec_default_cmd.go @@ -1,4 +1,4 @@ -package utils +package process import ( "io" diff --git a/utils/exec_default_process.go b/utils/process/exec_default_process.go similarity index 94% rename from utils/exec_default_process.go rename to utils/process/exec_default_process.go index f8382e3..76d1253 100644 --- a/utils/exec_default_process.go +++ b/utils/process/exec_default_process.go @@ -1,4 +1,4 @@ -package utils +package process import "os/exec" diff --git a/utils/exec_process.go b/utils/process/exec_process.go similarity index 82% rename from utils/exec_process.go rename to utils/process/exec_process.go index dc69d03..4ffd402 100644 --- a/utils/exec_process.go +++ b/utils/process/exec_process.go @@ -1,4 +1,4 @@ -package utils +package process type ExecProcess interface { Command(name string, args ...string) ExecCmd diff --git a/utils/retry.go b/utils/resiliency/retry.go similarity index 95% rename from utils/retry.go rename to utils/resiliency/retry.go index 112507a..9fd9ec9 100644 --- a/utils/retry.go +++ b/utils/resiliency/retry.go @@ -1,6 +1,6 @@ // Package utils contains command functionality which is reused across // multiple other packages. -package utils +package resiliency import "time" diff --git a/utils/retryable_error.go b/utils/resiliency/retryable_error.go similarity index 94% rename from utils/retryable_error.go rename to utils/resiliency/retryable_error.go index 563c01c..97a6d37 100644 --- a/utils/retryable_error.go +++ b/utils/resiliency/retryable_error.go @@ -1,4 +1,4 @@ -package utils +package resiliency // RetryableError can be returned inside of the Retry() block to indicate // that the function failed and should be retried. diff --git a/utils/file_stream.go b/utils/stream/file_stream.go similarity index 98% rename from utils/file_stream.go rename to utils/stream/file_stream.go index 40131ef..04d584a 100644 --- a/utils/file_stream.go +++ b/utils/stream/file_stream.go @@ -1,4 +1,4 @@ -package utils +package stream import ( "errors" diff --git a/utils/file_stream_test.go b/utils/stream/file_stream_test.go similarity index 99% rename from utils/file_stream_test.go rename to utils/stream/file_stream_test.go index 9477c2b..11a5e20 100644 --- a/utils/file_stream_test.go +++ b/utils/stream/file_stream_test.go @@ -1,4 +1,4 @@ -package utils +package stream import ( "io" diff --git a/utils/memory_stream.go b/utils/stream/memory_stream.go similarity index 97% rename from utils/memory_stream.go rename to utils/stream/memory_stream.go index e567fc1..8554a58 100644 --- a/utils/memory_stream.go +++ b/utils/stream/memory_stream.go @@ -1,4 +1,4 @@ -package utils +package stream import ( "bytes" diff --git a/utils/memory_stream_test.go b/utils/stream/memory_stream_test.go similarity index 98% rename from utils/memory_stream_test.go rename to utils/stream/memory_stream_test.go index 78c0f5b..787546e 100644 --- a/utils/memory_stream_test.go +++ b/utils/stream/memory_stream_test.go @@ -1,4 +1,4 @@ -package utils +package stream import ( "io" diff --git a/utils/reader_stream.go b/utils/stream/reader_stream.go similarity index 97% rename from utils/reader_stream.go rename to utils/stream/reader_stream.go index c180b84..b45cde2 100644 --- a/utils/reader_stream.go +++ b/utils/stream/reader_stream.go @@ -1,4 +1,4 @@ -package utils +package stream import ( "io" diff --git a/utils/reader_stream_test.go b/utils/stream/reader_stream_test.go similarity index 98% rename from utils/reader_stream_test.go rename to utils/stream/reader_stream_test.go index 377cbee..003207a 100644 --- a/utils/reader_stream_test.go +++ b/utils/stream/reader_stream_test.go @@ -1,4 +1,4 @@ -package utils +package stream import ( "io" diff --git a/utils/stream.go b/utils/stream/stream.go similarity index 95% rename from utils/stream.go rename to utils/stream/stream.go index 7fd0703..20dd36e 100644 --- a/utils/stream.go +++ b/utils/stream/stream.go @@ -1,4 +1,4 @@ -package utils +package stream import ( "io" diff --git a/utils/progress.go b/utils/visualization/progress.go similarity index 94% rename from utils/progress.go rename to utils/visualization/progress.go index bb72f78..7bf28a9 100644 --- a/utils/progress.go +++ b/utils/visualization/progress.go @@ -1,4 +1,4 @@ -package utils +package visualization // The Progress structure contains statistics about how many bytes // have been read from the underlying reader. diff --git a/utils/progress_bar.go b/utils/visualization/progress_bar.go similarity index 99% rename from utils/progress_bar.go rename to utils/visualization/progress_bar.go index eb81c82..967a943 100644 --- a/utils/progress_bar.go +++ b/utils/visualization/progress_bar.go @@ -1,4 +1,4 @@ -package utils +package visualization import ( "fmt" diff --git a/utils/progress_bar_test.go b/utils/visualization/progress_bar_test.go similarity index 99% rename from utils/progress_bar_test.go rename to utils/visualization/progress_bar_test.go index 3f670d4..22c6260 100644 --- a/utils/progress_bar_test.go +++ b/utils/visualization/progress_bar_test.go @@ -1,4 +1,4 @@ -package utils +package visualization import ( "bytes" diff --git a/utils/progress_reader.go b/utils/visualization/progress_reader.go similarity index 98% rename from utils/progress_reader.go rename to utils/visualization/progress_reader.go index 4738c3f..eb2c559 100644 --- a/utils/progress_reader.go +++ b/utils/visualization/progress_reader.go @@ -1,4 +1,4 @@ -package utils +package visualization import ( "io"