You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by pe...@apache.org on 2025/04/03 13:23:35 UTC

(cloudstack-cloudmonkey) 01/01: Add support for excluding a list of parameters from an API response

This is an automated email from the ASF dual-hosted git repository.

pearl11594 pushed a commit to branch support-response-exlude-list-filtering
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git

commit bdfbeb36c50025e3a560683e3eabafd6ec54dca7
Author: Pearl Dsilva <pe...@gmail.com>
AuthorDate: Thu Apr 3 09:23:20 2025 -0400

    Add support for excluding a list of parameters from an API response
---
 cli/completer.go | 17 ++++++++++++++
 cmd/api.go       | 15 ++++++++++--
 cmd/output.go    | 69 ++++++++++++++++++++++++++++++++++++--------------------
 config/cache.go  |  7 ++++++
 4 files changed, 82 insertions(+), 26 deletions(-)

diff --git a/cli/completer.go b/cli/completer.go
index be1ba21..53de5fb 100644
--- a/cli/completer.go
+++ b/cli/completer.go
@@ -357,6 +357,7 @@ func (t *autoCompleter) Do(line []rune, pos int) (options [][]rune, offset int)
 				}
 				return
 			}
+
 			if arg.Type == config.FAKE && arg.Name == "filter=" {
 				offset = 0
 				filterInputs := strings.Split(strings.Replace(argInput, ",", ",|", -1), "|")
@@ -373,6 +374,22 @@ func (t *autoCompleter) Do(line []rune, pos int) (options [][]rune, offset int)
 				return
 			}
 
+			if arg.Type == config.FAKE && arg.Name == "exclude=" {
+				offset = 0
+				excludeFilterInputs := strings.Split(strings.Replace(argInput, ",", ",|", -1), "|")
+				lastExcludeFilterInput := lastString(excludeFilterInputs)
+				for _, key := range apiFound.ResponseKeys {
+					if inArray(key, excludeFilterInputs) {
+						continue
+					}
+					if strings.HasPrefix(key, lastExcludeFilterInput) {
+						options = append(options, []rune(key[len(lastExcludeFilterInput):]))
+						offset = len(lastExcludeFilterInput)
+					}
+				}
+				return
+			}
+
 			autocompleteAPI := findAutocompleteAPI(arg, apiFound, apiMap)
 			if autocompleteAPI == nil {
 				return nil, 0
diff --git a/cmd/api.go b/cmd/api.go
index 058c6fb..872a328 100644
--- a/cmd/api.go
+++ b/cmd/api.go
@@ -82,7 +82,7 @@ func init() {
 				if strings.HasSuffix(err.Error(), "context canceled") {
 					return nil
 				} else if response != nil {
-					printResult(r.Config.Core.Output, response, nil)
+					printResult(r.Config.Core.Output, response, nil, nil)
 				}
 				return err
 			}
@@ -98,8 +98,19 @@ func init() {
 				}
 			}
 
+			var excludeKeys []string
+			for _, arg := range apiArgs {
+				if strings.HasPrefix(arg, "exclude=") {
+					for _, excludeKey := range strings.Split(strings.Split(arg, "=")[1], ",") {
+						if len(strings.TrimSpace(excludeKey)) > 0 {
+							excludeKeys = append(excludeKeys, strings.TrimSpace(excludeKey))
+						}
+					}
+				}
+			}
+
 			if len(response) > 0 {
-				printResult(r.Config.Core.Output, response, filterKeys)
+				printResult(r.Config.Core.Output, response, filterKeys, excludeKeys)
 			}
 
 			return nil
diff --git a/cmd/output.go b/cmd/output.go
index 08004cf..f45e6d8 100644
--- a/cmd/output.go
+++ b/cmd/output.go
@@ -206,51 +206,72 @@ func printCsv(response map[string]interface{}, filter []string) {
 	enc.Flush()
 }
 
-func filterResponse(response map[string]interface{}, filter []string, outputType string) map[string]interface{} {
-	if filter == nil || len(filter) == 0 {
+func filterResponse(response map[string]interface{}, filter []string, excludeFilter []string, outputType string) map[string]interface{} {
+	// If no filtering is required, return the original response.
+	if len(filter) == 0 && len(excludeFilter) == 0 {
 		return response
 	}
+
+	// Create a set for excludeFilter to improve lookup performance.
+	excludeSet := make(map[string]struct{}, len(excludeFilter))
+	for _, key := range excludeFilter {
+		excludeSet[key] = struct{}{}
+	}
+
+	// Create a set for filter keys for fast lookup.
+	filterSet := make(map[string]struct{}, len(filter))
+	for _, key := range filter {
+		filterSet[key] = struct{}{}
+	}
+
 	filteredResponse := make(map[string]interface{})
-	for k, v := range response {
-		valueType := reflect.TypeOf(v)
-		if valueType.Kind() == reflect.Slice || valueType.Kind() == reflect.Map {
-			items, ok := v.([]interface{})
-			if !ok {
-				continue
-			}
+
+	for key, value := range response {
+		// Process only slice/map values
+		switch items := value.(type) {
+		case []interface{}:
 			var filteredRows []interface{}
 			for _, item := range items {
 				row, ok := item.(map[string]interface{})
-				if !ok || len(row) < 1 {
+				if !ok || len(row) == 0 {
 					continue
 				}
+
 				filteredRow := make(map[string]interface{})
-				for _, filterKey := range filter {
-					for field := range row {
-						if filterKey == field {
-							filteredRow[field] = row[field]
+
+				if len(filter) > 0 {
+					// Include only keys that exist in filterSet
+					for filterKey := range filterSet {
+						if val, exists := row[filterKey]; exists {
+							filteredRow[filterKey] = val
+						} else if outputType == config.COLUMN || outputType == config.CSV || outputType == config.TABLE {
+							filteredRow[filterKey] = "" // Ensure all filter keys exist in row
 						}
 					}
-					if outputType == config.COLUMN || outputType == config.CSV || outputType == config.TABLE {
-						if _, ok := filteredRow[filterKey]; !ok {
-							filteredRow[filterKey] = ""
+				} else {
+					// Exclude keys from excludeFilter
+					for field, val := range row {
+						if _, excluded := excludeSet[field]; !excluded {
+							filteredRow[field] = val
 						}
 					}
 				}
+
 				filteredRows = append(filteredRows, filteredRow)
 			}
-			filteredResponse[k] = filteredRows
-		} else {
-			filteredResponse[k] = v
-			continue
-		}
+			filteredResponse[key] = filteredRows
 
+		default:
+			// If it's not a slice, copy as is
+			filteredResponse[key] = value
+		}
 	}
+
 	return filteredResponse
 }
 
-func printResult(outputType string, response map[string]interface{}, filter []string) {
-	response = filterResponse(response, filter, outputType)
+func printResult(outputType string, response map[string]interface{}, filter []string, excludeFilter []string) {
+	response = filterResponse(response, filter, excludeFilter, outputType)
 	switch outputType {
 	case config.JSON:
 		printJSON(response)
diff --git a/config/cache.go b/config/cache.go
index 096582d..13596dd 100644
--- a/config/cache.go
+++ b/config/cache.go
@@ -151,6 +151,13 @@ func (c *Config) UpdateCache(response map[string]interface{}) interface{} {
 			Description: "cloudmonkey specific response key filtering",
 		})
 
+		// Add exclude arg
+		apiArgs = append(apiArgs, &APIArg{
+			Name:        "exclude=",
+			Type:        FAKE,
+			Description: "cloudmonkey specific response key to exlude when filtering",
+		})
+
 		sort.Slice(apiArgs, func(i, j int) bool {
 			return apiArgs[i].Name < apiArgs[j].Name
 		})