/
json_ysh.py
150 lines (119 loc) · 4.57 KB
/
json_ysh.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from __future__ import print_function
from _devbuild.gen import arg_types
from _devbuild.gen.runtime_asdl import cmd_value
from _devbuild.gen.syntax_asdl import loc, loc_t
from _devbuild.gen.value_asdl import value, LeftName
from builtin import read_osh
from core import error
from core.error import e_usage
from core import pyos
from core import state
from core import vm
from data_lang import j8
from frontend import flag_spec
from frontend import args
from frontend import typed_args
from mycpp import mylib
from mycpp.mylib import log
from ysh import cpython
import sys
import yajl
import posix_ as posix
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from core.ui import ErrorFormatter
_ = log
_JSON_ACTION_ERROR = "builtin expects 'read' or 'write'"
class Json(vm._Builtin):
"""JSON read and write.
--pretty=0 writes it on a single line
--indent=2 controls multiline indentation
"""
def __init__(self, mem, errfmt, is_j8):
# type: (state.Mem, ErrorFormatter, bool) -> None
self.mem = mem
self.errfmt = errfmt
self.stdout_ = mylib.Stdout()
if is_j8:
self.printer = j8.Printer(0)
else:
# TODO: restrict to JSON with some flags
self.printer = j8.Printer(0)
def Run(self, cmd_val):
# type: (cmd_value.Argv) -> int
arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
arg_r.Next() # skip 'json'
action, action_loc = arg_r.Peek2()
if action is None:
raise error.Usage(_JSON_ACTION_ERROR, loc.Missing)
arg_r.Next()
if action == 'write':
# NOTE slightly different flags
# json write --surrogate-ok $'\udc00'
# not valid for j8 write
attrs = flag_spec.Parse('json_write', arg_r)
arg_jw = arg_types.json_write(attrs.attrs)
if not arg_r.AtEnd():
e_usage('write got too many args', arg_r.Location())
rd = typed_args.ReaderForProc(cmd_val)
val = rd.PosValue()
rd.Done()
if arg_jw.pretty: # C++ BUG Here!
indent = arg_jw.indent
#log('arg_jw indent %d', indent)
extra_newline = False
else:
# How yajl works: if indent is -1, then everything is on one line.
indent = -1
extra_newline = True
#log('json write indent %d', indent)
if mylib.PYTHON:
#if 0:
obj = cpython._ValueToPyObj(val)
j = yajl.dumps(obj, indent=indent)
sys.stdout.write(j)
if extra_newline:
sys.stdout.write('\n')
else:
buf = mylib.BufWriter()
self.printer.Print(val, buf, indent)
self.stdout_.write(buf.getvalue())
self.stdout_.write('\n')
elif action == 'read':
attrs = flag_spec.Parse('json_read', arg_r)
arg_jr = arg_types.json_read(attrs.attrs)
# TODO:
# Respect -validate=F
if cmd_val.typed_args: # json read (&x)
rd = typed_args.ReaderForProc(cmd_val)
place = rd.PosPlace()
rd.Done()
blame_loc = cmd_val.typed_args.left # type: loc_t
else: # json read
var_name = '_reply'
#log('VAR %s', var_name)
blame_loc = cmd_val.arg_locs[0]
place = value.Place(LeftName(var_name, blame_loc),
self.mem.TopNamespace())
if not arg_r.AtEnd():
e_usage('read got too many args', arg_r.Location())
try:
contents = read_osh.ReadAll()
except pyos.ReadError as e: # different paths for read -d, etc.
# don't quote code since YSH errexit will likely quote
self.errfmt.PrintMessage("read error: %s" %
posix.strerror(e.err_num))
return 1
if mylib.PYTHON:
try:
obj = yajl.loads(contents)
except ValueError as e:
self.errfmt.Print_('json read: %s' % e,
blame_loc=action_loc)
return 1
# TODO: use token directly
val = cpython._PyObjToValue(obj)
self.mem.SetPlace(place, val, blame_loc)
else:
raise error.Usage(_JSON_ACTION_ERROR, action_loc)
return 0