|  | #!/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") |