pretty.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. // Copyright 2016 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package jsre
  17. import (
  18. "fmt"
  19. "io"
  20. "sort"
  21. "strconv"
  22. "strings"
  23. "github.com/fatih/color"
  24. "github.com/robertkrimen/otto"
  25. )
  26. const (
  27. maxPrettyPrintLevel = 3
  28. indentString = " "
  29. )
  30. var (
  31. FunctionColor = color.New(color.FgMagenta).SprintfFunc()
  32. SpecialColor = color.New(color.Bold).SprintfFunc()
  33. NumberColor = color.New(color.FgRed).SprintfFunc()
  34. StringColor = color.New(color.FgGreen).SprintfFunc()
  35. ErrorColor = color.New(color.FgHiRed).SprintfFunc()
  36. )
  37. // these fields are hidden when printing objects.
  38. var boringKeys = map[string]bool{
  39. "valueOf": true,
  40. "toString": true,
  41. "toLocaleString": true,
  42. "hasOwnProperty": true,
  43. "isPrototypeOf": true,
  44. "propertyIsEnumerable": true,
  45. "constructor": true,
  46. }
  47. // prettyPrint writes value to standard output.
  48. func prettyPrint(vm *otto.Otto, value otto.Value, w io.Writer) {
  49. ppctx{vm: vm, w: w}.printValue(value, 0, false)
  50. }
  51. // prettyError writes err to standard output.
  52. func prettyError(vm *otto.Otto, err error, w io.Writer) {
  53. failure := err.Error()
  54. if ottoErr, ok := err.(*otto.Error); ok {
  55. failure = ottoErr.String()
  56. }
  57. fmt.Fprint(w, ErrorColor("%s", failure))
  58. }
  59. // jsErrorString adds a backtrace to errors generated by otto.
  60. func jsErrorString(err error) string {
  61. if ottoErr, ok := err.(*otto.Error); ok {
  62. return ottoErr.String()
  63. }
  64. return err.Error()
  65. }
  66. func prettyPrintJS(call otto.FunctionCall, w io.Writer) otto.Value {
  67. for _, v := range call.ArgumentList {
  68. prettyPrint(call.Otto, v, w)
  69. fmt.Fprintln(w)
  70. }
  71. return otto.UndefinedValue()
  72. }
  73. type ppctx struct {
  74. vm *otto.Otto
  75. w io.Writer
  76. }
  77. func (ctx ppctx) indent(level int) string {
  78. return strings.Repeat(indentString, level)
  79. }
  80. func (ctx ppctx) printValue(v otto.Value, level int, inArray bool) {
  81. switch {
  82. case v.IsObject():
  83. ctx.printObject(v.Object(), level, inArray)
  84. case v.IsNull():
  85. fmt.Fprint(ctx.w, SpecialColor("null"))
  86. case v.IsUndefined():
  87. fmt.Fprint(ctx.w, SpecialColor("undefined"))
  88. case v.IsString():
  89. s, _ := v.ToString()
  90. fmt.Fprint(ctx.w, StringColor("%q", s))
  91. case v.IsBoolean():
  92. b, _ := v.ToBoolean()
  93. fmt.Fprint(ctx.w, SpecialColor("%t", b))
  94. case v.IsNaN():
  95. fmt.Fprint(ctx.w, NumberColor("NaN"))
  96. case v.IsNumber():
  97. s, _ := v.ToString()
  98. fmt.Fprint(ctx.w, NumberColor("%s", s))
  99. default:
  100. fmt.Fprint(ctx.w, "<unprintable>")
  101. }
  102. }
  103. func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
  104. switch obj.Class() {
  105. case "Array", "GoArray":
  106. lv, _ := obj.Get("length")
  107. len, _ := lv.ToInteger()
  108. if len == 0 {
  109. fmt.Fprintf(ctx.w, "[]")
  110. return
  111. }
  112. if level > maxPrettyPrintLevel {
  113. fmt.Fprint(ctx.w, "[...]")
  114. return
  115. }
  116. fmt.Fprint(ctx.w, "[")
  117. for i := int64(0); i < len; i++ {
  118. el, err := obj.Get(strconv.FormatInt(i, 10))
  119. if err == nil {
  120. ctx.printValue(el, level+1, true)
  121. }
  122. if i < len-1 {
  123. fmt.Fprintf(ctx.w, ", ")
  124. }
  125. }
  126. fmt.Fprint(ctx.w, "]")
  127. case "Object":
  128. // Print values from bignumber.js as regular numbers.
  129. if ctx.isBigNumber(obj) {
  130. fmt.Fprint(ctx.w, NumberColor("%s", toString(obj)))
  131. return
  132. }
  133. // Otherwise, print all fields indented, but stop if we're too deep.
  134. keys := ctx.fields(obj)
  135. if len(keys) == 0 {
  136. fmt.Fprint(ctx.w, "{}")
  137. return
  138. }
  139. if level > maxPrettyPrintLevel {
  140. fmt.Fprint(ctx.w, "{...}")
  141. return
  142. }
  143. fmt.Fprintln(ctx.w, "{")
  144. for i, k := range keys {
  145. v, _ := obj.Get(k)
  146. fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k)
  147. ctx.printValue(v, level+1, false)
  148. if i < len(keys)-1 {
  149. fmt.Fprintf(ctx.w, ",")
  150. }
  151. fmt.Fprintln(ctx.w)
  152. }
  153. if inArray {
  154. level--
  155. }
  156. fmt.Fprintf(ctx.w, "%s}", ctx.indent(level))
  157. case "Function":
  158. // Use toString() to display the argument list if possible.
  159. if robj, err := obj.Call("toString"); err != nil {
  160. fmt.Fprint(ctx.w, FunctionColor("function()"))
  161. } else {
  162. desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
  163. desc = strings.Replace(desc, " (", "(", 1)
  164. fmt.Fprint(ctx.w, FunctionColor("%s", desc))
  165. }
  166. case "RegExp":
  167. fmt.Fprint(ctx.w, StringColor("%s", toString(obj)))
  168. default:
  169. if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel {
  170. s, _ := obj.Call("toString")
  171. fmt.Fprintf(ctx.w, "<%s %s>", obj.Class(), s.String())
  172. } else {
  173. fmt.Fprintf(ctx.w, "<%s>", obj.Class())
  174. }
  175. }
  176. }
  177. func (ctx ppctx) fields(obj *otto.Object) []string {
  178. var (
  179. vals, methods []string
  180. seen = make(map[string]bool)
  181. )
  182. add := func(k string) {
  183. if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") {
  184. return
  185. }
  186. seen[k] = true
  187. if v, _ := obj.Get(k); v.IsFunction() {
  188. methods = append(methods, k)
  189. } else {
  190. vals = append(vals, k)
  191. }
  192. }
  193. iterOwnAndConstructorKeys(ctx.vm, obj, add)
  194. sort.Strings(vals)
  195. sort.Strings(methods)
  196. return append(vals, methods...)
  197. }
  198. func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
  199. seen := make(map[string]bool)
  200. iterOwnKeys(vm, obj, func(prop string) {
  201. seen[prop] = true
  202. f(prop)
  203. })
  204. if cp := constructorPrototype(obj); cp != nil {
  205. iterOwnKeys(vm, cp, func(prop string) {
  206. if !seen[prop] {
  207. f(prop)
  208. }
  209. })
  210. }
  211. }
  212. func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
  213. Object, _ := vm.Object("Object")
  214. rv, _ := Object.Call("getOwnPropertyNames", obj.Value())
  215. gv, _ := rv.Export()
  216. switch gv := gv.(type) {
  217. case []interface{}:
  218. for _, v := range gv {
  219. f(v.(string))
  220. }
  221. case []string:
  222. for _, v := range gv {
  223. f(v)
  224. }
  225. default:
  226. panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv))
  227. }
  228. }
  229. func (ctx ppctx) isBigNumber(v *otto.Object) bool {
  230. // Handle numbers with custom constructor.
  231. if v, _ := v.Get("constructor"); v.Object() != nil {
  232. if strings.HasPrefix(toString(v.Object()), "function BigNumber") {
  233. return true
  234. }
  235. }
  236. // Handle default constructor.
  237. BigNumber, _ := ctx.vm.Object("BigNumber.prototype")
  238. if BigNumber == nil {
  239. return false
  240. }
  241. bv, _ := BigNumber.Call("isPrototypeOf", v)
  242. b, _ := bv.ToBoolean()
  243. return b
  244. }
  245. func toString(obj *otto.Object) string {
  246. s, _ := obj.Call("toString")
  247. return s.String()
  248. }
  249. func constructorPrototype(obj *otto.Object) *otto.Object {
  250. if v, _ := obj.Get("constructor"); v.Object() != nil {
  251. if v, _ = v.Object().Get("prototype"); v.Object() != nil {
  252. return v.Object()
  253. }
  254. }
  255. return nil
  256. }