printjson.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package printutils
  15. import (
  16. "fmt"
  17. "os"
  18. "sort"
  19. "strconv"
  20. "strings"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/pkg/prettytable"
  23. "yunion.io/x/pkg/util/sets"
  24. )
  25. type StatusInfo struct {
  26. Status string `json:"status"`
  27. TotalCount int64 `json:"total_count"`
  28. PendingDeletedCount int64 `json:"pending_deleted_count"`
  29. }
  30. type StatusInfoList []StatusInfo
  31. func (a StatusInfoList) Len() int { return len(a) }
  32. func (a StatusInfoList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  33. func (a StatusInfoList) Less(i, j int) bool {
  34. if a[i].TotalCount != a[j].TotalCount {
  35. return a[i].TotalCount > a[j].TotalCount
  36. }
  37. if a[i].PendingDeletedCount != a[j].PendingDeletedCount {
  38. return a[i].PendingDeletedCount > a[j].PendingDeletedCount
  39. }
  40. return a[i].Status < a[j].Status
  41. }
  42. type TotalCountWithStatusInfo struct {
  43. StatusInfo StatusInfoList `json:"status_info"`
  44. }
  45. func PrintJSONList(list *ListResult, columns []string) {
  46. colsWithData := make([]string, 0)
  47. if len(columns) == 0 {
  48. colsWithDataMap := make(map[string]bool)
  49. for _, obj := range list.Data {
  50. objdict, _ := obj.(*jsonutils.JSONDict)
  51. objmap, _ := objdict.GetMap()
  52. for k := range objmap {
  53. colsWithDataMap[k] = true
  54. }
  55. }
  56. prefixCols := []string{}
  57. for k := range colsWithDataMap {
  58. if sets.NewString("id", "name").Has(strings.ToLower(k)) {
  59. prefixCols = append(prefixCols, k)
  60. } else {
  61. colsWithData = append(colsWithData, k)
  62. }
  63. }
  64. sort.Strings(prefixCols)
  65. sort.Strings(colsWithData)
  66. colsWithData = append(prefixCols, colsWithData...)
  67. } else {
  68. colsWithDataMap := make(map[string]bool)
  69. for _, obj := range list.Data {
  70. for _, k := range columns {
  71. if _, ok := colsWithDataMap[k]; !ok {
  72. _, e := obj.GetIgnoreCases(k)
  73. if e == nil {
  74. colsWithDataMap[k] = true
  75. }
  76. }
  77. }
  78. }
  79. for _, k := range columns {
  80. if _, ok := colsWithDataMap[k]; ok {
  81. colsWithData = append(colsWithData, k)
  82. }
  83. }
  84. }
  85. const (
  86. OS_MAX_COLUMN_TEXT_LENGTH = "OS_MAX_COLUMN_TEXT_LENGTH"
  87. OS_TRY_TERM_WIDTH = "OS_TRY_TERM_WIDTH"
  88. defaultMaxColLength = 512
  89. )
  90. maxColLength := int64(-1)
  91. colTruncated := false
  92. tryTermWidth := false
  93. screenWidth, _ := termWidth()
  94. if screenWidth > 0 {
  95. // the width of screen is available, which is an interactive shell
  96. // truncate the text to make it prettier
  97. osMaxTextLength := os.Getenv(OS_MAX_COLUMN_TEXT_LENGTH)
  98. if len(osMaxTextLength) > 0 {
  99. maxColLength, _ = strconv.ParseInt(osMaxTextLength, 10, 64)
  100. if maxColLength >= 0 && maxColLength < 3 {
  101. maxColLength = defaultMaxColLength
  102. }
  103. } else {
  104. maxColLength = defaultMaxColLength
  105. }
  106. osTryTermWidth := os.Getenv(OS_TRY_TERM_WIDTH)
  107. if strings.ToLower(osTryTermWidth) == "true" {
  108. tryTermWidth = true
  109. }
  110. }
  111. pt := prettytable.NewPrettyTableWithTryTermWidth(colsWithData, tryTermWidth)
  112. rows := make([][]string, 0)
  113. for _, obj := range list.Data {
  114. row := make([]string, 0)
  115. for _, k := range colsWithData {
  116. v, e := obj.GetIgnoreCases(k)
  117. if e == nil {
  118. s, _ := v.GetString()
  119. if maxColLength > 0 && int64(len(s)) > maxColLength {
  120. s = s[0:maxColLength-3] + "..."
  121. colTruncated = true
  122. }
  123. row = append(row, s)
  124. } else {
  125. row = append(row, "")
  126. }
  127. }
  128. rows = append(rows, row)
  129. }
  130. fmt.Print(pt.GetString(rows))
  131. total := int64(list.Total)
  132. if list.Total == 0 {
  133. total = int64(len(list.Data))
  134. }
  135. title := fmt.Sprintf("Total: %d", total)
  136. if len(list.MarkerField) > 0 {
  137. title += fmt.Sprintf(" Field: %s Order: %s", list.MarkerField, list.MarkerOrder)
  138. if len(list.NextMarker) > 0 {
  139. title += fmt.Sprintf(" NextMarker: %s", list.NextMarker)
  140. }
  141. } else {
  142. if list.Limit == 0 && total > int64(len(list.Data)) {
  143. list.Limit = len(list.Data)
  144. }
  145. if list.Limit > 0 {
  146. pages := int(total / int64(list.Limit))
  147. if int64(pages*list.Limit) < total {
  148. pages += 1
  149. }
  150. page := int(list.Offset/list.Limit) + 1
  151. title = fmt.Sprintf("%s Pages: %d Limit: %d Offset: %d Page: %d",
  152. title, pages, list.Limit, list.Offset, page)
  153. }
  154. }
  155. fmt.Println("*** ", title, " ***")
  156. if list.Totals != nil {
  157. totalWithStatusInfo := TotalCountWithStatusInfo{}
  158. err := list.Totals.Unmarshal(&totalWithStatusInfo)
  159. if err != nil {
  160. fmt.Println("error to unmarshal totals to TotalCountWithStatusInfo", err)
  161. } else if len(totalWithStatusInfo.StatusInfo) > 0 {
  162. sort.Sort(totalWithStatusInfo.StatusInfo)
  163. pt := prettytable.NewPrettyTableWithTryTermWidth([]string{"#", "Status", "Count", "PendingDeletedCount"}, tryTermWidth)
  164. rows := make([][]string, 0)
  165. for i, statusInfo := range totalWithStatusInfo.StatusInfo {
  166. rows = append(rows, []string{fmt.Sprintf("#%d", i+1), statusInfo.Status, strconv.FormatInt(statusInfo.TotalCount, 10), strconv.FormatInt(statusInfo.PendingDeletedCount, 10)})
  167. }
  168. fmt.Print(pt.GetString(rows))
  169. }
  170. }
  171. if colTruncated {
  172. fmt.Println("!!!Some text truncated, set env", OS_MAX_COLUMN_TEXT_LENGTH, "=-1 to show full text!!!")
  173. }
  174. }
  175. func printJSONObject(dict *jsonutils.JSONDict, cb PrintJSONObjectFunc) {
  176. keys := dict.SortedKeys()
  177. pt := prettytable.NewPrettyTable([]string{"Field", "Value"})
  178. rows := make([][]string, 0)
  179. for _, k := range keys {
  180. row := make([]string, 0)
  181. row = append(row, k)
  182. vs, e := dict.GetString(k)
  183. if e != nil {
  184. row = append(row, fmt.Sprintf("Error: %s", e))
  185. } else {
  186. row = append(row, vs)
  187. }
  188. rows = append(rows, row)
  189. }
  190. cb(pt.GetString(rows))
  191. }
  192. func PrintJSONObject(obj jsonutils.JSONObject) {
  193. switch jObj := obj.(type) {
  194. case *jsonutils.JSONDict:
  195. printJSONObject(jObj, func(s string) {
  196. fmt.Print(s)
  197. })
  198. case *jsonutils.JSONArray:
  199. printJSONArray(jObj)
  200. default:
  201. fmt.Println("Not a valid JSON object:", obj.String())
  202. return
  203. }
  204. }
  205. func printJSONArray(array *jsonutils.JSONArray) {
  206. objs, err := array.GetArray()
  207. if err != nil {
  208. fmt.Printf("GetArray objects error: %v", err)
  209. }
  210. if len(objs) == 0 {
  211. return
  212. }
  213. columns := sets.NewString()
  214. for _, obj := range objs {
  215. dict, ok := obj.(*jsonutils.JSONDict)
  216. if !ok {
  217. fmt.Printf("Object %q is not dict", obj)
  218. return
  219. }
  220. jMap, err := dict.GetMap()
  221. if err != nil {
  222. fmt.Printf("GetMap error: %v, object: %q", err, obj)
  223. return
  224. }
  225. for k := range jMap {
  226. columns.Insert(k)
  227. }
  228. }
  229. list := &ListResult{
  230. Data: objs,
  231. Total: array.Length(),
  232. Limit: 0,
  233. Offset: 0,
  234. }
  235. PrintJSONList(list, columns.List())
  236. }
  237. func flattenJSONObjectRecursive(v jsonutils.JSONObject, k string, rootDict *jsonutils.JSONDict) {
  238. switch vv := v.(type) {
  239. case *jsonutils.JSONString, *jsonutils.JSONInt, *jsonutils.JSONBool, *jsonutils.JSONFloat:
  240. rootDict.Set(k, vv)
  241. case *jsonutils.JSONArray:
  242. arr, _ := vv.GetArray()
  243. for i, arrElem := range arr {
  244. nextK := fmt.Sprintf("%s.%d", k, i)
  245. flattenJSONObjectRecursive(arrElem, nextK, rootDict)
  246. }
  247. if k != "" {
  248. rootDict.Remove(k)
  249. }
  250. case *jsonutils.JSONDict:
  251. m, _ := vv.GetMap()
  252. for kk, w := range m {
  253. nextK := kk
  254. if k != "" {
  255. nextK = k + "." + nextK
  256. }
  257. flattenJSONObjectRecursive(w, nextK, rootDict)
  258. }
  259. if k != "" {
  260. rootDict.Remove(k)
  261. }
  262. }
  263. }
  264. type PrintJSONObjectRecursiveExFunc func(jsonutils.JSONObject)
  265. type PrintJSONObjectFunc func(string)
  266. func printJSONObjectRecursive_(obj jsonutils.JSONObject, cb PrintJSONObjectRecursiveExFunc) {
  267. dict, ok := obj.(*jsonutils.JSONDict)
  268. if !ok {
  269. fmt.Println("Not a valid JSON object:", obj.String())
  270. return
  271. }
  272. dictCopy := jsonutils.DeepCopy(dict).(*jsonutils.JSONDict)
  273. flattenJSONObjectRecursive(dictCopy, "", dictCopy)
  274. cb(dictCopy)
  275. }
  276. func PrintJSONObjectRecursive(obj jsonutils.JSONObject) {
  277. printJSONObjectRecursive_(obj, PrintJSONObject)
  278. }
  279. func PrintJSONObjectRecursiveEx(obj jsonutils.JSONObject, cb PrintJSONObjectRecursiveExFunc) {
  280. printJSONObjectRecursive_(obj, cb)
  281. }
  282. func PrintJSONBatchResults(results []SubmitResult, columns []string) {
  283. objs := make([]jsonutils.JSONObject, 0)
  284. errs := make([]jsonutils.JSONObject, 0)
  285. for _, r := range results {
  286. if r.Status == 200 {
  287. objs = append(objs, r.Data)
  288. } else {
  289. err := jsonutils.NewDict()
  290. err.Add(jsonutils.NewInt(int64(r.Status)), "status")
  291. err.Add(jsonutils.Marshal(r.Id), "id")
  292. err.Add(r.Data, "error")
  293. errs = append(errs, err)
  294. }
  295. }
  296. if len(objs) > 0 {
  297. PrintJSONList(&ListResult{Data: objs}, columns)
  298. }
  299. if len(errs) > 0 {
  300. PrintJSONList(&ListResult{Data: errs}, []string{"status", "id", "error"})
  301. }
  302. }