github.com/0xsequence/ethkit@v1.25.0/cmd/ethkit/print.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "reflect" 10 "strconv" 11 "strings" 12 "text/tabwriter" 13 ) 14 15 type printableFormat struct { 16 minwidth int 17 tabwidth int 18 padding int 19 padchar byte 20 } 21 22 // NewPrintableFormat returns a customized configuration format 23 func NewPrintableFormat(minwidth, tabwidth, padding int, padchar byte) *printableFormat { 24 return &printableFormat{minwidth, tabwidth, padding, padchar} 25 } 26 27 // Printable is a generic key-value (map) structure that could contain nested objects. 28 type Printable map[string]any 29 30 // PrettyJSON prints an object in "prettified" JSON format 31 func PrettyJSON(toJSON any) (*string, error) { 32 b, err := json.MarshalIndent(toJSON, "", " ") 33 if err != nil { 34 return nil, err 35 } 36 jsonString := string(b) 37 38 // remove the trailing newline character ("%") 39 if jsonString[len(jsonString)-1] == '\n' { 40 jsonString = jsonString[:len(jsonString)-1] 41 } 42 43 return &jsonString, nil 44 } 45 46 // FromStruct converts a struct into a Printable using, when available, JSON field names as keys 47 func (p *Printable) FromStruct(input any) error { 48 bytes, err := json.Marshal(input) 49 if err != nil { 50 return err 51 } 52 if err := json.Unmarshal(bytes, &p); err != nil { 53 return err 54 } 55 56 return nil 57 } 58 59 // Columnize returns a formatted-in-columns (vertically aligned) string based on a provided configuration. 60 func (p *Printable) Columnize(pf printableFormat) string { 61 var buf bytes.Buffer 62 w := tabwriter.NewWriter(&buf, pf.minwidth, pf.tabwidth, pf.padding, pf.padchar, tabwriter.Debug) 63 // NOTE: Order is not maintained whilst looping over map's . Results from different execution may differ. 64 for k, v := range *p { 65 printKeyValue(w, k, v) 66 } 67 w.Flush() 68 69 return buf.String() 70 } 71 72 func printKeyValue(w *tabwriter.Writer, key string, value any) { 73 switch t := value.(type) { 74 // NOTE: Printable is not directly inferred as map[string]any therefore explicit reference is necessary 75 case map[string]any: 76 fmt.Fprintln(w, key, "\t") 77 for tk, tv := range t { 78 printKeyValue(w, "\t "+tk, tv) 79 } 80 case []any: 81 fmt.Fprintln(w, key, "\t") 82 for _, elem := range t { 83 elemMap, ok := elem.(map[string]any) 84 if ok { 85 for tk, tv := range elemMap { 86 printKeyValue(w, "\t "+tk, tv) 87 } 88 fmt.Fprintln(w, "\t", "\t") 89 } else { 90 fmt.Fprintln(w, "\t", customFormat(elem)) 91 } 92 } 93 default: 94 // custom format for numbers to avoid scientific notation 95 fmt.Fprintf(w, "%s\t %s\n", key, customFormat(value)) 96 } 97 } 98 99 func customFormat(value any) string { 100 switch v := value.(type) { 101 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: 102 return fmt.Sprintf("%d", v) 103 case float32, float64: 104 return formatFloat(v) 105 default: 106 return fmt.Sprintf("%v", base64ToHex(value)) 107 } 108 } 109 110 func formatFloat(f any) string { 111 str := fmt.Sprintf("%v", f) 112 if strings.ContainsAny(str, "eE.") { 113 if floatValue, err := strconv.ParseFloat(str, 64); err == nil { 114 return strconv.FormatFloat(floatValue, 'f', -1, 64) 115 } 116 } 117 118 return str 119 } 120 121 func base64ToHex(str any) any { 122 _, ok := str.(string); if !ok { 123 return str 124 } 125 decoded, err := base64.StdEncoding.DecodeString(str.(string)) 126 if err != nil { 127 return str 128 } 129 130 return "0x" + hex.EncodeToString(decoded) 131 } 132 133 // GetValueByJSONTag returns the value of a struct field matching a JSON tag provided in input. 134 func GetValueByJSONTag(input any, jsonTag string) any { 135 // TODO: Refactor to support both nil values and errors when key not found 136 return findField(reflect.ValueOf(input), jsonTag) 137 } 138 139 func findField(val reflect.Value, jsonTag string) any { 140 seen := make(map[uintptr]bool) 141 142 // take the value the pointer val points to 143 if val.Kind() == reflect.Ptr { 144 val = val.Elem() 145 } 146 147 // return if the element is not a struct 148 if val.Kind() != reflect.Struct { 149 return nil 150 } 151 152 // check if the struct has already been processed to avoid infinite recursion 153 if val.CanAddr() { 154 ptr := val.Addr().Pointer() 155 if seen[ptr] { 156 return nil 157 } 158 seen[ptr] = true 159 } 160 161 t := val.Type() 162 163 for i := 0; i < val.NumField(); i++ { 164 field := t.Field(i) 165 tag := field.Tag.Get("json") 166 167 fieldValue := val.Field(i) 168 if fieldValue.Kind() == reflect.Struct { 169 // recursively process fields including embedded ones 170 return findField(fieldValue, jsonTag) 171 } else { 172 if strings.EqualFold(strings.ToLower(tag), strings.ToLower(jsonTag)) { 173 return val.Field(i).Interface() 174 } 175 } 176 } 177 178 return nil 179 }