swaggerizer 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,392 @@
1
+ import os, re, shutil, yaml
2
+ import sys, threading, time
3
+ from collections import defaultdict
4
+ from importlib import resources
5
+ from ruamel.yaml import YAML
6
+ from ruamel.yaml.error import YAMLError
7
+ from .validator import validator
8
+
9
+ class Swaggerizer:
10
+
11
+ def __init__(self, input_file):
12
+ self.target_file_path = os.path.abspath(input_file)
13
+ self.target_file_name = os.path.basename(self.target_file_path)
14
+ self.target_dir = os.path.dirname(self.target_file_path)
15
+ self.show_progress = False
16
+ self.version = None
17
+ self.src_sub_dirs = dict.fromkeys(["intro", "endpoints", "components"])
18
+ self.project_sections = {
19
+ # Correlate project sections to top-level OpenAPI sections...
20
+ "intro": {
21
+ "basics": {
22
+ "v2": ["swagger", "info", "schemes", "host", "basePath", "externalDocs"],
23
+ "v3": ["openapi", "info", "servers", "externalDocs"]
24
+ },
25
+ "other": ["tags"]
26
+ },
27
+ "endpoints": "paths",
28
+ "components": {
29
+ "v2": ["definitions", "parameters", "responses", "securityDefinitions"],
30
+ "v3": "components"
31
+ }
32
+ # ... anything else will get added to "misc".
33
+ }
34
+
35
+
36
+ def create(self):
37
+ if os.path.exists(self.target_file_path):
38
+ print(f"Found {self.target_file_path}.")
39
+ proceed = input("Create modularized OpenAPI project for this file? (Y/n) ").strip().lower()
40
+ else:
41
+ print(f"Could not find {self.target_file_path}.")
42
+ proceed = input("Create default modularized OpenAPI project in current directory? (Y/n) ").strip().lower()
43
+
44
+ if proceed in ["y", ""]:
45
+ print("\nCreating the project...")
46
+ self._create_project()
47
+ else:
48
+ print("\nCreate operation canceled.")
49
+
50
+
51
+ def _create_project(self):
52
+ # Check for existing "openapi_src" directory
53
+ openapi_src_dir = os.path.join(self.target_dir, "openapi_src")
54
+ if os.path.exists(openapi_src_dir):
55
+ print(f"\nError: Source directory {openapi_src_dir} already exists.")
56
+ print(" To avoid overwrite, rename this directory or create the new project somewhere else.\n")
57
+ print("Aborting project creation.")
58
+ return
59
+
60
+ # Validate the src file
61
+ if os.path.exists(self.target_file_path):
62
+ print("\n- Make sure input file is valid YAML", end=" ")
63
+ self.show_progress = True
64
+ progress_thread = threading.Thread(target=self._progress_tracker)
65
+ progress_thread.start()
66
+ try:
67
+ validator.validate_yaml(self.target_file_path)
68
+ finally:
69
+ self.show_progress = False
70
+ progress_thread.join()
71
+ print(" done")
72
+
73
+ # Create the project dir structure
74
+ print(f"- Create OpenAPI source directory: {openapi_src_dir}")
75
+ os.makedirs(openapi_src_dir)
76
+ self.src_sub_dirs.update({
77
+ "intro": os.path.join(openapi_src_dir, "0_intro"),
78
+ "endpoints": os.path.join(openapi_src_dir, "1_endpoints"),
79
+ "components": os.path.join(openapi_src_dir, "2_components")
80
+ })
81
+ for src_sub_dir in self.src_sub_dirs.values():
82
+ os.makedirs(src_sub_dir)
83
+
84
+ # Select the target file
85
+ if os.path.exists(self.target_file_path):
86
+ print(f"- Modularize OpenAPI content from file: {self.target_file_name}", end=" ")
87
+ else:
88
+ print("- Modularize OpenAPI content from the default 'pet store' file", end=" ")
89
+ self._copy_default_file(os.path.dirname(self.target_file_path), "openapi.yaml")
90
+
91
+ # Modularize the content...
92
+ self.show_progress = True
93
+ progress_thread = threading.Thread(target=self._progress_tracker)
94
+ progress_thread.start()
95
+
96
+ try:
97
+ self._extract_intro(self.src_sub_dirs["intro"])
98
+ self._extract_endpoints(self.src_sub_dirs["endpoints"])
99
+ if self.version == "v2":
100
+ self._extract_components_v2(self.src_sub_dirs["components"])
101
+ elif self.version == "v3":
102
+ self._extract_components_v3(self.src_sub_dirs["components"])
103
+ self._extract_misc(openapi_src_dir)
104
+ finally:
105
+ self.show_progress = False
106
+ progress_thread.join()
107
+ print(f" done (OpenAPI{self.version})")
108
+
109
+ # Add and update the makefile
110
+ print("- Add makefile to the source directory")
111
+ self._copy_default_file(openapi_src_dir, "makefile")
112
+ self._update_makefile(openapi_src_dir, self.src_sub_dirs["components"])
113
+
114
+ # Implement project structure creation and file organization here
115
+ print("\nProject creation complete!")
116
+ print("Read the docs @ https://swaggerizer.com")
117
+
118
+
119
+ def _extract_intro(self, output_dir):
120
+ """
121
+ Extracts OpenAPI intro sections as defined in self.project_sections["intro"]
122
+ and writes them to a series of files in src/intro.
123
+ """
124
+
125
+ # Use ruamel to preserve order and spacing
126
+ yaml = YAML()
127
+ yaml.preserve_quotes = True
128
+ yaml.indent(mapping=2, sequence=4, offset=2)
129
+ yaml.width = 4096
130
+
131
+ # Read the OpenAPI YAML input file
132
+ with open(self.target_file_path, 'r', encoding='utf-8') as input_file:
133
+ openapi_content = yaml.load(input_file)
134
+
135
+ # Determine the Swagger/OpenAPI version
136
+ if "swagger" in openapi_content:
137
+ self.version = "v2"
138
+ elif "openapi" in openapi_content:
139
+ self.version = "v3"
140
+ else:
141
+ raise ValueError("Error: Missing 'openapi' or 'swagger' tag in OpenAPI input file!")
142
+
143
+ # Get the project sections
144
+ basics_sections = self.project_sections["intro"]["basics"][self.version]
145
+ other_sections = self.project_sections["intro"]["other"]
146
+
147
+ # Write intro/basics.yaml
148
+ basics = {k: openapi_content[k] for k in basics_sections if k in openapi_content}
149
+ if basics:
150
+ output_file = os.path.join(output_dir, "basics.yaml")
151
+ with open(output_file, "w", encoding="utf-8") as file:
152
+ yaml.dump(basics, file)
153
+
154
+ # Write other sections to intro/<name>.yaml
155
+ for section in other_sections:
156
+ if section in openapi_content:
157
+ output_data = {section: openapi_content[section]}
158
+ output_file = os.path.join(output_dir, f"{section}.yaml")
159
+ with open(output_file, "w", encoding="utf-8") as file:
160
+ yaml.dump(output_data, file)
161
+
162
+
163
+ def _extract_endpoints(self, output_dir):
164
+ """
165
+ Extracts the endpoints from the OpenAPI "paths" section and writes them to
166
+ a series of files in src/endpoints named by the tag group.
167
+ """
168
+
169
+ # Use ruamel to preserve order and indentation
170
+ yaml = YAML()
171
+ yaml.preserve_quotes = True
172
+ yaml.indent(mapping=2, sequence=4, offset=2)
173
+
174
+ # Read the OpenAPI YAML input file
175
+ with open(self.target_file_path, 'r', encoding='utf-8') as file:
176
+ openapi_content = yaml.load(file)
177
+
178
+ # Make sure the "paths" section exists
179
+ if 'paths' not in openapi_content:
180
+ raise ValueError("Error: Missing 'paths' section in OpenAPI input file!")
181
+
182
+ # Group endpoints by tag
183
+ tag_endpoints = defaultdict(dict)
184
+ for path, methods in openapi_content['paths'].items():
185
+ for method, details in methods.items():
186
+ if 'tags' in details:
187
+ # store tagged endpoints by tag name
188
+ for tag in details['tags']:
189
+ tag = tag.replace(" ", "_").lower()
190
+ if path not in tag_endpoints[tag]:
191
+ tag_endpoints[tag][path] = {}
192
+ tag_endpoints[tag][path][method] = details
193
+ else:
194
+ # store untagged endpoints as "untagged"
195
+ tag_endpoints['untagged'][path] = tag_endpoints['untagged'].get(path, {})
196
+ tag_endpoints['untagged'][path][method] = details
197
+
198
+ # Write endpoints to the 'src/endpoints' dir
199
+ for tag, endpoints in tag_endpoints.items():
200
+ output_file_path = os.path.join(output_dir, f"{tag}.yaml")
201
+
202
+ # write to the output file
203
+ with open(output_file_path, 'w', encoding='utf-8') as output_file:
204
+ yaml.dump({'paths': endpoints}, output_file)
205
+
206
+ # remove 'paths:' label from output
207
+ self._chop_first_line(output_file_path)
208
+
209
+
210
+ def _extract_components_v2(self, output_dir):
211
+ """
212
+ Extracts the sections from the OpenAPI v2 top-level components sections and writes
213
+ them to a series of files in src/components named by the section.
214
+ """
215
+
216
+ # Use ruamel to preserve order and indentation
217
+ yaml = YAML()
218
+ yaml.preserve_quotes = True
219
+ yaml.indent(mapping=2, sequence=4, offset=2)
220
+
221
+ # Read the OpenAPI YAML input file
222
+ with open(self.target_file_path, 'r', encoding='utf-8') as file:
223
+ openapi_content = yaml.load(file)
224
+
225
+ # Get the list of v2 components
226
+ components_sections = self.project_sections["components"]["v2"]
227
+
228
+ # Write sections to the 'src/components' dir
229
+ for section_name in components_sections:
230
+ if section_name in openapi_content:
231
+ output_file = os.path.join(output_dir, f"{section_name}.yaml")
232
+
233
+ # include both section name and content
234
+ section_data = {section_name: openapi_content[section_name]}
235
+
236
+ # write to the output file
237
+ with open(output_file, 'w', encoding='utf-8') as out_file:
238
+ yaml.dump(section_data, out_file)
239
+
240
+
241
+ def _extract_components_v3(self, output_dir):
242
+ """
243
+ Extracts the sections from the OpenAPI v3 "components" section and writes them
244
+ to a series of files in src/components named by the section.
245
+ """
246
+
247
+ # Use ruamel to preserve order and indentation
248
+ yaml = YAML()
249
+ yaml.preserve_quotes = True
250
+ yaml.indent(mapping=2, sequence=4, offset=2)
251
+
252
+ # Read the OpenAPI YAML input file
253
+ with open(self.target_file_path, 'r', encoding='utf-8') as file:
254
+ openapi_content = yaml.load(file)
255
+
256
+ # Make sure the "components" section exists
257
+ if 'components' not in openapi_content:
258
+ return
259
+
260
+ # Write components to the 'src/components' dir
261
+ components = openapi_content['components']
262
+ for section_name, section_content in components.items():
263
+ output_file = os.path.join(output_dir, f"{section_name}.yaml")
264
+ yaml.indent(mapping=4, sequence=4, offset=2)
265
+
266
+ # write to the output file
267
+ with open(output_file, 'w', encoding='utf-8') as out_file:
268
+ yaml.dump({'components': {section_name: section_content}}, out_file)
269
+
270
+ # remove 'components:' label from output
271
+ self._chop_first_line(output_file)
272
+
273
+
274
+ def _extract_misc(self, openapi_src_dir):
275
+ """
276
+ Extracts any other top-level OpenAPI sections besides the ones defined in
277
+ self.project_sections. If present, writes them to src/misc.
278
+ """
279
+
280
+ # Use ruamel to preserve order and spacing
281
+ yaml = YAML()
282
+ yaml.preserve_quotes = True
283
+ yaml.indent(mapping=2, sequence=4, offset=2)
284
+ yaml.width = 4096
285
+
286
+ # Read the OpenAPI YAML input file
287
+ with open(self.target_file_path, 'r', encoding='utf-8') as input_file:
288
+ openapi_content = yaml.load(input_file)
289
+
290
+ # Assign all the main sections to a set
291
+ assigned_keys = self._collect_keys(self.project_sections)
292
+
293
+ # Find any unassigned keys in the OpenAPI file
294
+ misc_keys = sorted(set(openapi_content.keys()) - assigned_keys)
295
+
296
+ # If there misc keys, save and write them to src/misc dir
297
+ if misc_keys:
298
+ # save keys and create 'src/misc' dir
299
+ self.src_sub_dirs["misc"] = os.path.join(openapi_src_dir, "3_misc")
300
+ os.makedirs(self.src_sub_dirs["misc"])
301
+ self.project_sections["misc"] = misc_keys
302
+
303
+ # Write each section to 'misc/<name>.yaml'
304
+ for name in misc_keys:
305
+ output_data = {name: openapi_content[name]}
306
+ output_file = os.path.join(self.src_sub_dirs["misc"], f"{name}.yaml")
307
+ with open(output_file, "w", encoding="utf-8") as file:
308
+ yaml.dump(output_data, file)
309
+
310
+
311
+ def _copy_default_file(self, target_dir, filename):
312
+ """
313
+ Copy default template files from swaggerizer/files into target_dir.
314
+ """
315
+ os.makedirs(target_dir, exist_ok=True)
316
+ src = resources.files("swaggerizer.files").joinpath(filename)
317
+ dest = os.path.join(target_dir, filename)
318
+ shutil.copy(src, dest)
319
+
320
+
321
+ def _update_makefile(self, src_dir, components_dir):
322
+ """
323
+ Add target to makefile to build final OpenAPI yaml file.
324
+ If no misc sections present, remove that part.
325
+ """
326
+
327
+ # Read default makefile content
328
+ make_file = os.path.join(src_dir, 'makefile')
329
+ with open(make_file, 'r', encoding='utf-8') as file:
330
+ content = file.read()
331
+
332
+ # Replace placeholder build target
333
+ content = re.sub(r'FINAL_BUILD_TARGET', self.target_file_name, content)
334
+
335
+ # If no components files, remove components build
336
+ if not os.listdir(components_dir):
337
+ content = re.sub(r'^.+components[:/].+\n', '', content, flags=re.MULTILINE)
338
+ elif self.version == "v2":
339
+ content = re.sub(r'^.+components[:].+\n', '', content, flags=re.MULTILINE)
340
+
341
+ # If no misc sections, remove misc build
342
+ if not self.project_sections.get("misc"):
343
+ content = re.sub(r'^.+_misc[/].+\n', '', content, flags=re.MULTILINE)
344
+
345
+ # Write final version of makefile
346
+ with open(make_file, 'w', encoding='utf-8') as file:
347
+ file.write(content)
348
+
349
+
350
+ def _collect_keys(self, data):
351
+ """
352
+ Iterate nested data structure to get all the keys.
353
+ """
354
+ keys = set()
355
+ if isinstance(data, str):
356
+ keys.add(data)
357
+ elif isinstance(data, list):
358
+ keys.update(data)
359
+ elif isinstance(data, dict):
360
+ for val in data.values():
361
+ if isinstance(val, dict) and self.version in val:
362
+ keys.update(self._collect_keys(val[self.version]))
363
+ else:
364
+ keys.update(self._collect_keys(val))
365
+ return keys
366
+
367
+
368
+ def _chop_first_line(self, file_path):
369
+ """
370
+ Remove 1st line of output file during OpenAPI content modularization.
371
+ """
372
+ with open(file_path, 'r', encoding='utf-8') as file:
373
+ lines = file.readlines()
374
+
375
+ if len(lines) > 1:
376
+ # ouput everything after line 1
377
+ with open(file_path, 'w', encoding='utf-8') as file:
378
+ file.writelines(lines[1:])
379
+ else:
380
+ # output empty file
381
+ open(file_path, 'w').close()
382
+
383
+
384
+ def _progress_tracker(self):
385
+ """
386
+ Progress bar for long modularize operations.
387
+ """
388
+ while self.show_progress:
389
+ sys.stdout.write(".")
390
+ sys.stdout.flush()
391
+ time.sleep(2)
392
+
@@ -0,0 +1,112 @@
1
+ import os, yaml, json
2
+ from .json_utils import json_serializer
3
+
4
+ """
5
+ Helper class for converting Swagger YAML files into HTML format.
6
+
7
+ Example usage:
8
+ exporter = HTMLExporter("input.yaml", "output.html")
9
+ exporter.export()
10
+
11
+ # Copyright 2024 JR Halterlein
12
+ # Licensed under the Apache License, Version 2.0.
13
+ # See http://www.apache.org/licenses/LICENSE-2.0 for the full text.
14
+ #
15
+ # This file is based on
16
+ # https://github.com/swagger-api/swagger-ui/blob/4f1772f6544699bc748299bd65f7ae2112777abc/dist/index.html
17
+ # (Copyright 2017 SmartBear Software, Licensed under Apache 2.0)
18
+ # https://github.com/yousan/swagger-yaml-to-html/blob/master/swagger-yaml-to-html.py
19
+ # (Copyright 2017 Otto Seiskari, Licensed under Apache 2.0)
20
+ """
21
+
22
+
23
+ class HTMLExporter:
24
+ TEMPLATE = """
25
+ <!DOCTYPE html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="UTF-8">
29
+ <title>Swagger UI</title>
30
+ <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
31
+ <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.28.0/swagger-ui.css" >
32
+ <link rel="stylesheet" type="text/css" href="customizer.css" >
33
+ <style>
34
+ html
35
+ {
36
+ box-sizing: border-box;
37
+ overflow: -moz-scrollbars-vertical;
38
+ overflow-y: scroll;
39
+ }
40
+ *,
41
+ *:before,
42
+ *:after
43
+ {
44
+ box-sizing: inherit;
45
+ }
46
+ body {
47
+ margin:0;
48
+ background: #fafafa;
49
+ }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div id="swagger-ui"></div>
54
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.28.0/swagger-ui-bundle.js"> </script>
55
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/5.28.0/swagger-ui-standalone-preset.js"> </script>
56
+ <script src="customizer.js"> </script>
57
+ <script>
58
+ window.onload = function() {
59
+ var spec = %s;
60
+ // Build a system
61
+ const ui = SwaggerUIBundle({
62
+ spec: spec,
63
+ dom_id: '#swagger-ui',
64
+ deepLinking: true,
65
+ presets: [
66
+ SwaggerUIBundle.presets.apis,
67
+ SwaggerUIStandalonePreset
68
+ ],
69
+ plugins: [
70
+ SwaggerUIBundle.plugins.DownloadUrl
71
+ ],
72
+ layout: "StandaloneLayout"
73
+ })
74
+ window.ui = ui
75
+ }
76
+ </script>
77
+ </body>
78
+ </html>
79
+ """
80
+
81
+
82
+ def __init__(self, input_file, output_file):
83
+ self.input_file = input_file
84
+ if output_file:
85
+ self.output_file = output_file
86
+ else:
87
+ # Derive from input filename
88
+ input_dir = os.path.dirname(input_file)
89
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
90
+ self.output_file = os.path.join(input_dir, f"{base_name}.html")
91
+
92
+
93
+ def export(self):
94
+ try:
95
+ # Read the YAML file
96
+ with open(self.input_file, 'r', encoding='utf-8') as infile:
97
+ spec = yaml.load(infile, Loader=yaml.FullLoader)
98
+
99
+ # Generate the HTML content
100
+ html_content = self.TEMPLATE % json.dumps(spec, default=json_serializer)
101
+
102
+ # Write the HTML content to the output file
103
+ with open(self.output_file, 'w', encoding='utf-8') as outfile:
104
+ outfile.write(html_content)
105
+
106
+ print(f"Exported HTML to {self.output_file}")
107
+
108
+ except FileNotFoundError:
109
+ print(f"Error: Input file '{self.input_file}' not found.")
110
+ except Exception as e:
111
+ print(f"Error during export: {e}")
112
+
@@ -0,0 +1,44 @@
1
+ import os, json, yaml
2
+
3
+ """
4
+ Helper class for converting JSON files into YAML format.
5
+
6
+ Example usage:
7
+ converter = json2YamlConverter(args.input, args.output)
8
+ converter.convert()
9
+
10
+ # Copyright 2024 JR Halterlein
11
+ # Licensed under the Apache License, Version 2.0.
12
+ # See http://www.apache.org/licenses/LICENSE-2.0 for the full text.
13
+ """
14
+
15
+
16
+ class json2YamlConverter:
17
+ def __init__(self, input_file, output_file):
18
+ self.input_file = input_file
19
+ if output_file:
20
+ self.output_file = output_file
21
+ else:
22
+ # Derive from input filename
23
+ input_dir = os.path.dirname(input_file)
24
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
25
+ self.output_file = os.path.join(input_dir, f"{base_name}.yaml")
26
+
27
+ def convert(self):
28
+ try:
29
+ with open(self.input_file, 'r') as json_file:
30
+ data = json.load(json_file)
31
+ with open(self.output_file, 'w') as yaml_file:
32
+ yaml.dump(data, yaml_file, default_flow_style=False)
33
+
34
+ print(f"Converted JSON file {self.input_file} to YAML file {self.output_file}.")
35
+
36
+ except FileNotFoundError:
37
+ print(f"Error: Input file '{self.input_file}' not found.")
38
+ except json.JSONDecodeError as e:
39
+ print(f"Error: Failed to parse JSON file. Details: {e}")
40
+ except PermissionError:
41
+ print(f"Error: Permission denied when accessing '{self.output_file}'.")
42
+ except Exception as e:
43
+ print(f"An unexpected error occurred: {e}")
44
+
@@ -0,0 +1,11 @@
1
+ from datetime import date, datetime
2
+
3
+ def json_serializer(obj):
4
+ """
5
+ Custom JSON serializer for non-serializable objects.
6
+ Converts datetime.date and datetime.datetime to ISO 8601 strings.
7
+ """
8
+ if isinstance(obj, (date, datetime)):
9
+ return obj.isoformat()
10
+ raise TypeError(f"Type {type(obj)} not serializable")
11
+
@@ -0,0 +1,67 @@
1
+ import os, sys, json
2
+ from json import JSONDecodeError
3
+ from ruamel.yaml import YAML
4
+ from ruamel.yaml.error import YAMLError
5
+
6
+ class validator:
7
+
8
+ @staticmethod
9
+ def validate(file_path, file_type):
10
+ """
11
+ Validate a file as YAML or JSON, depending on file_type ('yaml'/'yml' or 'json').
12
+ Confirms the file exists before validation.
13
+ """
14
+ if not os.path.isfile(file_path):
15
+ print(f"\nInput file does not exist: {file_path}\n")
16
+ sys.exit(1)
17
+
18
+ type = (file_type or "").strip().lower()
19
+ if not type:
20
+ file, file_type = os.path.splitext(file_path)
21
+ type = file_type.lstrip(".").lower()
22
+
23
+ if type in ("yaml", "yml"):
24
+ validator.validate_yaml(file_path)
25
+ print(f"\n{file_path} is valid YAML.\n")
26
+ elif type == "json":
27
+ validator.validate_json(file_path)
28
+ print(f"\n{file_path} is valid JSON.\n")
29
+ else:
30
+ print(f"\nUnsupported file type: {type!r}. Expected 'yaml'/'yml' or 'json'.\n")
31
+ sys.exit(1)
32
+
33
+
34
+ @staticmethod
35
+ def validate_yaml(file_path):
36
+ """
37
+ Validate structural YAML in src file.
38
+ Note: Does not validate OpenAPI spec.
39
+ """
40
+ yaml = YAML()
41
+
42
+ # Validate YAML structure
43
+ try:
44
+ with open(file_path, 'r', encoding='utf-8') as file:
45
+ file_content = yaml.load(file)
46
+ except YAMLError as e:
47
+ print(f"\nCould not parse YAML for input file: {file_path}")
48
+ print(f"Error details:\n\n{e}")
49
+ print("Fix YAML structural issues and try again.")
50
+ sys.exit(1)
51
+
52
+
53
+ @staticmethod
54
+ def validate_json(file_path):
55
+ """
56
+ Validate structural JSON in src file.
57
+ Note: Does not validate OpenAPI spec.
58
+ """
59
+ try:
60
+ with open(file_path, 'r', encoding='utf-8') as file:
61
+ file_content = json.load(file)
62
+ except JSONDecodeError as e:
63
+ print(f"\nCould not parse JSON for input file: {file_path}")
64
+ print(f"Error details:\n\n{e}")
65
+ print("Fix JSON structural issues and try again.")
66
+ sys.exit(1)
67
+
@@ -0,0 +1,45 @@
1
+ import os, json, yaml
2
+ from .json_utils import json_serializer
3
+
4
+ """
5
+ Helper class for converting YAML files into JSON format.
6
+
7
+ Example usage:
8
+ converter = yaml2JsonConverter(args.input, args.output)
9
+ converter.convert()
10
+
11
+ # Copyright 2024 JR Halterlein
12
+ # Licensed under the Apache License, Version 2.0.
13
+ # See http://www.apache.org/licenses/LICENSE-2.0 for the full text.
14
+ """
15
+
16
+
17
+ class yaml2JsonConverter:
18
+ def __init__(self, input_file, output_file):
19
+ self.input_file = input_file
20
+ if output_file:
21
+ self.output_file = output_file
22
+ else:
23
+ # Derive from input filename
24
+ input_dir = os.path.dirname(input_file)
25
+ base_name = os.path.splitext(os.path.basename(input_file))[0]
26
+ self.output_file = os.path.join(input_dir, f"{base_name}.json")
27
+
28
+ def convert(self):
29
+ try:
30
+ with open(self.input_file, 'r') as yaml_file:
31
+ data = yaml.safe_load(yaml_file)
32
+ with open(self.output_file, 'w') as json_file:
33
+ json.dump(data, json_file, indent=4, default=json_serializer)
34
+
35
+ print(f"Converted YAML file {self.input_file} to JSON file {self.output_file}")
36
+
37
+ except FileNotFoundError:
38
+ print(f"Error: Input file '{self.input_file}' not found.")
39
+ except yaml.YAMLError as e:
40
+ print(f"Error: Failed to parse YAML file. Details: {e}")
41
+ except PermissionError:
42
+ print(f"Error: Permission denied when accessing '{self.output_file}'.")
43
+ except Exception as e:
44
+ print(f"An unexpected error occurred: {e}")
45
+