| 1 | from __future__ import print_function
 | 
| 2 | 
 | 
| 3 | from _devbuild.gen import arg_types
 | 
| 4 | from _devbuild.gen.runtime_asdl import cmd_value
 | 
| 5 | from _devbuild.gen.syntax_asdl import loc, loc_t
 | 
| 6 | from _devbuild.gen.value_asdl import value, LeftName
 | 
| 7 | from builtin import read_osh
 | 
| 8 | from core import error
 | 
| 9 | from core.error import e_usage
 | 
| 10 | from core import pyos
 | 
| 11 | from core import state
 | 
| 12 | from core import vm
 | 
| 13 | from data_lang import j8
 | 
| 14 | from frontend import flag_util
 | 
| 15 | from frontend import args
 | 
| 16 | from frontend import typed_args
 | 
| 17 | from mycpp import mops
 | 
| 18 | from mycpp import mylib
 | 
| 19 | from mycpp.mylib import log
 | 
| 20 | 
 | 
| 21 | import posix_ as posix
 | 
| 22 | 
 | 
| 23 | from typing import TYPE_CHECKING
 | 
| 24 | if TYPE_CHECKING:
 | 
| 25 |     from core.ui import ErrorFormatter
 | 
| 26 | 
 | 
| 27 | _ = log
 | 
| 28 | 
 | 
| 29 | _JSON_ACTION_ERROR = "builtin expects 'read' or 'write'"
 | 
| 30 | 
 | 
| 31 | 
 | 
| 32 | class Json(vm._Builtin):
 | 
| 33 |     """JSON read and write.
 | 
| 34 | 
 | 
| 35 |     --pretty=0 writes it on a single line
 | 
| 36 |     --indent=2 controls multiline indentation
 | 
| 37 |     """
 | 
| 38 | 
 | 
| 39 |     def __init__(self, mem, errfmt, is_j8):
 | 
| 40 |         # type: (state.Mem, ErrorFormatter, bool) -> None
 | 
| 41 |         self.mem = mem
 | 
| 42 |         self.errfmt = errfmt
 | 
| 43 | 
 | 
| 44 |         self.is_j8 = is_j8
 | 
| 45 |         self.name = 'json8' if is_j8 else 'json'  # for error messages
 | 
| 46 | 
 | 
| 47 |         self.stdout_ = mylib.Stdout()
 | 
| 48 | 
 | 
| 49 |     def Run(self, cmd_val):
 | 
| 50 |         # type: (cmd_value.Argv) -> int
 | 
| 51 |         arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
 | 
| 52 |         arg_r.Next()  # skip 'json'
 | 
| 53 | 
 | 
| 54 |         action, action_loc = arg_r.Peek2()
 | 
| 55 |         if action is None:
 | 
| 56 |             raise error.Usage(_JSON_ACTION_ERROR, loc.Missing)
 | 
| 57 |         arg_r.Next()
 | 
| 58 | 
 | 
| 59 |         if action == 'write':
 | 
| 60 |             # NOTE slightly different flags
 | 
| 61 |             # json write --surrogate-ok $'\udc00'
 | 
| 62 |             attrs = flag_util.Parse('json_write', arg_r)
 | 
| 63 | 
 | 
| 64 |             arg_jw = arg_types.json_write(attrs.attrs)
 | 
| 65 | 
 | 
| 66 |             if not arg_r.AtEnd():
 | 
| 67 |                 e_usage('write got too many args', arg_r.Location())
 | 
| 68 | 
 | 
| 69 |             rd = typed_args.ReaderForProc(cmd_val)
 | 
| 70 |             val = rd.PosValue()
 | 
| 71 |             # default is 2, rather than 0 for toJson()
 | 
| 72 |             space = mops.BigTruncate(rd.NamedInt('space', 2))
 | 
| 73 |             rd.Done()
 | 
| 74 | 
 | 
| 75 |             # Convert from external JS-like API to internal API.
 | 
| 76 |             if space <= 0:
 | 
| 77 |                 indent = -1
 | 
| 78 |             else:
 | 
| 79 |                 indent = space
 | 
| 80 | 
 | 
| 81 |             buf = mylib.BufWriter()
 | 
| 82 |             try:
 | 
| 83 |                 if self.is_j8:
 | 
| 84 |                     j8.PrintMessage(val, buf, indent)
 | 
| 85 |                 else:
 | 
| 86 |                     j8.PrintJsonMessage(val, buf, indent)
 | 
| 87 |             except error.Encode as e:
 | 
| 88 |                 self.errfmt.PrintMessage(
 | 
| 89 |                     '%s write: %s' % (self.name, e.Message()), action_loc)
 | 
| 90 |                 return 1
 | 
| 91 | 
 | 
| 92 |             self.stdout_.write(buf.getvalue())
 | 
| 93 |             self.stdout_.write('\n')
 | 
| 94 | 
 | 
| 95 |         elif action == 'read':
 | 
| 96 |             attrs = flag_util.Parse('json_read', arg_r)
 | 
| 97 |             #arg_jr = arg_types.json_read(attrs.attrs)
 | 
| 98 | 
 | 
| 99 |             if cmd_val.typed_args:  # json read (&x)
 | 
| 100 |                 rd = typed_args.ReaderForProc(cmd_val)
 | 
| 101 |                 place = rd.PosPlace()
 | 
| 102 |                 rd.Done()
 | 
| 103 | 
 | 
| 104 |                 blame_loc = cmd_val.typed_args.left  # type: loc_t
 | 
| 105 | 
 | 
| 106 |             else:  # json read
 | 
| 107 |                 var_name = '_reply'
 | 
| 108 | 
 | 
| 109 |                 #log('VAR %s', var_name)
 | 
| 110 |                 blame_loc = cmd_val.arg_locs[0]
 | 
| 111 |                 place = value.Place(LeftName(var_name, blame_loc),
 | 
| 112 |                                     self.mem.TopNamespace())
 | 
| 113 | 
 | 
| 114 |             if not arg_r.AtEnd():
 | 
| 115 |                 e_usage('read got too many args', arg_r.Location())
 | 
| 116 | 
 | 
| 117 |             try:
 | 
| 118 |                 contents = read_osh.ReadAll()
 | 
| 119 |             except pyos.ReadError as e:  # different paths for read -d, etc.
 | 
| 120 |                 # don't quote code since YSH errexit will likely quote
 | 
| 121 |                 self.errfmt.PrintMessage("read error: %s" %
 | 
| 122 |                                          posix.strerror(e.err_num))
 | 
| 123 |                 return 1
 | 
| 124 | 
 | 
| 125 |             p = j8.Parser(contents, self.is_j8)
 | 
| 126 |             try:
 | 
| 127 |                 val = p.ParseValue()
 | 
| 128 |             except error.Decode as err:
 | 
| 129 |                 # TODO: Need to show position info
 | 
| 130 |                 self.errfmt.Print_('%s read: %s' % (self.name, err.Message()),
 | 
| 131 |                                    blame_loc=action_loc)
 | 
| 132 |                 return 1
 | 
| 133 | 
 | 
| 134 |             self.mem.SetPlace(place, val, blame_loc)
 | 
| 135 | 
 | 
| 136 |         else:
 | 
| 137 |             raise error.Usage(_JSON_ACTION_ERROR, action_loc)
 | 
| 138 | 
 | 
| 139 |         return 0
 |