| #!/usr/bin/python3 | 
 | # all arguments to this script are considered as json files | 
 | # and attempted to be formatted alphabetically | 
 |  | 
 | import json | 
 | import os | 
 | import re | 
 | from sys import argv | 
 | from typing import List, Tuple, Union | 
 |  | 
 | # Trying to parse JSON comments and then being able to re-insert them into | 
 | # the correct location on a re-emitted and sorted JSON would be very difficult. | 
 | # To make this somewhat manageable, we take a few shortcuts here: | 
 | # | 
 | #       - Single-line style comments (//) can be on a new line or at the end of | 
 | #         a line with contents. | 
 | # | 
 | #       - Multi-line style comments (/* */) use the must be free-standing. | 
 | # | 
 | #       - Comments will get inserted back into the file in the line they came | 
 | #         from.  If keys are resorted or the number of lines change, all bets | 
 | #         for correctness are off. | 
 | # | 
 | #       - No attempts to re-indent multi-line comments will be made. | 
 | # | 
 | # In light of this, it is highly recommended to use a JSON formatter such as | 
 | # prettier before using this script and planning to move multi-line comments | 
 | # around after key resorting. | 
 |  | 
 |  | 
 | class CommentTracker: | 
 |     # Regex patterns used. | 
 |     single_line_pattern = re.compile(r"\s*//.*$") | 
 |     multi_line_start_pattern = re.compile(r"/\*") | 
 |     multi_line_end_pattern = re.compile(r".*\*/", re.MULTILINE | re.DOTALL) | 
 |  | 
 |     def __init__(self) -> None: | 
 |         self.comments: List[Tuple[bool, int, str]] = [] | 
 |  | 
 |     # Extract out the comments from a JSON-like string and save them away. | 
 |     def extract_comments(self, contents: str) -> str: | 
 |         result = [] | 
 |  | 
 |         multi_line_segment: Union[str, None] = None | 
 |         multi_line_start = 0 | 
 |  | 
 |         for idx, line in enumerate(contents.split("\n")): | 
 |             single = CommentTracker.single_line_pattern.search(line) | 
 |             if single: | 
 |                 do_append = False if line.startswith(single.group(0)) else True | 
 |                 line = line[: single.start(0)] | 
 |                 self.comments.append((do_append, idx, single.group(0))) | 
 |  | 
 |             multi_start = CommentTracker.multi_line_start_pattern.search(line) | 
 |             if not multi_line_segment and multi_start: | 
 |                 multi_line_start = idx | 
 |                 multi_line_segment = line | 
 |             elif multi_line_segment: | 
 |                 multi_line_segment = multi_line_segment + "\n" + line | 
 |  | 
 |             if not multi_line_segment: | 
 |                 result.append(line) | 
 |                 continue | 
 |  | 
 |             multi_end = CommentTracker.multi_line_end_pattern.search( | 
 |                 multi_line_segment | 
 |             ) | 
 |             if multi_end: | 
 |                 self.comments.append( | 
 |                     (False, multi_line_start, multi_end.group(0)) | 
 |                 ) | 
 |                 result.append(multi_line_segment[multi_end.end(0) :]) | 
 |                 multi_line_segment = None | 
 |  | 
 |         return "\n".join(result) | 
 |  | 
 |     # Re-insert the saved off comments into a JSON-like string. | 
 |     def insert_comments(self, contents: str) -> str: | 
 |         result = contents.split("\n") | 
 |  | 
 |         for append, idx, string in self.comments: | 
 |             if append: | 
 |                 result[idx] = result[idx] + string | 
 |             else: | 
 |                 result = result[:idx] + string.split("\n") + result[idx:] | 
 |  | 
 |         return "\n".join(result) | 
 |  | 
 |  | 
 | files = [] | 
 |  | 
 | for file in argv[1:]: | 
 |     if not os.path.isdir(file): | 
 |         files.append(file) | 
 |         continue | 
 |     for root, _, filenames in os.walk(file): | 
 |         for f in filenames: | 
 |             files.append(os.path.join(root, f)) | 
 |  | 
 | for file in files: | 
 |     if not file.endswith(".json"): | 
 |         continue | 
 |     print("formatting file {}".format(file)) | 
 |  | 
 |     comments = CommentTracker() | 
 |  | 
 |     with open(file) as fp: | 
 |         j = json.loads(comments.extract_comments(fp.read())) | 
 |  | 
 |     if isinstance(j, list): | 
 |         for item in j: | 
 |             item["Exposes"] = sorted(item["Exposes"], key=lambda k: k["Type"]) | 
 |     else: | 
 |         j["Exposes"] = sorted(j["Exposes"], key=lambda k: k["Type"]) | 
 |  | 
 |     with open(file, "w") as fp: | 
 |         contents = json.dumps( | 
 |             j, indent=4, sort_keys=True, separators=(",", ": ") | 
 |         ) | 
 |  | 
 |         fp.write(comments.insert_comments(contents)) | 
 |         fp.write("\n") |