| James Feist | 3cb5fec | 2018-01-23 14:41:51 -0800 | [diff] [blame] | 1 | #!/usr/bin/python3 | 
 | 2 | # all arguments to this script are considered as json files | 
 | 3 | # and attempted to be formatted alphabetically | 
 | 4 |  | 
 | 5 | import json | 
| James Feist | 5ffd8b4 | 2019-10-25 11:22:10 -0700 | [diff] [blame] | 6 | import os | 
| Potin Lai | 0f3a4d9 | 2023-12-05 00:13:55 +0800 | [diff] [blame] | 7 | import re | 
| James Feist | 3cb5fec | 2018-01-23 14:41:51 -0800 | [diff] [blame] | 8 | from sys import argv | 
| Potin Lai | 0f3a4d9 | 2023-12-05 00:13:55 +0800 | [diff] [blame] | 9 | from typing import List, Tuple, Union | 
 | 10 |  | 
 | 11 | # Trying to parse JSON comments and then being able to re-insert them into | 
 | 12 | # the correct location on a re-emitted and sorted JSON would be very difficult. | 
 | 13 | # To make this somewhat manageable, we take a few shortcuts here: | 
 | 14 | # | 
 | 15 | #       - Single-line style comments (//) can be on a new line or at the end of | 
 | 16 | #         a line with contents. | 
 | 17 | # | 
 | 18 | #       - Multi-line style comments (/* */) use the must be free-standing. | 
 | 19 | # | 
 | 20 | #       - Comments will get inserted back into the file in the line they came | 
 | 21 | #         from.  If keys are resorted or the number of lines change, all bets | 
 | 22 | #         for correctness are off. | 
 | 23 | # | 
 | 24 | #       - No attempts to re-indent multi-line comments will be made. | 
 | 25 | # | 
 | 26 | # In light of this, it is highly recommended to use a JSON formatter such as | 
 | 27 | # prettier before using this script and planning to move multi-line comments | 
 | 28 | # around after key resorting. | 
 | 29 |  | 
 | 30 |  | 
 | 31 | class CommentTracker: | 
 | 32 |     # Regex patterns used. | 
 | 33 |     single_line_pattern = re.compile(r"\s*//.*$") | 
 | 34 |     multi_line_start_pattern = re.compile(r"/\*") | 
 | 35 |     multi_line_end_pattern = re.compile(r".*\*/", re.MULTILINE | re.DOTALL) | 
 | 36 |  | 
 | 37 |     def __init__(self) -> None: | 
 | 38 |         self.comments: List[Tuple[bool, int, str]] = [] | 
 | 39 |  | 
 | 40 |     # Extract out the comments from a JSON-like string and save them away. | 
 | 41 |     def extract_comments(self, contents: str) -> str: | 
 | 42 |         result = [] | 
 | 43 |  | 
 | 44 |         multi_line_segment: Union[str, None] = None | 
 | 45 |         multi_line_start = 0 | 
 | 46 |  | 
 | 47 |         for idx, line in enumerate(contents.split("\n")): | 
 | 48 |             single = CommentTracker.single_line_pattern.search(line) | 
 | 49 |             if single: | 
 | 50 |                 do_append = False if line.startswith(single.group(0)) else True | 
 | 51 |                 line = line[: single.start(0)] | 
 | 52 |                 self.comments.append((do_append, idx, single.group(0))) | 
 | 53 |  | 
 | 54 |             multi_start = CommentTracker.multi_line_start_pattern.search(line) | 
 | 55 |             if not multi_line_segment and multi_start: | 
 | 56 |                 multi_line_start = idx | 
 | 57 |                 multi_line_segment = line | 
 | 58 |             elif multi_line_segment: | 
 | 59 |                 multi_line_segment = multi_line_segment + "\n" + line | 
 | 60 |  | 
 | 61 |             if not multi_line_segment: | 
 | 62 |                 result.append(line) | 
 | 63 |                 continue | 
 | 64 |  | 
 | 65 |             multi_end = CommentTracker.multi_line_end_pattern.search( | 
 | 66 |                 multi_line_segment | 
 | 67 |             ) | 
 | 68 |             if multi_end: | 
 | 69 |                 self.comments.append( | 
 | 70 |                     (False, multi_line_start, multi_end.group(0)) | 
 | 71 |                 ) | 
 | 72 |                 result.append(multi_line_segment[multi_end.end(0) :]) | 
 | 73 |                 multi_line_segment = None | 
 | 74 |  | 
 | 75 |         return "\n".join(result) | 
 | 76 |  | 
 | 77 |     # Re-insert the saved off comments into a JSON-like string. | 
 | 78 |     def insert_comments(self, contents: str) -> str: | 
 | 79 |         result = contents.split("\n") | 
 | 80 |  | 
 | 81 |         for append, idx, string in self.comments: | 
 | 82 |             if append: | 
 | 83 |                 result[idx] = result[idx] + string | 
 | 84 |             else: | 
 | 85 |                 result = result[:idx] + string.split("\n") + result[idx:] | 
 | 86 |  | 
 | 87 |         return "\n".join(result) | 
 | 88 |  | 
| James Feist | 3cb5fec | 2018-01-23 14:41:51 -0800 | [diff] [blame] | 89 |  | 
| Patrick Williams | ae74f23 | 2024-09-05 14:51:04 -0400 | [diff] [blame] | 90 | files = [] | 
| James Feist | 5ffd8b4 | 2019-10-25 11:22:10 -0700 | [diff] [blame] | 91 |  | 
| Patrick Williams | ae74f23 | 2024-09-05 14:51:04 -0400 | [diff] [blame] | 92 | for file in argv[1:]: | 
 | 93 |     if not os.path.isdir(file): | 
 | 94 |         files.append(file) | 
 | 95 |         continue | 
 | 96 |     for root, _, filenames in os.walk(file): | 
 | 97 |         for f in filenames: | 
 | 98 |             files.append(os.path.join(root, f)) | 
| James Feist | 5ffd8b4 | 2019-10-25 11:22:10 -0700 | [diff] [blame] | 99 |  | 
 | 100 | for file in files: | 
| Patrick Williams | cad2d1f | 2022-12-04 14:38:16 -0600 | [diff] [blame] | 101 |     if not file.endswith(".json"): | 
| Brad Bishop | ca000e5 | 2019-12-19 15:43:06 -0500 | [diff] [blame] | 102 |         continue | 
| James Feist | c4e5694 | 2019-04-19 12:15:19 -0700 | [diff] [blame] | 103 |     print("formatting file {}".format(file)) | 
| Potin Lai | 0f3a4d9 | 2023-12-05 00:13:55 +0800 | [diff] [blame] | 104 |  | 
 | 105 |     comments = CommentTracker() | 
 | 106 |  | 
 | 107 |     with open(file) as fp: | 
 | 108 |         j = json.loads(comments.extract_comments(fp.read())) | 
| James Feist | 3cb5fec | 2018-01-23 14:41:51 -0800 | [diff] [blame] | 109 |  | 
| James Feist | c4e5694 | 2019-04-19 12:15:19 -0700 | [diff] [blame] | 110 |     if isinstance(j, list): | 
 | 111 |         for item in j: | 
 | 112 |             item["Exposes"] = sorted(item["Exposes"], key=lambda k: k["Type"]) | 
 | 113 |     else: | 
 | 114 |         j["Exposes"] = sorted(j["Exposes"], key=lambda k: k["Type"]) | 
 | 115 |  | 
| Potin Lai | 0f3a4d9 | 2023-12-05 00:13:55 +0800 | [diff] [blame] | 116 |     with open(file, "w") as fp: | 
 | 117 |         contents = json.dumps( | 
 | 118 |             j, indent=4, sort_keys=True, separators=(",", ": ") | 
| Patrick Williams | cad2d1f | 2022-12-04 14:38:16 -0600 | [diff] [blame] | 119 |         ) | 
| Potin Lai | 0f3a4d9 | 2023-12-05 00:13:55 +0800 | [diff] [blame] | 120 |  | 
 | 121 |         fp.write(comments.insert_comments(contents)) | 
 | 122 |         fp.write("\n") |