-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjpc.py
More file actions
228 lines (193 loc) · 6.67 KB
/
jpc.py
File metadata and controls
228 lines (193 loc) · 6.67 KB
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#!/usr/bin/python
# jpc - JSON prototype compiler for python
# jpc takes several JSON files as input then generates python code that
# can be used to serialize/deserialize those JSON data structures.
import os
import sys
import json
from keyword import iskeyword
class PythonFormatWriter(object):
def __init__(self, tab_spaces=4, line_ending='\n'):
self._line_ending = line_ending
self._tab = ''.join([' ' for _ in range(0, tab_spaces)])
self._filebuf = []
self._indent_level = 0
self._indent = ''
def __str__(self):
return self.getstr()
@property
def tab(self):
return self._tab
def set_indent(self, indent=0):
self._indent_level = indent
self._indent = ''.join([' ' for _ in range(0, indent)])
def indent(self):
self._indent_level += 1
self._indent += self._tab
def back(self):
self._indent_level -= 1
self._indent = self._indent[:-len(self._tab)]
def putln(self, s=""):
self._filebuf.append(self._indent)
if s:
self._filebuf.append(s)
self._filebuf.append(self._line_ending)
def put(self, s):
self._filebuf.append(s)
def clear(self):
self._filebuf = []
self._indent_level = 0
self._indent = ''
def getstr(self):
return ''.join(self._filebuf)
class JSONObjectMetadata(object):
def __init__(self, name):
if iskeyword(name):
raise SyntaxError("Can't use a python keyword as class name: %s" % name)
self._classname = name
self._members = []
def add_member(self, key, hint, decoder=None):
name = key
if iskeyword(name):
name += '_'
if isinstance(hint, str) or isinstance(hint, unicode):
default = "\'\'"
elif isinstance(hint, bool): # ATTENTION: bool test must precede int test
default = "False"
elif isinstance(hint, int):
default = "0"
elif isinstance(hint, float):
default = "0.0"
elif isinstance(hint, list):
default = "[]"
elif isinstance(hint, dict):
default = self._classname + "_" + name + "()"
else:
default = "None"
self._members.append((name, key, default, decoder))
def python_code(self):
w = PythonFormatWriter()
w.putln("class %s(object):" % self._classname)
w.indent()
# __init__ method
w.putln("def __init__(self):")
w.indent()
for name, key, default, decoder in self._members:
w.putln("self.%s = %s" % (name, default))
w.back() # __init__
w.putln()
# from_dict method
w.putln("@staticmethod")
w.putln("def from_dict(dct):")
w.indent()
w.putln("if not dct:")
w.indent()
w.putln("return None")
w.back()
w.putln("obj = %s()" % self._classname)
for name, key, default, decoder in self._members:
if decoder:
w.putln("obj.%s = %s(dct.get(\"%s\"))" % (name, decoder, key))
else:
w.putln("obj.%s = dct.get(\"%s\")" % (name, key))
w.putln("return obj")
w.back() # from_dict
w.back() # class
return w.getstr()
class JSONPrototypeCompiler(object):
def __init__(self):
self._objs = []
self._metas = []
self._decoders = []
def compile_object(self, name, dct):
self._objs = []
self._metas = []
self._decoders = []
self._parse_dict(name, dct)
def python_code(self):
code = []
for m in self._metas:
code.append(m.python_code())
for d in self._decoders:
code.append(d)
return "\n\n".join(code)
def _parse_dict(self, name, dct):
obj = JSONObjectMetadata(name)
self._metas.append(obj)
self._objs.append((name, dct))
for member, value in dct.items():
decoder = None
if not value:
# null or empty values
pass
elif isinstance(value, dict):
decoder = self._parse_dict(name+"_"+member, value)
elif isinstance(value, list):
decoder = self._parse_list(name+"_"+member, value)
obj.add_member(member, value, decoder)
return name + ".from_dict"
def _parse_list(self, name, lst):
elem = lst[0]
item_decoder = None
if isinstance(elem, dict):
item_decoder = self._parse_dict(name+"_item", elem)
elif isinstance(elem, list):
item_decoder = self._parse_list(name+"_item", elem)
if item_decoder:
list_decoder = name + "_from_list"
w = PythonFormatWriter()
w.putln("def %s(lst):" % list_decoder)
w.indent()
w.putln("if not lst:")
w.indent()
w.putln("return None")
w.back()
w.putln("values = []")
w.putln("for v in lst:")
w.indent()
w.putln("values.append(%s(v))" % item_decoder)
w.back()
w.putln("return values")
w.back()
self._decoders.append(w.getstr())
return list_decoder
return None
if __name__ == "__main__":
from sys import stdout
from sys import stderr
if len(sys.argv) < 2:
print("Usage: jpc.py [OPTIONS] <FILE1 [FILE2...]>")
print("Available OPTIONS:")
print(" -stdout print python code to standard output instead of files")
exit(0)
option_start = 1
option_end = 1
while sys.argv[option_end].startswith('-'):
option_end += 1
options = sys.argv[option_start:option_end]
use_stdout = "-stdout" in options
jpc = JSONPrototypeCompiler()
for fpath in sys.argv[option_end:]:
try:
d = json.load(open(fpath))
fname = os.path.basename(fpath)
fdir = fpath[:-len(fname)]
name = fname.split('.', 1)[0]
jpc.compile_object(name, d)
if use_stdout:
f = stdout
else:
e = name.rfind('.')
if e >= 0:
f = open(fdir+name+".py", 'w')
else:
f = open(fdir+name+".py", 'w')
f.write("# The following code is auto generated by jpc.py\n")
f.write("# Source JSON file: " + fpath + "\n")
f.write(jpc.python_code())
if not use_stdout:
f.close()
except Exception, e:
stderr.write("** ERROR ** Bad JSON file \"%s\"\n" % fpath)
stderr.write("Exception: %s\n" % str(e))
exit(0)