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