OILS / builtin / json_ysh.py View on Github | oils.pub

140 lines, 95 significant
1from __future__ import print_function
2
3from _devbuild.gen import arg_types
4from _devbuild.gen.runtime_asdl import cmd_value
5from _devbuild.gen.syntax_asdl import loc, loc_t
6from _devbuild.gen.value_asdl import value, LeftName
7from builtin import read_osh
8from core import error
9from core.error import e_usage
10from core import pyos
11from core import state
12from core import vm
13from data_lang import j8
14from frontend import flag_util
15from frontend import args
16from frontend import typed_args
17from mycpp import mops
18from mycpp import mylib
19from mycpp.mylib import log
20
21import posix_ as posix
22
23from typing import TYPE_CHECKING
24if TYPE_CHECKING:
25 from display import ui
26
27_ = log
28
29_JSON_ACTION_ERROR = "builtin expects 'read' or 'write'"
30
31
32class 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, ui.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 type_errors = rd.NamedBool('type_errors', True)
74 rd.Done()
75
76 # Convert from external JS-like API to internal API.
77 if space <= 0:
78 indent = -1
79 else:
80 indent = space
81
82 buf = mylib.BufWriter()
83 try:
84 if self.is_j8:
85 j8.PrintMessage(val, buf, indent, type_errors)
86 else:
87 j8.PrintJsonMessage(val, buf, indent, type_errors)
88 except error.Encode as e:
89 self.errfmt.PrintMessage(
90 '%s write: %s' % (self.name, e.Message()), action_loc)
91 return 1
92
93 self.stdout_.write(buf.getvalue())
94 self.stdout_.write('\n')
95
96 elif action == 'read':
97 attrs = flag_util.Parse('json_read', arg_r)
98 #arg_jr = arg_types.json_read(attrs.attrs)
99
100 if cmd_val.proc_args: # json read (&x)
101 rd = typed_args.ReaderForProc(cmd_val)
102 place = rd.PosPlace()
103 rd.Done()
104
105 blame_loc = cmd_val.proc_args.typed_args.left # type: loc_t
106
107 else: # json read
108 var_name = '_reply'
109
110 #log('VAR %s', var_name)
111 blame_loc = cmd_val.arg_locs[0]
112 place = value.Place(LeftName(var_name, blame_loc),
113 self.mem.CurrentFrame())
114
115 if not arg_r.AtEnd():
116 e_usage('read got too many args', arg_r.Location())
117
118 try:
119 contents = read_osh.ReadAll()
120 except pyos.ReadError as e: # different paths for read -d, etc.
121 # don't quote code since YSH errexit will likely quote
122 self.errfmt.PrintMessage("read error: %s" %
123 posix.strerror(e.err_num))
124 return 1
125
126 p = j8.Parser(contents, self.is_j8)
127 try:
128 val = p.ParseValue()
129 except error.Decode as err:
130 # TODO: Need to show position info
131 self.errfmt.Print_('%s read: %s' % (self.name, err.Message()),
132 blame_loc=action_loc)
133 return 1
134
135 self.mem.SetPlace(place, val, blame_loc)
136
137 else:
138 raise error.Usage(_JSON_ACTION_ERROR, action_loc)
139
140 return 0