-
Notifications
You must be signed in to change notification settings - Fork 4
/
generator.py
341 lines (263 loc) · 10.9 KB
/
generator.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
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
"""Module to generate the API wrappers for lbrynet and lbrycrd.
This module can be executed directly or called from a building system
like `Makefile` or `setuptools`.
"""
import ast
import json
import os
import shutil
import sys
import urllib.request
import urllib.error
from template.constants import (LBRY_API_RAW_JSON_URL,
TEMPLATE_DIR,
PKG_DIR,
LBRYD_BASE_FPATH,
LBRYD_FPATH,
LBRYCRD_BASE_FPATH,
LBRYCRD_FPATH,
DTYPE_MAPPING)
def get_lbry_api_function_docs(url=LBRY_API_RAW_JSON_URL, doc=None):
"""Scrapes the given input in JSON format to obtain the lbrynet API.
:param str url: URL to the documentation we need to obtain,
pybry.constants.LBRY_API_RAW_JSON_URL by default
:return: List of functions retrieved from the `url` given
:rtype: list
"""
try:
if doc:
with open(doc, "r") as api_file:
contents = api_file.read()
else:
# Grab the page content
docs_page = urllib.request.urlopen(url)
# Read the contents of the actual url we grabbed and decode them into UTF-8
contents = docs_page.read().decode("utf-8")
# Return the contents loaded as JSON
return json.loads(contents)
# If we get an exception, simply exit
except urllib.error.URLError as err:
print(f"Cannot open URL for reading; {err} '{url}'")
except (FileNotFoundError, PermissionError) as err:
print(f"Cannot open file for reading; {err}")
except Exception as err:
print(f"{err}, url='{url}', doc='{doc}'")
return []
def generate_method_definition(func):
"""Generates the body for the given function.
:param dict func: dict of a JSON-Formatted function as defined by the API docs
:return: A String containing the definition for the function as it should be written in code
:rtype: str
"""
indent = 4
# initial definition
method_definition = (" " * indent) + "def " + func["name"]
# Here we just create a queue and put all the parameters
# into the queue in the order that they were given,
params_required = [
param for param in func["arguments"] if param["is_required"]
]
params_optional = [
param for param in func["arguments"]
if not param["is_required"]
]
# Open the parameter definitions
method_definition += "(self, "
for param in params_required:
# Put the parameter into the queue
method_definition += param["name"]
method_definition += ", "
for param in params_optional:
method_definition += param["name"]
# Default methods not required
method_definition += "=None, "
# Peel off the final ", " and close off the parameter definition
method_definition = method_definition.rstrip(", ") + "):\n"
indent += 4
# re-indent
method_definition += " " * indent
# Begin with description.
method_definition += '"""' + func["description"]
# re-indent
method_definition += "\n\n" + " " * indent
# Go through each parameter and insert description & type hint
for param in params_required + params_optional:
# Add the type
method_definition += ":param " + DTYPE_MAPPING[param["type"].lower()]
# Add the name
method_definition += " " + param["name"] + ": "
# Add the description
method_definition += param["description"]
# Add optionality & reindent
method_definition += "\n" if param[
"is_required"] else " (Optional)\n"
method_definition += " " * indent
# Do not parse the returns because it doesn't work correctly at the moment
# open_index = func["returns"].find('(')
# close_index = func["returns"].find(
# ')', (open_index if open_index > -1 else 0))
#
# func["returns"] = func["returns"].replace("\t", " " * 4)
# return_string = func["returns"].replace("\n", "")
#
# if open_index < close_index and func["returns"][
# open_index + 1:close_index] in DTYPE_MAPPING:
# method_definition += ":rtype: " + DTYPE_MAPPING[
# func["returns"][open_index + 1:close_index]]
#
# func["returns"] = func["returns"].replace(
# func["returns"][open_index:close_index + 1], "")
#
# method_definition += "\n" + " " * indent
#
# method_definition += ":return: " + return_string
#
# for i in range(0, len(return_string) + 1, 80 - (indent + 2)):
# method_definition += return_string[i:i + (
# 80 - (indent + 2))] + "\n" + " " * indent
# Close it off & reindent
method_definition += '"""' + "\n" + " " * indent
# Create the params map
params_map = "__params_map = {"
# Save the indent
params_indent, num_params = len(
params_map), len(params_required) + len(params_optional)
# Append the map to the method_definition
method_definition += params_map
# Go through the required parameters first
for i, param in enumerate(params_required + params_optional):
# append the methods to the map
method_definition += "'" + param["name"] + "': " + param["name"]
if not param["is_required"]:
method_definition + " if " + param[
"name"] + "is not None else None"
# add commas or ending bracket if needed & reindent correctly
method_definition += ",\n" + " " * indent + ' ' * params_indent if i + 1 < num_params else ""
method_definition += '}\n\n' + ' ' * indent
method_definition += "return self.make_request(SERVER_ADDRESS, '" + func["name"] + "', " \
+ params_map.rstrip(" = {") + ", timeout=self.timeout)\n\n"
return method_definition
def generate_lbryd_wrapper(url=LBRY_API_RAW_JSON_URL,
doc=None,
read_file=LBRYD_BASE_FPATH,
write_file=LBRYD_FPATH):
"""Generates the wrapper for the lbrynet daemon.
:param str url: URL to the documentation we need to obtain,
pybry.constants.LBRY_API_RAW_JSON_URL by default
:param str read_file: This is the path to the file from which we will be reading
:param str write_file: Path from project root to the file we'll be writing to.
"""
print(80 * "-")
if doc:
sections = get_lbry_api_function_docs(doc=doc)
inpt = doc
else:
sections = get_lbry_api_function_docs(url=url)
inpt = url
if not sections:
print("Empty information; wrapper module not written.")
return True
print("Input JSON:", inpt)
# Open the actual file for appending
with open(write_file, 'w') as lbry_file:
docstring = ['"""',
'LBRY daemon wrapper in Python. Import it and initialize the main class.',
'',
'This file was generated at build time using the `generator` module.',
'You may edit it but do so with caution.',
'If this file contains syntax errors, check the input file',
'for badly formated fields.',
f'Input JSON: {inpt}',
'"""',
'']
docstring = "\n".join(docstring)
lbry_file.write(docstring)
with open(read_file, 'r') as template:
header = template.read()
lbry_file.write(header)
# Iterate through all the functions we retrieved
# and write them to the file
for section in sections:
commands = sections[section]["commands"]
for command in commands:
method_definition = generate_method_definition(command)
lbry_file.write(method_definition)
print("Generated 'lbrynet' API wrapper:", write_file)
with open(write_file) as lbry_file:
source = lbry_file.read()
parsed = True
try:
result = ast.parse(source, filename=write_file)
except SyntaxError as err:
print("The resulting file has syntax errors. Look at the error line for clues.")
print("Error:", err)
print()
print("The problem is usually in the input JSON file; it may contain badly formatted fields.")
print("Input:", inpt)
print()
parsed = False
if parsed:
try:
from yapf.yapflib.yapf_api import FormatFile
FormatFile(write_file, in_place=True)
except ImportError:
print()
print("[Warning]: 'yapf' could not be imported, so the generated code will not be formatted")
return None
def generate_lbrycrd_wrapper(read_file=LBRYCRD_BASE_FPATH,
write_file=LBRYCRD_FPATH):
"""Generate wrapper for the lbrycrd daemon.
At the moment there is no JSON file that describes the API of `lbrycrd`,
therefore the wrapper is just a copy of the template, that is placed
in the final package location.
"""
print(80 * "-")
print("Input JSON:", None)
with open(write_file, "w") as lbrycrd_file:
docstring = ['"""',
'LBRYCRD daemon wrapper in Python. Import it an initialize the main class.',
'',
'This file was generated at build time using the `generator` module.',
'"""',
'']
docstring = "\n".join(docstring)
lbrycrd_file.write(docstring)
with open(read_file, "r") as template:
header = template.read()
lbrycrd_file.write(header)
print("Generated 'lbrycrd' API wrapper:", write_file)
def generate_basic_modules(template_dir=TEMPLATE_DIR, out_dir=PKG_DIR):
"""Generate the static modules in the final package directory.
These are simply copied over from the template directory.
"""
print(80 * "-")
print("Package:", out_dir)
basic_modules = ["_init.py",
"constants.py",
"base_api.py",
"exception.py"]
if not os.path.exists(out_dir):
os.mkdir(out_dir)
installed = []
for module in basic_modules:
in_file = os.path.join(template_dir, module)
if module == "_init.py":
module = "__init__.py"
out_file = os.path.join(out_dir, module)
try:
shutil.copy(in_file, out_file)
except (FileNotFoundError, shutil.SameFileError) as err:
print(err)
installed.append("- " + out_file)
print("Basic modules:")
print("\n".join(installed))
def main(argv=None):
if argv and isinstance(argv, (list, tuple)):
doc = argv[1] if len(argv) > 1 else None
else:
doc = None
generate_basic_modules()
generate_lbrycrd_wrapper()
generate_lbryd_wrapper(doc=doc)
if __name__ == "__main__":
sys.exit(main(sys.argv))