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..17606e6 100644 --- a/cmd/output.go +++ b/cmd/output.go @@ -206,51 +206,67 @@ 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 (filter == nil || len(filter) == 0) && (excludeFilter == nil || len(excludeFilter) == 0) { return response } + + excludeSet := make(map[string]struct{}, len(excludeFilter)) + for _, key := range excludeFilter { + excludeSet[key] = struct{}{} + } + + 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 { + 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: + 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 })