OILS / builtin / json_ysh.py View on Github | oilshell.org

143 lines, 94 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 core.ui import ErrorFormatter
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, ErrorFormatter, bool) -> None
41 self.mem = mem
42 self.errfmt = errfmt
43
44 self.is_j8 = is_j8
45 self.name = 'j8' 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 # not valid for j8 write
63 attrs = flag_util.Parse('json_write', arg_r)
64
65 arg_jw = arg_types.json_write(attrs.attrs)
66
67 if not arg_r.AtEnd():
68 e_usage('write got too many args', arg_r.Location())
69
70 rd = typed_args.ReaderForProc(cmd_val)
71 val = rd.PosValue()
72 rd.Done()
73
74 if arg_jw.pretty: # C++ BUG Here!
75 indent = mops.BigTruncate(arg_jw.indent)
76 else:
77 # How yajl works: if indent is -1, then everything is on one line.
78 indent = -1
79
80 #log('json write indent %d', indent)
81
82 buf = mylib.BufWriter()
83
84 try:
85 if self.is_j8:
86 j8.PrintMessage(val, buf, indent)
87 else:
88 j8.PrintJsonMessage(val, buf, indent)
89 except error.Encode as e:
90 self.errfmt.PrintMessage(
91 '%s write: %s' % (self.name, e.Message()), action_loc)
92 return 1
93
94 self.stdout_.write(buf.getvalue())
95 self.stdout_.write('\n')
96
97 elif action == 'read':
98 attrs = flag_util.Parse('json_read', arg_r)
99 arg_jr = arg_types.json_read(attrs.attrs)
100 # TODO:
101 # Respect -validate=F
102
103 if cmd_val.typed_args: # json read (&x)
104 rd = typed_args.ReaderForProc(cmd_val)
105 place = rd.PosPlace()
106 rd.Done()
107
108 blame_loc = cmd_val.typed_args.left # type: loc_t
109
110 else: # json read
111 var_name = '_reply'
112
113 #log('VAR %s', var_name)
114 blame_loc = cmd_val.arg_locs[0]
115 place = value.Place(LeftName(var_name, blame_loc),
116 self.mem.TopNamespace())
117
118 if not arg_r.AtEnd():
119 e_usage('read got too many args', arg_r.Location())
120
121 try:
122 contents = read_osh.ReadAll()
123 except pyos.ReadError as e: # different paths for read -d, etc.
124 # don't quote code since YSH errexit will likely quote
125 self.errfmt.PrintMessage("read error: %s" %
126 posix.strerror(e.err_num))
127 return 1
128
129 p = j8.Parser(contents, self.is_j8)
130 try:
131 val = p.ParseValue()
132 except error.Decode as err:
133 # TODO: Need to show position info
134 self.errfmt.Print_('%s read: %s' % (self.name, err.Message()),
135 blame_loc=action_loc)
136 return 1
137
138 self.mem.SetPlace(place, val, blame_loc)
139
140 else:
141 raise error.Usage(_JSON_ACTION_ERROR, action_loc)
142
143 return 0