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

139 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 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 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