-
Notifications
You must be signed in to change notification settings - Fork 0
/
postman-to-markdown.py
373 lines (293 loc) · 9.79 KB
/
postman-to-markdown.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
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
import argparse
from os.path import exists
from json import dumps, load, loads
from re import sub
from typing import List
def get_args_parser() -> argparse.ArgumentParser:
"""
Initializes the parser
@source=https://stackoverflow.com/questions/40001892/reading-named-command-arguments
returns argparse.ArgumentParser
"""
parser = argparse.ArgumentParser()
parser.add_argument('--collection', help="""
The Postman Collection to parse.
A complete absolute path should be given.
Currently only working with the latest exported version (v2.1)
""")
parser.add_argument('--destination', help="""
The wanted destination file.
A complete absolute path should be given.
""")
return parser
def get_collection_content(
collection_source: str
) -> dict:
"""
Gets the parsed content from the given collection
collection_source : str
The absolute path to the collection
returns dict
"""
assert exists(collection_source)
content = None
with open(collection_source) as reader:
content = load(reader)
return content
def get_collection_title(
collection: dict
) -> str:
"""
Get the title from the given collection
collection: dict
The collection to evaluate
returns str
"""
return f"# {collection['info']['name']} #"
def get_slugified_name(
name: str
) -> str:
"""
Gets the slugified version of a given item name
name : str
The given item name
returns str
"""
slug = name.lower()
slug = sub(r'\s+', '-', slug)
slug = sub(r'[\/\[\]\.]', '', slug)
slug = sub(r'\-+', '-', slug)
return slug
def get_collection_table_of_contents(
collection: dict
) -> List[str]:
"""
Get the table scheme of contents from the given collection
collection: dict
The collection to evaluate
returns List[str]
"""
contents = []
for item in collection['item']:
contents.append(
f"1. [{item['name']}](#{get_slugified_name(item['name'])})")
if 'item' in item:
sub_elements = get_collection_table_of_contents(item)
sub_elements = [
contents.append(f"\t{element}")
for element in sub_elements
]
return contents
def get_query_markdown_table(
request_details: dict
) -> str:
"""
Generates a markdown table from a collection's request's details, if possible
request_details : dict
The collection's request's details
returns str
"""
if 'query' not in request_details['url'] or len(request_details['url']['query']) < 1:
return 'This request does not have any query params available, or documented.'
query_params = request_details['url']['query']
markdown_table = []
markdown_table.append('**HTTP query params**')
markdown_table.append('')
markdown_table.append(
f"| {' | '.join([ f'**{colname}**' for colname in query_params[0].keys() ])} |")
markdown_table.append(
f"| {' | '.join([ '---' for _ in query_params[0].keys() ])} |")
for query_param in query_params:
try:
def get_value(key, value):
if value is None:
value = ''
return f'**{value}**' if key in ['key'] else value
markdown_table.append(
f"| {' | '.join([ get_value(key, value) for key, value in query_param.items() ])} |"
)
except:
print(query_param)
markdown_table = '\n'.join(markdown_table)
return markdown_table
def get_request_body(
request_details: dict,
body_type: str = 'raw',
) -> str:
"""
Generates a markdown json body from a collection's request's body, if possible
request_details : dict
The collection's request's details
body_type : str
The body's type, it will always try to be raw
returns str
"""
if 'body' not in request_details or not request_details['body']:
return 'This request does not have an example request body.'
body_details = request_details['body']
body_lang = body_details['options'][body_type]['language']
body = body_details[body_type]
# body = sub(r'[\s]+', ' ', body)
# body = sub(r'(\\r\\n)+', '\n', body)
try:
body = dumps(loads(body), indent=4)
except:
# print(body)
return 'This request does not have an example request body.'
return '\n'.join([
'<details>',
"<summary>Show request's body</summary>",
' ',
f"```{body_lang}\n{body}\n```",
'</details>',
])
def parse_collection_item_to_markdown(
item: dict,
recursive_level: int = 3,
parent: dict = None,
) -> str:
"""
Parses a collection item to markdown
item : dict
The collection's item to parse
recursive_level : int
The index level for the markdown titles
parent : dict
The parent of this element
returns str
"""
request_details = item['request']
item_details = [
'\n'.join([
f"{recursive_level * '#'} {item['name']}",
f"[Back to {parent['name']}](#{get_slugified_name(parent['name'])})",
]),
'\\\n'.join([
f"**HTTP method**: {request_details['method'] if 'method' in request_details else 'none to be kown.'}",
f"**Authentication type**: {request_details['auth']['type'] if 'auth' in request_details else 'none to be kown.'}",
f"**Url**: `{request_details['url']['raw'] if request_details['url']['raw'] else 'none to be found.'}`"
]),
f"Description: {request_details['description'] if 'description' in request_details else 'None.'}",
get_query_markdown_table(request_details),
get_request_body(request_details)
]
return '\n\n'.join(item_details)
def get_parsed_collection_items(
collection: dict,
recursive_level: int = 2,
parent: dict = None,
) -> List[str]:
"""
Gets the parsed collection items, the body you could say
collection : dict
The parsed collection's content
recursive_level : int
The index level for the markdown titles
parent : dict
The parent of this element
returns List[str]
"""
assert 'item' in collection
contents = []
for item in collection['item']:
if 'item' in item: # is a folder
contents.append('\n'.join([
f"{recursive_level * '#'} {item['name']}",
f"[Back to {parent['name']}](#{get_slugified_name(parent['name'])})"
]))
if 'description' in item:
contents.append(item['description'])
# table of contents here
contents.append(
get_parsed_collection_items(
item,
recursive_level=recursive_level + 1,
parent=item
)
)
else: # it's an element (most likely a request)
contents.append(parse_collection_item_to_markdown(
item,
recursive_level,
parent=parent,
))
return '\n\n'.join(contents)
def get_with_variables_replaced(
markdown: str,
collection: dict,
) -> str:
"""
Replaces the variables so that the document is more readable,
as readable as possible without overengineering.
markdown : str
The raw generated markdown document
collection : dict
The parsed collection's content
returns str
"""
parametrized_markdown = markdown
copied_variables = collection['variable']
for variable in copied_variables:
key, value = variable['key'], variable['value']
parametrized_markdown = parametrized_markdown.replace(
'{{' + key + '}}',
value,
)
return parametrized_markdown
def get_markdown_from_collection(
collection: dict,
credit_repository=True,
) -> str:
"""
Generate/extract the markdown content from the collection's dict
collection : dict
The parsed collection's content
returns str
"""
markdown = []
markdown.append(get_collection_title(collection))
if credit_repository:
repository = 'https://github.com/jofaval/utilities'
filepath = f'{repository}/blob/master/python/postman-to-markdown.py'
markdown.append(f'Generated with [{filepath}]({filepath})')
markdown.append('## Contents')
markdown.append('\n'.join(get_collection_table_of_contents(collection)))
markdown.append(get_parsed_collection_items(
collection, parent={'name': 'Contents'}))
parsed_markdown = '\n\n'.join(markdown)
parsed_markdown = parsed_markdown.replace('\[', '[')
parsed_markdown = parsed_markdown.replace('\]', ']')
parsed_markdown = get_with_variables_replaced(parsed_markdown, collection)
return parsed_markdown
def save_mardown(
markdown: str,
destination: str,
) -> int:
"""
Saves the markdown in the required file
markdown : str
The markdown to save
destination : str
The file it will be saved in
returns int
"""
success = 0
with open(destination, 'w+') as writer:
success = writer.write(markdown)
return success
def main() -> None:
"""
Initializes the workflow
returns None
"""
args = get_args_parser().parse_args()
collection = get_collection_content(args.collection)
markdown = get_markdown_from_collection(collection)
# print(markdown)
success = save_mardown(markdown, destination=args.destination)
if __name__ == '__main__':
main()
# Collection
# "D:/Pepe/WebDesign/xampp/htdocs/e-learning/[Go(lang)] E-Learning.postman_collection.json"
# Destination
# "D:/Pepe/WebDesign/xampp/htdocs/utilities/ignore/python/postman-to-markdown/[Go(lang)] E-Learning.postman_collection.md"