openapi/output_filter.go (202 lines of code) (raw):

// Copyright (c) 2009-present, Alibaba Cloud All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openapi import ( "bufio" "bytes" "encoding/json" "fmt" "reflect" "strconv" "strings" "text/tabwriter" "github.com/aliyun/aliyun-cli/v3/cli" "github.com/aliyun/aliyun-cli/v3/i18n" jmespath "github.com/jmespath/go-jmespath" ) func NewOutputFlag() *cli.Flag { return &cli.Flag{ Name: OutputFlagName, Shorthand: 'o', AssignedMode: cli.AssignedRepeatable, Short: i18n.T( "use `--output cols=Field1,Field2 [rows=jmesPath]` to print output as table", "使用 `--output cols=Field1,Field1 [rows=jmesPath]` 使用表格方式打印输出", ), Long: i18n.T( "", "", ), Fields: []cli.Field{ {Key: "cols", Repeatable: false, Required: true}, {Key: "rows", Repeatable: false, Required: false}, {Key: "num", Repeatable: false, Required: false}, }, } } type OutputFilter interface { FilterOutput(input string) (string, error) } func GetOutputFilter(ctx *cli.Context) OutputFilter { if !OutputFlag(ctx.Flags()).IsAssigned() { return nil } return NewTableOutputFilter(ctx) } type TableOutputFilter struct { ctx *cli.Context } func NewTableOutputFilter(ctx *cli.Context) OutputFilter { return &TableOutputFilter{ctx: ctx} } func (a *TableOutputFilter) FilterOutput(s string) (string, error) { var v interface{} s = fmt.Sprintf("{\"RootFilter\":[%s]}", s) decoder := json.NewDecoder(bytes.NewBufferString(s)) decoder.UseNumber() err := decoder.Decode(&v) if err != nil { return s, fmt.Errorf("unmarshal output failed %s", err) } var rowPath string if v, ok := OutputFlag(a.ctx.Flags()).GetFieldValue("rows"); ok { rowPath = "RootFilter[0]." + v } else { rowPath = "RootFilter" } var colNames []string if v, ok := OutputFlag(a.ctx.Flags()).GetFieldValue("cols"); ok { v = UnquoteString(v) colNames = strings.Split(v, ",") } else { return s, fmt.Errorf("you need to assign col=col1,col2,... with --output") } return a.FormatTable(rowPath, colNames, v) } func isArrayOrSlice(value interface{}) bool { v := reflect.ValueOf(value) return v.Kind() == reflect.Array || v.Kind() == reflect.Slice } func (a *TableOutputFilter) FormatTable(rowPath string, colNames []string, v interface{}) (string, error) { // Add row number if v, ok := OutputFlag(a.ctx.Flags()).GetFieldValue("num"); ok { if v == "true" { colNames = append([]string{"Num"}, colNames...) } } rows, err := jmespath.Search(rowPath, v) if err != nil { return "", fmt.Errorf("jmespath: '%s' failed %s", rowPath, err) } rowsArray, ok := rows.([]interface{}) if !ok { return "", fmt.Errorf("jmespath: '%s' failed Need Array Expr", rowPath) } // delete date type is struct // 1 = object, 2 = array dataType := 1 if len(rowsArray) > 0 { _, ok := rowsArray[0].(map[string]interface{}) if !ok { // check if it is an array if isArrayOrSlice(rowsArray[0]) { dataType = 2 } } } colNamesArray := make([]string, 0) colIndexArray := make([]int, 0) if dataType == 2 { // all colNames must be string:number format for _, colName := range colNames { // Num ignore if colName == "Num" { colNamesArray = append(colNamesArray, colName) continue } if !strings.Contains(colName, ":") { return "", fmt.Errorf("colNames: %s must be string:number format, like 'name:0', 0 is the array index", colName) } // split colName to name and number, must be two parts parts := strings.Split(colName, ":") if len(parts) != 2 { return "", fmt.Errorf("colNames: %s must be string:number format, like 'name:0', 0 is the array index", colName) } // check if number is a number, use regex match if !isNumber(parts[1]) { return "", fmt.Errorf("colNames: %s must be string:number format, like 'name:0', 0 is the array index", colName) } colNamesArray = append(colNamesArray, parts[0]) num, err := strconv.Atoi(parts[1]) if err != nil { return "", fmt.Errorf("colNames: %s must be string:number format, like 'name:0', 0 is the array index", colName) } colIndexArray = append(colIndexArray, num) } } var buf bytes.Buffer writer := bufio.NewWriter(&buf) format := strings.Repeat("%v\t ", len(colNames)-1) + "%v" w := tabwriter.NewWriter(writer, 0, 0, 1, ' ', tabwriter.Debug) if dataType == 1 { fmt.Fprintln(w, fmt.Sprintf(format, toIntfArray(colNames)...)) separator := "" for i, colName := range colNames { separator = separator + strings.Repeat("-", len(colName)) if i < len(colNames)-1 { separator = separator + "\t " } } fmt.Fprintln(w, separator) } else { fmt.Fprintln(w, fmt.Sprintf(format, toIntfArray(colNamesArray)...)) separator := "" for i, colNameArray := range colNamesArray { separator = separator + strings.Repeat("-", len(colNameArray)) if i < len(colNamesArray)-1 { separator = separator + "\t " } } fmt.Fprintln(w, separator) } for i, row := range rowsArray { r := make([]string, 0) var s string var index int if v, ok := OutputFlag(a.ctx.Flags()).GetFieldValue("num"); ok { if v == "true" { s = fmt.Sprintf("%v", i) r = append(r, s) index = 1 } } if dataType == 1 { for _, colName := range colNames[index:] { v, _ := jmespath.Search(colName, row) s = fmt.Sprintf("%v", v) r = append(r, s) } } else { for _, colIndex := range colIndexArray { v, _ := jmespath.Search(fmt.Sprintf("[%d]", colIndex), row) s = fmt.Sprintf("%v", v) r = append(r, s) } } fmt.Fprintln(w, fmt.Sprintf(format, toIntfArray(r)...)) } w.Flush() writer.Flush() return buf.String(), nil } func isNumber(s string) bool { for _, c := range s { if c < '0' || c > '9' { return false } } return true } func toIntfArray(stringArray []string) []interface{} { intfArray := []interface{}{} for _, elem := range stringArray { intfArray = append(intfArray, elem) } return intfArray } func UnquoteString(s string) string { if strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") && len(s) >= 2 { return s[1 : len(s)-1] } return s }